JobService does not repeat - android

In Android Oreo, I want to create a service that periodically updates data from the network.
class NetworkJobService : JobService() {
override fun onStopJob(p0: JobParameters?): Boolean {
jobFinished(p0,true)
return true
}
override fun onStartJob(p0: JobParameters?): Boolean {
//Average working time 3 to 5 minutes
NetworkConnect.connect()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally {
jobFinished(p0,true)
}
.subscribe({result->
// Writes the parameters to the cache with the current time.
Cache.write("result : $result")
},{e->
// Writes the parameters to the cache with the current time.
Cache.write(e)
})
return true
}
}
This service is registered in the schedule when you run MainActivity.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
jobSchedule()
button.setOnClickListener { readLog() }
}
val interval = 1000 * 60 * 15L
private fun jobSchedule(){
val jobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val jobInfo = JobInfo.Builder(3,
ComponentName(this, NetworkJobService::class.java))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPeriodic(interval)
.build()
jobScheduler.schedule(jobInfo)
}
private fun readLog(){
//The log file is read line by line.
Cache.read()
.reversed()
.toObservable()
.subscribe({ text->
Log.i("Service Log",text)
},{
})
}
}
However, when I read the log file and checked the results, the service was running only when the MainActivity was running. In other words, it was not being rescheduled.
1) Run the activity and just turn off the device's screen
2) Run the activity and press the Home button to return to the launcher.
3) When the service is terminated and the app is deleted in the multitasking window
The most I wanted was to work in case 3), but in any of the above, the services I wanted were not rescheduled.
What have I missed?

While working with background threads in Oreo when the app is in killed you need to start the services as foreground service. Here is the details of the same. Essentially, show a notification to make the user aware that your app is trying to doing something in the background.
Hope it helps you.

Related

Collect Flows in Service

So, I'm trying to collect data from flows in my Foreground service (LifecycleService) in onCreate(), but after the first callback, it is not giving new data.
The code is :
override fun onCreate() {
super.onCreate()
lifecycleScope.launchWhenStarted {
repeatOnLifecycle(Lifecycle.State.STARTED) {
observeCoinsPrices()
}
}
}
I couldn't get lifecycleScope.launch to work in the LifecycleService.onCreate method without it freezing the app, so what I did instead was moved the collector into a method that I use to start the service, assign the Job into a property so I can cancel it when the Service is destroyed.
import kotlinx.coroutines.Job
//...
class MyService : LifecycleService() {
//...
private lateinit var myJob: Job
// my custom method for starting The Foreground service
fun startTheService() {
// call startForeground()
//...
myJob = lifecycleScope.launch {
collectFromFlow()
}
}
override fun onDestroy() {
myJob.cancel()
}
}
In my case, I was wanting to update text in the foreground notification every time a value was emitted to my Flow collector.
Because Flow used in observeCoinsPrices() not replay the latest value (replay < 1). You should change Flow logic

Periodic work requests with WorkManager not working

I am using Workmanager to execute a task within a time period of minutes but it gets executed for the first time only. From my point of view it should execute every minutes.
I am testing on device while the app is in foreground running and power is on.
Code:
class MainActivity : AppCompatActivity() {
val TAG: String = "MainActivity"
lateinit var workLiveData: LiveData<List<WorkInfo>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initWM()
}
private fun initWM() {
val request = PeriodicWorkRequestBuilder<DemoWorker>(1, TimeUnit.MINUTES)
.addTag(TAG)
.build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(TAG,
ExistingPeriodicWorkPolicy.REPLACE, request)
}
}
DemoWorker:
class DemoWorker(
context: Context,
params: WorkerParameters
) : Worker(context, params) {
val TAG: String = "MainActivity"
override fun doWork(): Result {
Log.d(TAG, "doWork: ")
return try {
Result.success(workDataOf("KEY" to "SUCCESS"))
} catch (e: Exception) {
Result.failure()
}
}
}
A reminder about the “minimal interval”. WorkManager is balancing two different requirements: the application with its WorkRequest, and the Android operating system with its need to limit battery consumption. For this reason, even if all the constraints set on a WorkRequest are satisfied, your Work can still be run with some additional delay.
So you are replacing one work after another. The OS may not have the proper time to execute the work. So the best option will be to try with a 1-hour delay.
You can use a flexInterval.Let’s look at an example. Imagine you want to build a periodic Work request with a 30 minutes period. You can specify a flexInterval, smaller than this period, say a 15 minute flexInterval.
The actual code to build a PeriodicWorkPequest with this parameters is:
val logBuilder = PeriodicWorkRequestBuilder<MyWorker>(
30, TimeUnit.MINUTES,
15, TimeUnit.MINUTES)
The result is that our worker will be executed in the second half of the period (the flexInterval is always positioned at the end of the repetition period):

