I am building an app the needs to go through a collection of photos stored locally, which I import to a room database, and try to detect for each if it contains faces or not. I've got everything sorted, my only issue is how to run this operation, which could take a while, in a service that wouldn't stuff up the UI thread.
At first I wanted to use a JobIntentService
but couldn't because I was unable to observeForever on a background thread, and couldn't use a simple observer because I have no lifecycleOwner to give to the Observer
.
I've ended up using just a service, as soon as the operation starts my UI is pretty much stuck and if I try to do anything the app crashes.
I tried maybe IntentService
but I can't use the observer in onHandleIntent
because it's a worker thread and it doesn't let me, and when I run the operations under onStartCommand
then it's just the same thing.
I feel like I am stuck with the architecture of this thing, I'd appreciate any ideas. Thank you.
This is my service:
class DetectJobIntentService : Service() {
private val TAG = "DetectJobIntentServi22"
lateinit var repo: PhotoRepository
lateinit var observer : Observer<MutableList<Photo>>
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val options = FirebaseVisionFaceDetectorOptions.Builder()
.setClassificationMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
.setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
.setMinFaceSize(0.15f)
.build()
val detector = FirebaseVision.getInstance()
.getVisionFaceDetector(options)
repo = PhotoRepository(application)
observer = Observer {
for (file in it) {
val image = FirebaseVisionImage.fromFilePath(application, Uri.parse(file.uri))
AsyncTask.execute {
detector.detectInImage(image).addOnSuccessListener { list ->
if (list.isNotEmpty()) {
file.hasFaces = 1
repo.update(file)
} else {
file.hasFaces = 2
repo.update(file)
}
}
}
}
}
repo.getAllPhotos().observeForever(observer)
val notificationIntent= Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0)
val notification = NotificationCompat.Builder(this, getString(tech.levanter.anyvision.R.string.channel_id))
.setContentTitle("Detecting faces..")
.setContentText("64 photos detected")
.setSmallIcon(tech.levanter.anyvision.R.drawable.ic_face)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
repo.getAllPhotos().removeObserver(observer)
}
}
Seeing your code is in Kotlin, I'll advice you try out Kotlin Coroutines. This would enable you dispatch expensive operations i.e. querying databases, making network requests/calls off to other threads thereby not blocking the UIThread
. Coroutines
help you avoid the hassle of callbacks
. Also, Google just deprecated the AsyncTask
API in favour of Coroutines
as the way to go for multi-threading purposes.