After reopening application, multiple instances of a service are created - android

I am trying to create an application to update a persistent notification even while the app is closed. Right now, I'm using a service that is started in MainActivity's onCreate():
serviceIntent = Intent(this, PersistentService::class.java)
val stopped = stopService(serviceIntent)
println("[123] stopped: $stopped")
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent)
} else {
startService(serviceIntent)
}
This works perfectly to start it. However, when I reopen and close the app, even though stopped: true is printed, the previous service is still running and the previous service's stopService() was not called.
Stripped down version of the PersistentService class:
var timesStarted = 0
var lastService: PersistentService? = null
class PersistentService: Service(){
private var timer: Timer? = null
private var task: AsyncTask<*, *, *>? = null
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
timesStarted++
println("[123]starting persistent service. timesStarted: $timesStarted lastService===this: ${lastService===this} lastService==this: ${lastService==this} lastService: $lastService")
println("[123] hashCode: ${hashCode()}")
lastService = this
if(timer == null){
timer = Timer()
}
setToLoadingNotification()
timer?.scheduleAtFixedRate(object : TimerTask(){
override fun run(){
println("[123]Updating hashCode: ${this#PersistentService.hashCode()}")
if(task?.status == AsyncTask.Status.RUNNING){
// send notification saying last request timed out
}
task?.cancel(true)
task = DataUpdaterTask(DatabaseDataRequester { GlobalData.connectionProperties }) { dataRequest ->
// send notification based on dataRequest value
}.execute()
}
}, 1000L, UPDATE_PERIOD)
return START_STICKY
}
private fun notify(notification: Notification){
getManager().notify(NOTIFICATION_ID, notification)
startForeground(NOTIFICATION_ID, notification)
}
private fun getBuilder(): Notification.Builder {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
return Notification.Builder(this, NotificationChannels.PERSISTENT_STATUS.id)
}
return Notification.Builder(this)
}
private fun getManager() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
override fun stopService(name: Intent?): Boolean {
println("[123]Stopping persistent service")
timer?.cancel() // stop the timer from calling the code any more times
task?.cancel(true) // stop the code from running if it's running
// getManager().cancel(NOTIFICATION_ID)
return super.stopService(name)
}
}
Here is the output
[123] stopped: false
[123]starting persistent service. timesStarted: 1 lastService===this: false lastService==this: false lastService: null
[123] hashCode: 4008007
[123]Updating hashCode: 4008007
[123]Got successful data request
[123]Updating hashCode: 4008007
[123]Got successful data request
// *close and reopen the app*
[123] stopped: true
[123]starting persistent service. timesStarted: 2 lastService===this: false lastService==this: false lastService: me.retrodaredevil.solarthing.android.PersistentService#3d2847
[123] hashCode: 7823272
[123]Updating hashCode: 7823272
[123]Got successful data request
[123]Updating hashCode: 4008007
[123]Got successful data request
[123]Updating hashCode: 7823272
[123]Got successful data request
As you can see from the output, both services are running at the same time. The hashcode shows that they are not the same object so it wouldn't matter if I put my code in the onCreate() instead of onStartCommand() (I already tested this anyway)
It would be helpful if anyone could point me in the right direction. I am new to android development and I had trouble finding the correct way to do this. I'm not even sure if what I'm doing right now is the best way to update the notification.

and the previous service's stopService() was not called.
stopService() is not a lifecycle method of a Service. Perhaps you are thinking of onDestroy().
the previous service is still running
No, the previous service instance was stopped. You just leaked the Timer, because you did not cancel the Timer in onDestroy().
So, override onDestroy(), put your cancel() calls in there, get rid of the rest of stopService(), and you should be in better shape.

Related

Check for network status update while application killed