Android WorkManager observe progress

I'm using WorkManager for deferred work in my app.
The total work is divided into a number of chained workers, and I'm having trouble showing the workers' progress to the user (using progress bar).
I tried creating one tag and add it to the different workers, and inside the workers update the progress by that tag, but when I debug I always get progress is '0'.
Another thing I noticed is that the workManager's list of work infos is getting bigger each time I start the work (even if the workers finished their work).
Here is my code:
//inside view model
private val workManager = WorkManager.getInstance(appContext)
internal val progressWorkInfoItems: LiveData<List<WorkInfo>>
init
{
progressWorkInfoItems = workManager.getWorkInfosByTagLiveData(TAG_SAVING_PROGRESS)
}
companion object
{
const val TAG_SAVING_PROGRESS = "saving_progress_tag"
}
//inside a method
var workContinuation = workManager.beginWith(OneTimeWorkRequest.from(firstWorker::class.java))
val secondWorkRequest = OneTimeWorkRequestBuilder<SecondWorker>()
secondWorkRequest.addTag(TAG_SAVING_PROGRESS)
secondWorkRequest.setInputData(createData())
workContinuation = workContinuation.then(secondWorkRequest.build())
val thirdWorkRequest = OneTimeWorkRequestBuilder<ThirdWorker>()
thirdWorkRequest.addTag(TAG_SAVING_PROGRESS)
thirdWorkRequest.setInputData(createData())
workContinuation = workContinuation.then(thirdWorkRequest.build())
workContinuation.enqueue()
//inside the Activity
viewModel.progressWorkInfoItems.observe(this, observeProgress())
private fun observeProgress(): Observer<List<WorkInfo>>
{
return Observer { listOfWorkInfo ->
if (listOfWorkInfo.isNullOrEmpty()) { return#Observer }
listOfWorkInfo.forEach { workInfo ->
if (WorkInfo.State.RUNNING == workInfo.state)
{
val progress = workInfo.progress.getFloat(TAG_SAVING_PROGRESS, 0f)
progress_bar?.progress = progress
}
}
}
}
//inside the worker
override suspend fun doWork(): Result = withContext(Dispatchers.IO)
{
setProgress(workDataOf(TAG_SAVING_PROGRESS to 10f))
...
...
Result.success()
}
The setProgress method is to observe intermediate progress in a single Worker (as explained in the guide):
Progress information can only be observed and updated while the ListenableWorker is running.
For this reason, the progress information is available only till a Worker is active (e.g. it is not in a terminal state like SUCCEEDED, FAILED and CANCELLED). This WorkManager guide covers Worker's states.
My suggestion is to use the Worker's unique ID to identify which worker in your chain is not yet in a terminal state. You can use WorkRequest's getId method to retrieve its unique ID.
According to my analysis I have found that there might be two reasons why you always get 0
setProgress is set just before the Result.success() in the doWork() of the worker then it's lost and you never get that value in your listener. This could be because the state of the worker is now SUCCEEDED
the worker is completing its work in fraction of seconds
Lets take a look at the following code
class Worker1(context: Context, workerParameters: WorkerParameters) : Worker(context,workerParameters) {
override fun doWork(): Result {
setProgressAsync(Data.Builder().putInt("progress",10).build())
for (i in 1..5) {
SystemClock.sleep(1000)
}
setProgressAsync(Data.Builder().putInt("progress",50).build())
SystemClock.sleep(1000)
return Result.success()
}
}
In the above code
if you remove only the first sleep method then the listener only get the progres50
if you remove only the second sleep method then the listener only get the progress 10
If you remove both then the you get the default value 0
This analysis is based on the WorkManager version 2.4.0
Hence I found that the following way is better and always reliable to show the progress of various workers of your chain work.
I have two workers that needs to be run one after the other. If the first work is completed then 50% of the work is done and 100% would be done when the second work is completed.
Two workers
class Worker1(context: Context, workerParameters: WorkerParameters) : Worker(context,workerParameters) {
override fun doWork(): Result {
for (i in 1..5) {
Log.e("worker", "worker1----$i")
}
return Result.success(Data.Builder().putInt("progress",50).build())
}
}
class Worker2(context: Context, workerParameters: WorkerParameters) : Worker(context,workerParameters) {
override fun doWork(): Result {
for (i in 5..10) {
Log.e("worker", "worker1----$i")
}
return Result.success(Data.Builder().putInt("progress",100).build())
}
}
Inside the activity
workManager = WorkManager.getInstance(this)
workRequest1 = OneTimeWorkRequest.Builder(Worker1::class.java)
.addTag(TAG_SAVING_PROGRESS)
.build()
workRequest2 = OneTimeWorkRequest.Builder(Worker2::class.java)
.addTag(TAG_SAVING_PROGRESS)
.build()
findViewById<Button>(R.id.btn).setOnClickListener(View.OnClickListener { view ->
workManager?.
beginUniqueWork(TAG_SAVING_PROGRESS,ExistingWorkPolicy.REPLACE,workRequest1)
?.then(workRequest2)
?.enqueue()
})
progressBar = findViewById(R.id.progressBar)
workManager?.getWorkInfoByIdLiveData(workRequest1.id)
?.observe(this, Observer { workInfo: WorkInfo? ->
if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
val progress = workInfo.outputData
val value = progress.getInt("progress", 0)
progressBar?.progress = value
}
})
workManager?.getWorkInfoByIdLiveData(workRequest2.id)
?.observe(this, Observer { workInfo: WorkInfo? ->
if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
val progress = workInfo.outputData
val value = progress.getInt("progress", 0)
progressBar?.progress = value
}
})
The reason workManager's list of work infos is getting bigger each time the work is started even if the workers finished their work is because of
workManager.beginWith(OneTimeWorkRequest.from(firstWorker::class.java))
instead one need to use
workManager?.beginUniqueWork(TAG_SAVING_PROGRESS, ExistingWorkPolicy.REPLACE,OneTimeWorkRequest.from(firstWorker::class.java))
You can read more about it here

