I'm writing an app for searching memes on android smartphone. I've already got code for fetching all images from storage and displaying them on screen and it works just fine. Now i'm trying to process every fetched image with google ML text recognition and add that text to image objects, which are stored in arraylist (i save that list in sharedpreferences every time a new image is processed). Generally, my code works, but somehow, when there is a lot of images to process, application crashes. I can see literally nothing from logs, there is no error. I had concurrent modification error once but when i added newList, it doesn't show anymore.
However, i can start the app again and it continues loading images until the next crash. Seems like crash happenes after several hundred images processed.
Here is the whole function for fetching and processing images from device storage (sorry for messy code, i'm not a professional yet):
//Function for fetching all the data and creating Image objects, which are returned in a list.
//I will pass them to the main activity and show on the screen
@SuppressLint("SetTextI18n")
@RequiresApi(Build.VERSION_CODES.Q)
private fun queryImageStorage() {
var imageNumber = 0
var percentageloaded = 0
val list = readListFromPref(this, R.string.preference_file_key.toString()).toList()
Log.d(TAG, "queryImageStorage: ROZMIAR ${list.size}")
val newList = readListFromPref(this, R.string.preference_file_key.toString())
val imageProjection = arrayOf(
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media._ID,
)
val imageSortOrder = "${MediaStore.Images.Media.DATE_TAKEN} DESC"
val cursor = contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
imageProjection,
null,
null,
imageSortOrder
)
cursor.use { it ->
val imagesAmount = cursor?.count
//Log.d(TAG, "queryImageStorage: $x")
it?.let { it ->
val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val nameColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
val sizeColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)
val dateColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN)
while (it.moveToNext()) {
val id = it.getLong(idColumn)
val name = it.getString(nameColumn)
val size = it.getString(sizeColumn)
val date = it.getString(dateColumn)
val contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
).toString()
// make image object and add to list if it's not there already.
//TODO: Dodac skanowanie obrazów z unused stuff
if (!list.any { Image -> Image.id == id }) {
//process the image
val inputImage =
contentUri.let { InputImage.fromFilePath(this, it.toUri()) }
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
val result = recognizer.process(inputImage)
result.addOnSuccessListener { visionText ->
// Task completed successfully
if (visionText.toString().isNotEmpty()){
val image = Image(id, name, size, date, contentUri, visionText.text)
newList.add(image)
Log.d(TAG, "queryImageStorage: SUCCESS ${visionText.text}")
Log.d(TAG, "queryImageStorage: NEW LIST $newList")
writeListToPref(this, newList, R.string.preference_file_key.toString())
}
}
result.addOnFailureListener { e ->
Log.d(TAG, "queryImageStorage: FAILURE $e")
// Task failed with an exception
}
}
imageNumber += 1
//Updating textview with percentage
if (imagesAmount != null) {
percentageloaded = (imageNumber *100/ imagesAmount)
//Log.d(TAG, "queryImageStorage: PROCENTY $imageNumber $imagesAmount")
}
Handler(Looper.getMainLooper()).post(Runnable {
loadingTextView.text = "Loading images: $percentageloaded %"
})
try {
Thread.sleep(0,1)
} catch (e: InterruptedException) {
e.printStackTrace()
}
//Log.d(TAG, "queryImageStorage: $imageNumber")
//TODO: Działa sharedpreferences. Przy 1 uruchomienu laduje wszystko, przy kolejnych tylko nowe zdjecia. Do zrobienia Listener zeby działało płynnie.
}
}
} ?: kotlin.run {
Log.e("TAG", "Cursor is null!")
}
val intent = Intent(applicationContext, MainActivity::class.java)
startActivity(intent)
finish()
}
When i delete code responsible for processing image and create image object without text, app works fine.
Image data class:
data class Image(val id: Long, val name: String?, val size: String?, val date: String?, val uri: String?, val text: String? = null) {
}
Thank you for all tips and help. After all, i could just automatically restart my app when it crashes but i don't think that would be a good solution :)
Edit: Actually, the app doesn't crash, it just freezes for a second and it's minimalizing. When i open it again, app continues loading pictures. May it be some kind of memory issue?
The call val result = recognizer.process(inputImage)
is an async call. If you put too much images into the queue, it may cause memory issue.
You could start a worker thread and wait for the result in the worker thread before sending a new image. Use Tasks.await(task)