I'm trying to use Kotlin Coroutines for better performance.
But I'm not sure is it the correct way to use it, so I'd like to have experts review.
After taking a photo with camera, the screen is the blackout for half second while the image processing I guess.
The original code was,
fun uploadPhoto(data: Intent): Observable<Response> {
val bitmap = data.extras.get("data") as Bitmap
val bytes = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bytes)
val baseDir = Environment.getExternalStorageDirectory()
val file = File(baseDir, Calendar.getInstance().timeInMillis.toString() + ".jpg")
val fileOutputStream = FileOutputStream(file)
fileOutputStream.write(bytes.toByteArray())
fileOutputStream.close()
return uploadMedia(file)
}
and after I read this tutorial, https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html
I changed it to,
fun uploadPhoto(data: Intent): Observable<Response> {
val baseDir = Environment.getExternalStorageDirectory()
val file = File(baseDir, Calendar.getInstance().timeInMillis.toString() + ".jpg")
launch {
val bitmap = data.extras.get("data") as Bitmap
val bytes = compressBitMap(bitmap).await()
val fileOutputStream = FileOutputStream(file)
fileOutputStream.write(bytes.toByteArray())
fileOutputStream.close()
}
return uploadMedia(file)
}
private fun compressBitMap(bitmap: Bitmap) = async(CommonPool) {
val bytes = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bytes)
return#async bytes
}
But I don't see any difference.
What I want to do thing is, I want to run Compressing Bitmap and fileOutputStream jobs in Background to prevent bothering MainThread.
Does it make the better performance?
Coroutines are executed in "CoroutineContexts", you appear to be using an older version of the libs, in newer versions you should always specify the context by using launch(UI) or launch(CommonPool). I don't know by default which context is your "launch" using, but I guess is UI.
If that's correct, you are in deed waiting in the UI thread for the compressBitMap completion, blocking the UI thread (A pointless coroutine use)
Try to use launch(CommonPool) instead and see if the magic happens.
Related
I get the following error when trying to iterate over the uploadTasks inside an addOnSuccessListener method.
java.lang.ClassCastException: com.google.firebase.storage.UploadTask$TaskSnapshot cannot be cast to com.google.firebase.storage.UploadTask
So how can i get the Download String of each Img inside addOnSuccessListener?
val baos = ByteArrayOutputStream()
val tasks = mutableListOf<UploadTask>()
listImg.forEach {
if(bitmap!!.byteCount != it.byteCount) {
val bitmap = it
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val data = baos.toByteArray()
var uploadTask = spaceRef.putBytes(data)
tasks.add(uploadTask)
}
}
Tasks.whenAllSuccess<UploadTask>(tasks).addOnSuccessListener { uploadTasks ->
//uploadTasks has size of 2
val urls = mutableListOf<Uri>()
lifecycleScope.launch
{
//Error throws here
uploadTasks.forEach{
urls.add(it.await().storage.downloadUrl.await())
}
}
}
The type of whenAllSuccess is <TResult>, so you should use the result type of UploadTask (UploadTask.TaskSnapshot) instead:
Tasks.whenAllSuccess<UploadTask.TaskSnapshot>(tasks).addOnSuccessListener { uploadTasks ->
And then you can drop the the first await() on that last line:
urls.add(it.storage.downloadUrl.await())
Bonus: Don't block the main thread
Note that Tasks.whenAllSuccess() will block the main thread until all uploads succeed, meaning your UI might freeze while uploading files.
To avoid that, consider uploading your files with Coroutines:
val baos = ByteArrayOutputStream()
val urls = mutableListOf<Uri>()
lifecycleScope.launch {
listImg.forEach {
if(bitmap!!.byteCount != it.byteCount) {
val bitmap = it
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val data = baos.toByteArray()
// Upload the image first
val taskSnapshot = spaceRef.putBytes(data).await()
// Get the download Url
val downloadUri = taskSnapshot.storage.downloadUrl.await()
// Add it to the list of Uris
urls.add(downloadUri)
}
}
}
In my Service, I need to call on onStartCommand some methods that require withContext(Dispatchers.IO) instead CoroutineScope(Dispatchers.IO) like:
url = URL(pokemon.linkImage)
url.openConnection().getInputStream()
fOut= FileOutputStream(file)
fOut.flush()
fOut.close()
But Suspend function 'withContext' should be called only from a coroutine or another suspend function. So if onStartCommand can't be a suspend function because it has override and withContext can't be called by CoroutineScope because Inappropriate blocking method call of methods
val url = URL(pokemon.linkImage)
val iS = url.openConnection().getInputStream()
How can I resolve this problem?
My onStartCommand():
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
//create the directory on internal storage that will contains all pokemon images
val path = applicationContext.filesDir.absolutePath
val dirName = "/pokeimg"
val dir = File(path, dirName)
if(!dir.exists())
dir.mkdir()
CoroutineScope(Dispatchers.IO).launch {
//get the list of pokemon when they are loaded from repository
val listOfPokemon =
PersistenceSingletonRepository.getInstance(applicationContext).getListOfPokemon()
//download all the image for each pokemon in the list, but only if the image is
//not present in the directory
for(pokemon in listOfPokemon) {
val imageName = "${pokemon.name}.png"
val file = File("${dir.absolutePath}/$imageName")
//check if image not exists on internal storage and if is not an official-artwork
//images that aren't official-artwork has less quality so don't resize
if(!file.exists()) {
//download img
val url = URL(pokemon.linkImage)
val iS = url.openConnection().getInputStream()
val opts = BitmapFactory.Options()
if (!Utils.isBadImage(pokemon.linkImage)) {
//request a smaller image
opts.inSampleSize = 2
}
val bitmap = BitmapFactory.decodeStream(iS, null, opts)
val fOut= FileOutputStream(file)
bitmap?.compress(Bitmap.CompressFormat.PNG, 100, fOut)
fOut.flush()
fOut.close()
}
}
stopSelf()
}
return START_NOT_STICKY
}
You can safely ignore that warning, it's known to have many false positives. You launched the coroutine in the IO dispatcher, which is designed for blocking calls.
On the other hand, launching anything with an ad-hoc CoroutineScope that has no parent and no lifecycle binding, is usually the wrong thing to do. You should respect the Service lifecycle and cancel your coroutine in onDestroy. See for example here.
I was working on Drawing App Project and I made it. But at last, I need to save that image on the device. I googled it and I found examples with AsyncTask which is now deprecated. So can anyone help me with how can I do with Coroutines in Kotlin? As I got to know Coroutine is an Alternative to AsyncTask.
I mean alternative for this example.
private inner class BitmapAsyncTask(val mBitmap: Bitmap): AsyncTask<Any, Void,String>(){
override fun doInBackground(vararg p0: Any?): String {
var result = ""
if(mBitmap != null){
try{
val bytes = ByteArrayOutputStream()
mBitmap.compress(Bitmap.CompressFormat.PNG, 90, bytes)
val f = File(externalCacheDir!!.absoluteFile.toString()
+ File.separator + "KidsDrawingApp_"
+ System.currentTimeMillis() / 1000 + ".png")
val fos = FileOutputStream(f)
fos.write(bytes.toByteArray())
fos.close()
result = f.absolutePath
}catch(e: Exception){
result = ""
e.printStackTrace()
}
}
return result
}
}
May this answer helps you to do what you want to save the image on storage.
I use the following function to write a long string (byte array size is 871504) to the internal storage of an Android device, but it takes around one and a half minute to complete. However, for another string (byte array size is 782979), it just takes a few seconds to complete.
fun saveTempSrc(data: String, ctx: Context) {
try {
val dataByteArray = data.toByteArray()
Timber.d("saveTempSrc: byte array size = %d", dataByteArray.size)
val inputStream = BufferedInputStream(ByteArrayInputStream(dataByteArray))
val outputStream = BufferedOutputStream(ctx.openFileOutput("example.txt", Context.MODE_PRIVATE))
inputStream.copyTo(outputStream)
inputStream.close()
outputStream.flush()
outputStream.close()
} catch (e: IOException) {
Timber.e(e, "Write Temp file failed")
}
}
Is this function suitable for writing long string to file? I run this function in RxJava's I/O scheduler.
Try to use FileWriter.
val sdcard = ctx.getExternalStorageDirectory()
val file = File("example.txt", sdcard)
val fileWriter = FileWriter(file)
fileWriter.write(data)
fileWriter.flush()
fileWriter.close()
Sorry that the long waiting time is not due to file I/O but due to Regex find with a very long string before writing file.
I have written a c# web service that returns a pdf in a stream of bytes as response. Once I make a call to the web-service from my android app, I will store the response in an array byte till here I will be able to do it. But after that I need to convert that byte array into pdf, I should be able to display that. I have a menu page in which once the button is pressed the call is made to the web service with file name and on click of button I should be able to open pdf. Is this possible? Or there is some other, better solution? I checked on the net for better understanding, but I was unable to find one that could help me understand better.
Thanks for the suggestion, but I don't have the pdf in hand, I just have the array bytes, which I got from the web service. So I now need to regenerate the pdf from this array of bytes and display it, but I am not getting how to do it.
Try following these steps
Convert byte array to InputStream
val inputStream = ByteArrayInputStream(byteArray)
Save InputStream as PDF file:
suspend fun saveInputStreamAsPdfFile(inputStream: InputStream, applicationContext: Context): File? {
var outputFile: File? = null
withContext(Dispatchers.IO) {
try {
val directory = ContextCompat.getExternalFilesDirs(applicationContext, "documents").first()
val outputDir = File(directory, "outputPath")
outputFile = File(outputDir, UUID.randomUUID().toString() + ".pdf")
if (!outputDir.exists()) {
outputDir.mkdirs()
}
val outputStream = FileOutputStream(outputFile, false)
inputStream.use { fileOut -> fileOut.copyTo(outputStream) }
outputStream.close()
} catch (e: Exception) {
// Something went wrong
}
}
return outputFile
}
Show PDF with PdfRenderer
var totalPdfPages: Int
fun showPdf(pdfFile: File) {
val input = ParcelFileDescriptor.open(pdfFile, MODE_READ_ONLY)
val renderer = PdfRenderer(input)
val wrapper = PdfRendererWrapper(renderer)
totalPdfPages = wrapper.getTotalPages()
showPdfPage(0)
}
fun showPdfPage(currentPageIndex: Int) {
val pageBitmap = wrapper.getBitmap(currentPageIndex)
imageView.setImageBitmap(pageBitmap) // Show current page
}