How do games work in the background on Android?

I am developing a game that has a certain number of moves that replenish every 10 seconds, only 250 moves. You may have seen this in many modern mobile games as energy units.
The method that counts down 10 seconds and adds moves is implemented in the "Service". But the fact is that after the death of the application, the service does not work for a long time. Or it is restarted by first calling "OnStartCommand", then immediately "onDestroy" and somehow works. In the event of such a restart, if you open an application where the "Service" is launched in "OnCreate", another "Service" is launched and works in parallel with another (this breaks the logic of the recovery of moves, because you can create a lot of "Service" in this way) ... And even in this case, the "Service" do not live long and die.
class MyService : Service() {
lateinit var pref: SharedPreferences
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i("MYTAG", "Сервис работает")
return START_STICKY
}
fun check() {
if (pref.getInt("servicestep", 250) < 250) {
reload()
} else stopSelf()
}
private fun reload() {
var progress = 0
val b = "com.example.driverclicker"
val intent = Intent(b)
object : CountDownTimer(10100, 1000) {
override fun onTick(millisUntilFinished: Long) {
Log.i("MYTAG", Thread.currentThread().name)
Log.i("MYTAG", "Таймер $millisUntilFinished")
progress += 1
intent.putExtra("step", progress)
sendBroadcast(intent)
if (progress == 10) {
var step = pref.getInt("servicestep", 249)
step += 1
pref.edit().putInt("servicestep", step).commit()
Log.i("MYTAG", "Таймер сохранился $step")
Log.i("MYTAG", "Progress if= $progress")
}
Log.i("MYTAG", "Progress после if= $progress")
}
override fun onFinish() {
intent.putExtra("step", 0)
sendBroadcast(intent)
check()
Log.i("MYTAG", "Таймер закончился")
}
}.start()
}
override fun onCreate() {
super.onCreate()
pref = getSharedPreferences("save", Context.MODE_PRIVATE)
check()
Log.i("MYTAG", "Сервис запущен")
}
override fun onDestroy() {
super.onDestroy()
Log.i("MYTAG", "Сервис отключен")
}
override fun onBind(intent: Intent): IBinder {
TODO("Return the communication channel to the service.")
}
}
BroadcastReceiver in Activity
br = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val serviceSteps = intent?.getIntExtra("step", 0)
if (serviceSteps != null) {
showProgress(serviceSteps, R.id.moves_bar)
if (serviceSteps == 10) {
val steps = presenter.loadMoveValue()
Log.i(TAGNAME, "Ресивер получил $steps")
presenter.showText("$steps", R.id.text_movesValue)
Log.i(TAGNAME, "Ресивер отобразил $steps")
}
}
}
}
registerReceiver(br, IntentFilter(BROAD))
I have a question. How do, for example, farm games work on Android? Resources are generated there in the background, and you just come and collect. How to implement this in the background in Android?
There are different methods to handle this, the easiest is to implement an offline calculation. Get a timestamp, when the service stopped working and get a timestamp when it is recreated and working again, with the time between these two timestamps you can calculate how often your service would have triggered.
This is as you already noticed not very safe and can easily be manipulated.
The 2nd approach is have a server handle the current moves and sync with the device. This needs a server, serverlogic and a database, but this can't be manipulated easy.
For multiplayer a serversynced solution is the better choice, if your game is single player only you can do it locally with services and offline calculation.
Which method you chose is your free choice.

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.

Categories

Resources