0

This one will require a bit of context, please bare with me...


I have migrated a dependency for evaluating NFC data to a new application. Whenever an NFC tag is discovered, my application will spawn an Activity to handle the event. In the onCreate function of this NfcActivity, a background service (let's call it MyNfcHelperService) is started to retrieve some data on the scanned tag:

class NfcActivity : AppCompatActivity() {
    /*...*/
    
    override fun onCreate(savedInstanceState: Bundle?) {
        /*...*/

        val intent = Intent(this, MyNfcHelperService::class.java)
            .putExtra(/*...*/)
        
        startService(intent)
    }
}

The work produced by this service is later retrieved and used by the NfcActivity. It all used to work just fine, but once released into the wild we noticed some crashes, which would report Not allowed to start service Intent on the startService(intent) call.

I quickly came across this related post, suggesting this is due to some improvements in RAM management on background processes (introduced in Android 8).

Following the accepted answer and comments raised, I studied the docs on JobIntentServices and ended up with a similar setup. I would've liked to drop the MyNfcHelperService all together and move its logic into the MyJobIntentService. But what happens inside MyNfcHelperService is an absolute black-box to me. Thus, I wrapped the aforementioned service inside the onHandleWork of my derived JobIntentService like so:

class MyJobIntentService: JobIntentService() {

    companion object {
        private const val JOB_ID = 1000

        fun start(context: Context) {
            val intentPkm = Intent(context, MyNfcHelperService::class.java)
                .putExtra(/*...*/)
            enqueueWork(context, intentPkm)
        }

        private fun enqueueWork(context: Context, intent: Intent) {
            enqueueWork(context, MyJobIntentService::class.java, JOB_ID, intent)
        }
    }

    override fun onHandleWork(intent: Intent) {
        applicationContext.startService(intent)
    }
}

Then I applied this class in NfcActivity:

class NfcActivity : AppCompatActivity() {
    /*...*/
    
    override fun onCreate(savedInstanceState: Bundle?) {
        /*...*/

        MyJobIntentService.start(applicationContext)
    }
}

Thus far, the code seems to work. But I am hesitant to release it into the wild, because it feels a bit hacky and I am unsure if this solution actually solved the aforementioned issue. After all, I understand that this infrastructure creates a background service from a scheduled job.


So, is my code robust towards the java.lang.IllegalStateException: Not allowed to start service Intent error, or did I totally head the wrong way? If the latter is the case, can anyone suggest an alternate approach, taking into account that I cannot access the guts of MyNfcHelperService?

Basti Vagabond
  • 1,458
  • 1
  • 18
  • 26
  • Why Jobintentservice? Use work manager – M D Jul 02 '20 at 14:58
  • @MD interesting, [Work Manager](https://developer.android.com/topic/libraries/architecture/workmanager) seems to use JobScheduler for API 23+ under the hood as well. So I guess my question remains, can I use this approach, while maintaining the ```MyNfcHelperService```? – Basti Vagabond Jul 02 '20 at 15:04
  • 1
    I guess WorkManager is more stable now days. Your approach is right but it will not work in some manufacturer devices like I saw this type of crashs in Honor (Huwai) devices – M D Jul 02 '20 at 15:08
  • 1
    Alright, thanks. I'll look into it and update my post as new insights come along. – Basti Vagabond Jul 03 '20 at 12:13

1 Answers1

0

After @MD's comment, I changed my approach towards using WorkManager. This should be most robust against different API versions. I followed the official docs and arrived at the following worker setup:

class MyNfcHelperServiceWorker(val context: Context, workerParams: WorkerParameters): Worker(context, workerParams) {
    override fun doWork(): Result {
        val intent = Intent(context, MyNfcHelperService::class.java)
                .putExtra(/*...*/)

        context.startService(intent)

        return Result.success()
    }
}

Then I adjusted the code inside NfcActivity like so:

class NfcActivity : AppCompatActivity() {
    /*...*/
    
    override fun onCreate(savedInstanceState: Bundle?) {
        /*...*/

        OneTimeWorkRequestBuilder<MyNfcHelperServiceWorker>()
            .build()
            .also { helperServiceWorkRequest ->
                WorkManager.getInstance(this)
                    .enqueue(helperServiceWorkRequest)
            }
    }
}

Initial tests have worked just fine. My understanding of why this fixes the java.lang.IllegalStateException: Not allowed to start service Intent issue would be that android will now schedule a work request which meets the requirements of launching MyNfcHelperService only when my app is allowed to create background processes.

That said, I still have a bit of a headache using a worker to start a service. Feels really redundant to do so and I am unsure of any additional implications this may lead. Thus, I wont accept this as an answer just now.

I'd really appreciate any additional comments and/or answers on the matter!

Basti Vagabond
  • 1,458
  • 1
  • 18
  • 26