My scenario is the following:
I'm working on a chat application and I would like to implement some type synchronization service that starts itself when device recovers network connection. Anytime device has network connection again, unsent messages are going to be automatically sent. With independence of application state (foregorund, background or killed).
Options tried:
1. Broadcast Receiver with android.net.conn.CONNECTIVITY_CHANGE
This scenario only works when the application is active (Foreground or Backround) but stops working when app is killed.
2. Foreground service
A notification is going to be shown all the time which is not ideal. Also I want to avoid draining users' battery.
3. AndroidX.Work.Worker
PeriodicWorkRequest networkCheckingPeriodicWork = PeriodicWorkRequest.Builder.
From<ConnectivityChangeWroker>(repeatInterval:30, repeatIntervalTimeUnit: Java.Util.Concurrent.TimeUnit.Minutes, flexInterval:25, flexIntervalTimeUnit:Java.Util.Concurrent.TimeUnit.Minutes)
.SetConstraints(new Constraints.Builder().SetRequiredNetworkType(AndroidX.Work.NetworkType.Connected)
.SetRequiredNetworkType(AndroidX.Work.NetworkType.Unmetered).Build()).Build();
WorkManager.Instance.EnqueueUniquePeriodicWork("", ExistingPeriodicWorkPolicy.Replace, networkCheckingPeriodicWork);
public class ConnectivityChangeWroker : AndroidX.Work.Worker
{
public ConnectivityChangeWroker(Context context, WorkerParameters workerParameters) : base(context, workerParameters)
{
}
public override Result DoWork()
{
try
{
//Start synch service
return Result.InvokeSuccess();
}
catch (Exception)
{
return Result.InvokeFailure();
}
}
}
But in this case, I'm not achieving the desired behaviour. For my undestanding, I just set a periodic work that checks for network connection, and if there is one, runs DoWork() method.
-- EDIT --
4.JobService
Java.Lang.Class javaClass = Java.Lang.Class.FromType(typeof(ConnectivityChangeJob));
ComponentName component = new ComponentName(Application.Context, javaClass);
JobInfo jobInfo = new JobInfo.Builder(1, component)
.SetRequiredNetworkType(Android.App.Job.NetworkType.Any)
.SetOverrideDeadline(5000)
.SetPersisted(true)
.Build();
JobScheduler jobScheduler = (JobScheduler)GetSystemService(JobSchedulerService);
jobScheduler.Schedule(jobInfo);
[Service(Name = "Extintores.ConnectivityChangeJob", Permission = "android.permission.BIND_JOB_SERVICE")]
public class ConnectivityChangeJob : JobService
{
private Intent startServiceIntent;
public ConnectivityChangeJob()
{
}
public override bool OnStartJob(JobParameters jobParams)
{
//Start synchService
return true;
}
public override bool OnStopJob(JobParameters jobParams)
{
return true; //Reschedule the job
}
}
But in this case, OnStartJob is only fired the first time the applicatio is opened and, apparently, never again.
Is there any way I can achieve what I'm aming for?
-- EDIT --
I want to achieve the same bahaviour as applications as WhatsApp. When it detects network connection again, automatically all unsent messages are going to be send.
I guess the AndroidX.Work.Worker is the best option.
In DoWork you should update databases and send requests.
Besides worker supports long-running workers
Example DownloadWorker:
class DownloadWorker(context: Context, parameters: WorkerParameters) :
CoroutineWorker(context, parameters) {
private val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as
NotificationManager
override suspend fun doWork(): Result {
val inputUrl = inputData.getString(KEY_INPUT_URL)
?: return Result.failure()
val outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME)
?: return Result.failure()
// Mark the Worker as important
val progress = "Starting Download"
setForeground(createForegroundInfo(progress))
download(inputUrl, outputFile)
return Result.success()
}
private fun download(inputUrl: String, outputFile: String) {
// Downloads a file and updates bytes read
// Calls setForeground() periodically when it needs to update
// the ongoing Notification
}
// Creates an instance of ForegroundInfo which can be used to update the
// ongoing notification.
private fun createForegroundInfo(progress: String): ForegroundInfo {
val id = applicationContext.getString(R.string.notification_channel_id)
val title = applicationContext.getString(R.string.notification_title)
val cancel = applicationContext.getString(R.string.cancel_download)
// This PendingIntent can be used to cancel the worker
val intent = WorkManager.getInstance(applicationContext)
.createCancelPendingIntent(getId())
// Create a Notification channel if necessary
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel()
}
val notification = NotificationCompat.Builder(applicationContext, id)
.setContentTitle(title)
.setTicker(title)
.setContentText(progress)
.setSmallIcon(R.drawable.ic_work_notification)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build()
return ForegroundInfo(notificationId, notification)
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createChannel() {
// Create a Notification channel
}
companion object {
const val KEY_INPUT_URL = "KEY_INPUT_URL"
const val KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME"
}
}

How to properly wait for an Android Service to be bounded to an Activity

I am developing an app in which several Activitis bound to a sevice once they become visible to the user. During start up, each Activity needs to:
check some status flag of the service, based on which some UI elements are configured
check wether an adapters is enabled whose reference is inside the service
execute some functions of the Service
Since the applications should not get updates in the background, I bind to the service at onStart() and unbind at onStop(). I.e. in have something like this:
override fun onStart() {
super.onStart()
Intent(context, MyService::class.java).also { intent ->
bindService(intent, serviceCallback, Context.BIND_AUTO_CREATE)
}
}
Now I want to perform the above mentionned actions inside onResume.
override fun onResume() {
super.onResume()
// check flags
// check adapter status
// excute functions of service
}
The problem is that binding to a service is asynchronous and I do not have a valid reference to the Service's binder inside onResume(). Consequently, the app will crash with a nullpointer exception.
Approach 1: Using lateinit
I tried solving this problem using the lateinit keyword. I.e. I define the reference to the binder as
private lateinit var myBinder: MyService.LocalBinder
Problem: I cannot guarantee that the binder is initialized as it is asynchronous. Thus, the app will crash.
Approach 2: Waiting for callback in while loop
In my service callback, I set a flag as follows:
val serviceCallback = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
myBinder = service as MyService.LocalBinder
isServiceBounded = true
}
override fun onServiceDisconnected(arg0: ComponentName) {
isServiceBounded = false
myBinder = null
}
}
Then inside onResume, I block the Activity until the flag is true
override fun onResume() {
super.onResume()
while(!isServiceBounded){
// block and wait
}
}
Problem: Doesn't work either. The app will stop responding and crashes.
Approach 3: Using suspended functions and Kotlin coroutines
A suspended function will not continue unless it has received a return value. This, I can use it to wait for an event. So I tried something like this:
override fun onStart() {
super.onStart()
CoroutineScope(Dispatchers.Main).launch {
bindServiceAndWait(this#Activityname)
}
}
suspend fun bindServiceAndWait(context: Context): Boolean{
Intent(context, MyService::class.java).also { intent ->
bindService(intent, serviceCallback, Context.BIND_AUTO_CREATE)
}
return isServiceBounded // This is the flag from the callback
}
Problem: This suspended function does not actually wait for the callback. It just returns the current value of isServiceBounded.
I found a similar solution here, but I do not quite understand this solution as it has a global service callback (ServiceConnection) as well as a local one inside the suspended function. Also, I don't understand how to I could unbound in this provided example.
What is the proper way of doing this?
you simply can't ensure that service will be bounded until onResume gets called. why won't you introduce flag isResumed, set it in onResume (unset in onPause) and line below check if (isResumed && isServiceBounded).... yes, there is a chance that isServiceBounded = false in onResume, so same if check put in onServiceConnected

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 can I run a service the would have access to my database and could run operations on it, but won't stuff up the UI thread

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.

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