JobIntentService job ID globally unique or scoped by the class? - android

Documentation implies it's scoped per the service class:
A unique job ID for scheduling; must be the same value for all work
enqueued for the same class.
However, I have two services with their JOB_ID, and enqueWork() as:
companion object {
private const val JOB_ID = 1
fun enqueueWork(context: Context, action: String) {
enqueueWork(context, Svc1::class.java, JOB_ID, Intent(action))
}
}
and
companion object {
private const val JOB_ID = 1
fun enqueueWork(context: Context, action: String) {
enqueueWork(context, Svc2::class.java, JOB_ID, Intent(action))
}
}
when I start both the services at boot (probably around the same time) Svc2 runs 2x. Changing Svc2's JOB_ID to 2 solves the problem.
If it's NOT scoped by the service class, that's awful as it means every service needs to be aware of the implementation of every other service in the same app.
?

Related

How to start a startForegroundService using WorkManager

Hi due to issues with startForegroundService not able to run in the background on certain android version. I am trying to startForegroundService using a WorkManager but unable to get it to work. this is what I tried so far.
Forground service that needs to start
ContextCompat.startForegroundService(context, CarInfoProcessingService.createIntent(context = applicationContext,
pushMsgData = message.data))
class that starts the foreground service
class BackupWorker(private val context: Context, workerParams: WorkerParameters,private val message: RemoteMessage) :
Worker(context, workerParams) {
override fun doWork(): Result {
//call methods to perform background task
ContextCompat.startForegroundService(context, CarInfoProcessingService.createIntent(context = applicationContext,
pushMsgData = message.data))
return Result.success()
}
companion object {
private const val TAG = "BackupWorker"
}
}
how I am starting the workManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val request =
OneTimeWorkRequest.Builder(BackupWorker::class.java).addTag("BACKUP_WORKER_TAG")
.build()
WorkManager.getInstance(context).enqueue(request)
}
Issues I have
The above code does not trigger CarInfoProcessingService.
Another question I had was for class BackupWorker I have a parameter message but I am never passing that any where,
Thank you for your help in advance
R

Android JobIntentService as wrapper on another service

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?
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!

How to check if an Android Service is already Running

This is a similar question to How to check if a service is running on Android? but since the question is old and the answers provided there are deprecated or not working properly. Thus the separate question.
I have an implementation, that fires a Service on Boot Complete, but I also want to start the service in onCreate of MainActivity, in case the service was not started before.
here are what I have tried:
1. Fetch Static Boolean to get the state of the Service as demonstrated below.
MyService.kt
class MyService : Service() {
override fun onCreate() {
super.onCreate()
isServiceStarted = true
}
override fun onDestroy() {
super.onDestroy()
isServiceStarted = false
}
companion object {
var isServiceStarted = false
}
}
MainActivity.kt
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val serviceStarted = MyService.isServiceStarted
if (!serviceStarted) {
val startMyService = Intent(this, MyService::class.java)
ContextCompat.startForegroundService(this, startMyService)
}
}
}
but I soon discovered that onDestroy is not always called when a Service is destroyed, thereby leaving my static boolean variable (isServiceStarted) to be true, when in reality it has been destroyed.
2.A function to check
fun isMyServiceRunning(serviceClass : Class<*> ) : Boolean{
var manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (service in manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.name.equals(service.service.className)) {
return true
}
}
return false
}
The Call
isMyServiceRunning(MyService::class.java)
Problems with this approach include:
- getRunningServices is deprecated since Android O (API 27),
- It is resource consuming and inefficient to loop through running services like that and because the docs say:
Note: this method is only intended for debugging or implementing service management type user interfaces.
It's not meant for control flow!
What is an Elegant/Efficient way to check if a Service is already running?
If service is in the application process just use the static field inside service (companion object) or bind to service.
If the service runs in remote process use Messanger if you want to have a synchronized communication or AIDL when you want to care about threads.

PublishSubject blockingLast() hangs Android app and not invoke

