I am downloading the file using OkHttp3, I want to see the downloading speed. but I am confused about how to measure the speed.
I tried getting the current millis before reading the buffer and calculating after it is written, but it always returns a static value.
Following is my download function.
fun download(fileName: String) {
val request = Request.Builder().url(url)
.get().build()
val call = OkHttpClient().newCall(request)
val response = call.execute()
if (response.isSuccessful) {
var inputStream: InputStream? = null
try {
inputStream = response.body()?.byteStream()
val buffer = ByteArray(8192)
val mediaFile = File(downloadDir, fileName)
val output = RandomAccessFile(mediaFile, "rw")
output.seek(0)
while (true) {
val readed = inputStream?.read(buffer)
if (readed == -1 || readed == null) {
break
}
output.write(buffer, 0, readed)
downloaded.append(readed.toLong())
}
output.close()
} catch (e: IOException) {
// TODO: handle IOException
console.log("${e.message}")
} finally {
inputStream?.close()
}
}
}
It's a very simple problem, I got confused by overthinking. Anyway here is the solution.
all I need to do is store the downloaded bytes in a variable after 1s subtract downloaded bytes from newly downloaded bytes, that will give me the downloaded bytes in 1s, then I can use those bytes to convert into speed like kbps or Mbps.
fun getSpeed(callback: (String) -> Unit) {
doAsync {
var prevDownloaded = 0L
while (true) {
if (contentLength != null) {
if (downloaded.get() >= contentLength!!) {
break
}
}
if (prevDownloaded != 0L) {
callback(formatBytes(downloaded.get() - prevDownloaded))
}
prevDownloaded = downloaded.get()
Thread.sleep(1000)
}
}
}
Related
I wrote code that downloads some source of data from the internet (in this example picture), shows downloadPercentages while the process of downloading is ongoing and writes this file on android storage. works well and looks very nice everything except saving on android storage.
Code is written in 3 classes, but I will show only one that I think is relevant (DownloadWorker). If anyone thinks other classes might help, let me now in comment and I will add them.
DownloadWorker:
class DownloadWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
companion object {
const val FILE_NAME = "image.jpg"
}
override fun doWork(): Result {
try {
if (DownloadHelper.url == null) {
DownloadHelper.downloadState.postValue(DownloadState.Failure)
return Result.failure()
}
DownloadHelper.url?.apply {
if(!startsWith("https")) {
DownloadHelper.url = replace("http", "https")
}
}
val url = URL(DownloadHelper.url)
val connection = url.openConnection()
connection.connect()
val fileSize = connection.contentLength
val inputStream = connection.getInputStream()
val buffer = ByteArray(1024)
val file = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
val outputFile = File(file, FILE_NAME)
var len = 0
var total = 0L
val fos = FileOutputStream(outputFile)
len = inputStream.read(buffer)
while (len != -1) {
fos.write(buffer, 0, len)
total += len
val percent = ((total * 100) / fileSize).toInt()
DownloadHelper.downloadState.postValue(DownloadState.InProgress(percent))
len = inputStream.read(buffer)
}
fos.close()
inputStream.close()
DownloadHelper.downloadState.postValue(DownloadState.Success(outputFile))
} catch (e: Exception) {
DownloadHelper.downloadState.postValue(DownloadState.Failure)
return Result.failure()
}
return Result.success()
}
}
After download success, my image is not shown in gallery, or in downloaded files folder. To see this image you need to enter android storage, find in android data app package by name and navigate all the way to the image. Pretty complicated.
Can anyone help, thanks.
I am using Kotlin and writing a function to upload a file. While testing, I have observed that if I do the following operations, OutOfMemory exception is raised and onFailure callback is called.
Select a file of size 100 MB to upload it to server. Upload it by calling uploadFile.
While it uploads, disconnect the internet. onFailure is called with an exception (timeout).
Enable the internet again and try to upload it again by calling uploadFile function.
Repeat the step 2 and 3 for 1-2 more times and app crashes. onFailure is called this time with OutOfMemory exception.
Here is my code.
class UploadManager(
private val fileTransferDataSource: IListFileTransferDataSource
) {
private val uploadClient by lazy {
OkHttpClient.Builder()
.retryOnConnectionFailure(false)
.writeTimeout(40, TimeUnit.SECONDS)
.readTimeout(40, TimeUnit.SECONDS)
.build()
}
suspend fun uploadFile(url: String,
fileUri: String,
downloadUrl: String?,
stream: InputStream,
callback: ((success: Boolean, filePath: String, url: String?, responseCode: Int?) -> Unit)? = null) {
val baseUrl = FileTransferUtility.getBaseUrl(url)
val authToken = fileTransferDataSource.getAuthenticationToken(baseUrl)
if (baseUrl.isEmpty() || authToken.isEmpty()) {
stream.close()
callback?.let { it(false, fileUri, null, null) }
return
}
kotlin.runCatching {
val buf = ByteArray(stream.available())
val bytesRead = stream.read(buf)
stream.close()
if (bytesRead == -1) {
callback?.let { it(false, fileUri, null, null) }
return#runCatching
}
val requestBody = create(FileTransferUtility.contentTypeStream.toMediaType(), buf)
val request = requestBody.let {
Request.Builder()
.url(url)
.post(it)
.addHeader(HttpConstants.Headers.AUTHORIZATION, HttpConstants.Values.AUTHORIZATION_TOKEN_BEARER_FORMAT.format(authToken))
.addHeader(HttpConstants.Headers.CONTENT_TYPE, HttpConstants.Values.APPLICATION_JSON)
.addHeader(HttpConstants.Headers.ACCEPT, HttpConstants.Values.APPLICATION_JSON)
.build()
}
if (request == null) {
callback?.let { it(false, fileUri, null, null) }
return#runCatching
}
uploadClient.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
call.cancel()
callback?.let { it(false, fileUri, null, null) }
}
override fun onResponse(call: Call, response: Response) {
response.body?.close()
callback?.let { it(response.code == 200, fileUri, downloadUrl, response.code) }
}
})
}
}
//Restricting object creation for this class by making it singleton
companion object : SingletonHolder<UploadManager, IListFileTransferDataSource>(::UploadManager)
}
However, it doesn't crash even if I upload multiple files of size 100 MB and they gets upload without any issue. Problem happens only when onFailure is triggered multiple times. I am suspecting the some internal buffer aren't getting deallocating on failure.
I have tired the following.
Cancelling the dispatcher
Add interceptor for Logging with log level None.
cancelling call explicitly in onFailure callback
Nothing seems to resolve this issue. Please help me to identify the memory leak.
Stack Trace:
java.io.IOException: canceled due to java.lang.OutOfMemoryError: Failed to allocate a 8208 byte allocation with 200 free bytes and 200B until OOM, target footprint 268435456, growth limit 268435456
0 = {StackTraceElement#16551} "okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:515)"
1 = {StackTraceElement#16552} "java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)"
2 = {StackTraceElement#16553} "java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)"
3 = {StackTraceElement#16554} "java.lang.Thread.run(Thread.java:919)"
I have realized the problem was with val buf = ByteArray(inputStream.available()). It creates a ByteArray of huge size in a single go. If we have to upload multiple files of larger size (in my case, 100MB), garbage collector take a while to free the memory from last uploads. It was causing the memory exception. I have changed my code and it stopped being a problem:
suspend fun uploadFile(
url: String,
fileUri: String,
downloadUrl: String?,
stream: InputStream,
coroutineScope: CoroutineScope
): Flow<Result> = flow {
kotlin.runCatching {
val baseUrl = FileTransferUtility.getBaseUrl(url)
val authToken = fileTransferDataSource.getAuthenticationToken(baseUrl)
if (baseUrl.isEmpty() || authToken.isEmpty()) {
stream.close()
emit(Result.Failure(fileUri))
return#flow
}
var connection: HttpURLConnection? = null
val bufferedInputStream = stream.buffered()
try {
val fileSize = stream.available()
if (fileSize == 0)
throw IOException("Unable to read file $fileUri")
connection = (URL(url).openConnection() as HttpURLConnection).also { conn ->
addHeadersForUpload(conn, authToken, fileSize)
conn.readTimeout = UPLOAD_READ_TIMEOUT
}
var progress = 0
emit(Result.InProgress(fileUri, progress))
BufferedOutputStream(connection.outputStream).use { uploadStream ->
var bytesWritten = 0
val buffer = ByteArray(DEFAULT_UPLOAD_BUFFER_SIZE)
while (true) {
val size = bufferedInputStream.read(buffer)
if (size <= 0)
break
bytesWritten += size
progress = ((100f * bytesWritten) / fileSize).toInt()
emit(Result.InProgress(fileUri, progress))
uploadStream.write(buffer, 0, size)
}
uploadStream.flush()
}
val responseCode = connection.responseCode
val isSuccess = responseCode == 200
if (isSuccess) {
if (progress < 100)
emit(Result.InProgress(fileUri, 100))
emit(Result.Success(fileUri, downloadUrl, readResponseBody(connection)))
} else {
emit(Result.Failure(fileUri))
}
} catch (e: Exception) {
emit(Result.Failure(fileUri))
} finally {
bufferedInputStream.close()
connection?.disconnect()
}
}
}.flowOn(Dispatchers.IO)
private fun readResponseBody(connection: HttpURLConnection): String? {
val builder = StringBuilder()
val lineBreak = System.getProperty("line.separator")
BufferedReader(connection.inputStream.reader()).use {
val line = it.readLine() ?: return#use
builder.append(line + lineBreak)
}
return builder.toString().trim(*lineBreak.toCharArray())
}
Basically, I am trying to download three different images(bitmaps) from a URL and save them to Apps Internal storage, and then use the URI's from the saved file to save a new Entity to my database. I am having a lot of issues with running this in parallel and getting it to work properly. As ideally all three images would be downloaded, saved and URI's returned simultaneously. Most of my issues come from blocking calls that I cannot seem to avoid.
Here's all of the relevant code
private val okHttpClient: OkHttpClient = OkHttpClient()
suspend fun saveImageToDB(networkImageModel: CBImageNetworkModel): Result<Long> {
return withContext(Dispatchers.IO) {
try {
//Upload all three images to local storage
val edgesUri = this.async {
val req = Request.Builder().url(networkImageModel.edgesImageUrl).build()
val response = okHttpClient.newCall(req).execute() // BLOCKING
val btEdges = BitmapFactory.decodeStream(response.body?.byteStream())
return#async saveBitmapToAppStorage(btEdges, ImageType.EDGES)
}
val finalUri = this.async {
val urlFinal = URL(networkImageModel.finalImageUrl) // BLOCKING
val btFinal = BitmapFactory.decodeStream(urlFinal.openStream())
return#async saveBitmapToAppStorage(btFinal, ImageType.FINAL)
}
val labelUri = this.async {
val urlLabels = URL(networkImageModel.labelsImageUrl)
val btLabel = BitmapFactory.decodeStream(urlLabels.openStream())
return#async saveBitmapToAppStorage(btLabel, ImageType.LABELS)
}
awaitAll(edgesUri, finalUri, labelUri)
if(edgesUri.getCompleted() == null || finalUri.getCompleted() == null || labelUri.getCompleted() == null) {
return#withContext Result.failure(Exception("An image couldn't be saved"))
}
} catch (e: Exception) {
Result.failure<Long>(e)
}
try {
// Result.success( db.imageDao().insertImage(image))
Result.success(123) // A placeholder untill I actually get the URI's to create my Db Entity
} catch (e: Exception) {
Timber.e(e)
Result.failure(e)
}
}
}
//Save the bitmap and return Uri or null if failed
private fun saveBitmapToAppStorage(bitmap: Bitmap, imageType: ImageType): Uri? {
val type = when (imageType) {
ImageType.EDGES -> "edges"
ImageType.LABELS -> "labels"
ImageType.FINAL -> "final"
}
val filename = "img_" + System.currentTimeMillis().toString() + "_" + type
val file = File(context.filesDir, filename)
try {
val fos = file.outputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
} catch (e: Exception) {
Timber.e(e)
return null
}
return file.toUri()
}
Here I am calling this function
viewModelScope.launch {
val imageID = appRepository.saveImageToDB(imageNetworkModel)
withContext(Dispatchers.Main) {
val uri = Uri.parse("$PAINT_DEEPLINK/$imageID")
navManager.navigate(uri)
}
}
Another issue I am facing is returning the URI in the first place and handling errors. As if one of these parts fails, I'd like to cancel the whole thing and return Result.failure(), but I am unsure on how to achieve that. As returning null just seems meh, I'd much prefer to have an error message or something along those lines.
I want Google Speech to text API to recognize a short phrase after I press a button. So I came up with the following code. But it keeps returning no results. I'm quite confused, there are results in there (the buffer etc.), the mic is working well and is enabled in the emulator. Google console also doesn't show errors.
Here's my code.
Click listener that starts the recording:
val clicker: View.OnClickListener = View.OnClickListener {
Log.d(TAG, "Starting record thread")
mAudioRecorder.record(LISTEN_TIME_MILLIS)
}
mReadButton.setOnClickListener(clicker)
Here's a broadcast receiver that processes the results and tries to send them to Google:
private val broadCastReceiver = object : BroadcastReceiver() {
override fun onReceive(contxt: Context?, intent: Intent?) {
if (intent!!.getBooleanExtra(RECORDING_SUCCESS, false)) {
val byteArrayExtra = intent.getByteArrayExtra(RECORDING_AUDIO)
val audioResultByteString: ByteString = ByteString.copyFrom(byteArrayExtra)
if (audioResultByteString.size() > 0) {
val audio: RecognitionAudio = RecognitionAudio.newBuilder()
.setContent(audioResultByteString).build()
val resultsList = mSpeechClient.recognize(config, audio).resultsList
if (resultsList.size > 0) {
for (result in resultsList) {
val resultText = result.alternativesList[0].transcript
}
}
Log.d(TAG, "- Done recognition. Result Qty: ${resultsList.size}")
}
}
}
}
Here is the AudioRecorder class function, which does the recording:
fun record(listenTimeMillis: Long) {
val byteString: ByteString = ByteString.EMPTY
mAudioRecorder = initAudioRecorder()
val mBuffer = ByteArray(4 * AudioRecord.getMinBufferSize(SAMPLE_RATE_HZ, CHANNEL, ENCODING))
mAudioRecorder!!.startRecording()
Thread {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
Thread.sleep(listenTimeMillis)
val read = mAudioRecorder!!.read(mBuffer, 0, mBuffer.size, AudioRecord.READ_NON_BLOCKING)
val intent = Intent(RECORDING_COMPLETED_INTENT)
try {
if (read > 0) {
intent.putExtra(RECORDING_AUDIO, mBuffer)
intent.putExtra(RECORDING_SUCCESS, true)
}
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
} catch (e: Exception) {
Log.e(TAG, e.stackTrace.toString())
}
releaseAudioRecorder()
}.start()
}
I solved this. The thing to blame was a too small buffer size. So the recognition server was actually getting half a second of audio record which it obviously couldn't recognize.
val mBuffer = ByteArray(4 * AudioRecord.getMinBufferSize(SAMPLE_RATE_HZ, CHANNEL, ENCODING))
instead of 4 I put 200 and instead of AudioRecord.READ_NON_BLOCKING I have put AudioRecord.READ_BLOCKING and I read the buffer in a loop and increase the offset in each iteration. Then it started working.
val startTime = System.currentTimeMillis()
var deltaTime = 0L
var offset = 0
val intent = Intent(RECORDING_COMPLETED_INTENT)
val readChunk = 512
while (deltaTime < listenTimeMillis && offset < mBuffer.size) {
val read = mAudioRecord!!.read(mBuffer, offset, readChunk, AudioRecord.READ_BLOCKING)
if (read < 0) {
intent.putExtra(RECORDING_SUCCESS, false)
break; //if read with error, end here
}
deltaTime = System.currentTimeMillis() - startTime //startTime is a while loop breaking condition so it lestens only for specified amount of time
offset += readChunk
}
What I am doing: I am using a connection to download file from server and write into a storage
What is happening: Code is working
What I am trying to do: How to achieve the same using okhttp
try {
val url = URL(DICTIONARY_FILE_URL)
val conection = url.openConnection()
conection.connect()
// getting file length
// input stream to read file - with 8k buffer
val input = BufferedInputStream(url.openStream(), 8192)
// Output stream to write file
val directoryPathName = Environment.getExternalStorageDirectory().absolutePath.plus("/CNX/dictionary/")
val dictionaryFileName = "DictionaryEN.quickdic"
var f = File(directoryPathName)
if(!f.isDirectory) {
f.mkdirs()
}
val output = FileOutputStream(directoryPathName.plus(dictionaryFileName))
val data = ByteArray(1024)
var count: Int? = 0
while ({ count = input.read(data);count }() != -1) {
output.write(data, 0, count!!)
}
// flushing output
output.flush()
// closing streams
output.close()
input.close()
isJobSuccess = true
//sharedPreferences[IS_DICTIONARY_DOWNLOADED] = true
} catch (e: Exception) {
Log.e("Exception",e.message)
isJobSuccess = false
//sharedPreferences[IS_DICTIONARY_DOWNLOADED] = false
}
Sample for Downloading a file with OkHttp asynchronously
fun downloadFileAsync(downloadUrl: String) {
val client = OkHttpClient();
val request = Request.Builder().url(downloadUrl).build();
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.e("fail", e.printStackTrace().toString())
}
override fun onResponse(call: Call, response: Response) {
if (!response.isSuccessful) {
Log.e("fail", "Download error")
}else{
// Output stream to write file
val directoryPathName = Environment.getExternalStorageDirectory().absolutePath.plus("/CNX/dictionary/")
val dictionaryFileName = "DictionaryEN.quickdic"
val f = File(directoryPathName)
if(!f.isDirectory) {
f.mkdirs()
}
val output = FileOutputStream(directoryPathName.plus(dictionaryFileName))
response.body?.let {
output.write(it.bytes())
}
// flushing output
output.flush()
// closing streams
output.close()
}
}
})
}
Sample for Downloading a file with OkHttp synchronously
fun downloadFileSync(downloadUrl: String) {
val client = OkHttpClient();
val request = Request.Builder().url(downloadUrl).build();
val response = client.newCall (request).execute();
if (!response.isSuccessful) {
Log.e("fail", "Failed to download file: " + response)
}else {
val directoryPathName = Environment.getExternalStorageDirectory().absolutePath.plus("/CNX/dictionary/")
val dictionaryFileName = "DictionaryEN.quickdic"
val f = File(directoryPathName)
if (!f.isDirectory) {
f.mkdirs()
}
val output = FileOutputStream(directoryPathName.plus(dictionaryFileName))
response.body?.let {
output.write(it.bytes())
}
// flushing output
output.flush()
// closing streams
output.close()
}
}
build.gradle
implementation("com.squareup.okhttp3:okhttp:4.5.0")