I have a problem with getting the last emitted value from the Subject
This is my class which is responsible for emitting and observing the battery changes:
class BatteryLevelProvider #Inject constructor(
app: App
) {
private val context: Context = app
private val receiver: PowerConnectionReceiver = PowerConnectionReceiver()
init {
initializeReceiver()
}
private fun initializeReceiver() {
IntentFilter(Intent.ACTION_BATTERY_CHANGED).let { intentFilter ->
context.registerReceiver(receiver, intentFilter)
}
}
companion object {
val batteryLevelSubject = PublishSubject.create<Int>()
}
fun observeBatteryLevel(): Observable<Int> = batteryLevelSubject.distinctUntilChanged()
fun getCurrentBatteryLevel(): Int {
Timber.d("getCurrentBatteryLevel: ENTERED")
val blockingLast = batteryLevelSubject.blockingLast(0)
Timber.d("getCurrentBatteryLevel: $blockingLast")
return blockingLast
}
inner class PowerConnectionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val level: Int = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val scale: Int = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
val percentage= (level / scale.toFloat() * 100).toInt()
batteryLevelSubject.onNext(percentage)
Timber.d("Battery changed: $percentage")
}
}
}
When i invoke the
getCurrentBatteryLevel()
It reach the blockingLast never get return the value and hangs the app.
What is the reason and how to handle this properly?
subject.blockingLast(0) means the following: get the last value after the stream has completed emitting values and if it has completed without emitting anything then return the default value.
That means that blockingLast will wait until it receives onComplete event because only then it can figure out that the stream has ended (and emit last value). PublishSubject creates an infinite stream and you never call batteryLevelSubject.onComplete to end the stream manually and that's why it hangs forever.
You can easily fix that by changing PublishSubject to BehaviorSubject. The main difference between them is that BehaviorSubject caches the last received value which can then be received by anyone. Also, you need to change batteryLevelSubject.blockingLast(0) to batteryLevelSubject.value to get the last cached value (and it won't block anything!). But be aware that the value may be null at the first run when you haven't put there anything yet. You can easily fix that by creating BehaviorSubject with default value like so:
val subject = BehaviorSubject.createDefault(0)

Android O background execution limits not fully applied with bounded and started service

My sample app uses targetSdkVersion 26.
I have a simple service, which is both started and bounded with the following code snippet:
val intent = BoundServiceTest.buildIntent(applicationContext)
applicationContext.startService(intent)
applicationContext.bindService(intent, serviceConnection, BIND_AUTO_CREATE)
Please be aware that I use a global application Context for binding, not an Activity Context.
The service itself does only imlements some basic logging:
class BoundServiceTest : Service() {
companion object {
private val TAG = "BoundServiceTest"
fun buildIntent(context: Context): Intent {
return Intent(context, BoundServiceTest::class.java)
}
}
private val binder = Binder()
override fun onBind(p0: Intent?): IBinder {
Log.d(TAG, "onBind")
return binder
}
override fun onUnbind(intent: Intent?): Boolean {
Log.d(TAG, "onUnbind")
return super.onUnbind(intent)
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand: startId = " + startId)
return START_STICKY
}
override fun onDestroy() {
Log.d(TAG, "onDestroy")
super.onDestroy()
}
}
Basically I am not sure, if Android O applies the Background execution limits or not, since basically the documentation states (Background Service Limitations):
Bound services are not affected
These rules do not affect bound services in any way. If your app
defines a bound service, other components can bind to that service
whether or not your app is in the foreground.
But it seems that the OS isn't quite sure as well regarding the Logcat:
de.continental.android.androidoservicetest D/BoundServiceTest: onCreate
de.continental.android.androidoservicetest D/BoundServiceTest: onStartCommand: startId = 1
de.continental.android.androidoservicetest D/BoundServiceTest: onBind
? W/ActivityManager: Stopping service due to app idle: u0a141 -1m9s733ms de.continental.android.androidoservicetest/.BoundServiceTest
I/RunningState: Unknown non-service process: de.continental.android.androidoservicetest #14943
I/chatty: uid=1000(system) RunningState:Ba identical 1 line
I/RunningState: Unknown non-service process: de.continental.android.androidoservicetest #14943
The ActivityManager log message indicates that the service shall be stopped (regardless that it is a bound one), but the OS doesn't stop my service: The log messages for calling onDestroy method are not displayed, compared to a simple started Service without binding.
How is this scenario (started and bound service) handled in Android O? Or do I encounter a bug?

Categories

Resources