Stop service when app goes to background, using a common Base Activity - android

I have a Base Activity class to implement common behaviour for all the Activities. All of them extend this BaseActivity.
I'm binding to a service in in the BaseActivity's onStart method and unbinding conditionally in the onStop method. With conditionally, I mean that depending on some option selected by the user, the service should or should not keep running in the background when the app goes to background.
The problem is that sometimes the service keeps running when it shouldn't (this is, when the option for killing it is enabled, and unbindService() is effectively called).
I'm thinking that on every Activity change the service is unbound and bound again. Since bound services are reference counted, maybe my service is bound more times than unbound, so that's why it keeps running at the end, even when I call unbindService().
Additionally, the documentation says something about that:
You should usually pair the binding and unbinding during matching bring-up and tear-down moments of the client's lifecycle. For example:
If you only need to interact with the service while your activity is visible, you should bind during onStart() and unbind during onStop().
If you want your activity to receive responses even while it is stopped in the background, then you can bind during onCreate() and unbind during onDestroy(). Beware that this implies that your activity needs to use the service the entire time it's running (even in the background), so if the service is in another process, then you increase the weight of the process and it becomes more likely that the system will kill it.
Since I'm kind of trying to implement both options, what should be the best approach to implement this?

Finally I changed my approach and decided to use only startService() and communicate with the service using a Local Broadcast Receiver.
I start the service in the onCreate() and stop it in the onDestroy() methods of the Base Activity.
Then, to send a message from the Service to the activity, I use this:
private void sendBroadcastMessage(String msg) {
Log.d(LOG_TAG, "send broadcast message: " + msg);
Intent intent = new Intent(MyService.class.getSimpleName());
// Add data
intent.putExtra("message", msg);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
Then, to be notified in the activity:
// handler for the events launched by the service
private BroadcastReceiver mMyServiceReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Extract data included in the Intent
String message = intent.getStringExtra("message");
Log.d(LOG_TAG, "Got message: " + message);
// Do stuff...
}
};
And to register the Receiver in the activity:
#Override
public void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(this).registerReceiver(mMonitorReceiver,
new IntentFilter(MyService.class.getSimpleName()));
}

Related

Update UI at onResume using LocalBroadcastManager

I have a Service that frequently updates the Main Activity UI by passing values via a LocalBroadCastManager. The following method is triggered within the Service to pass the value to the Main Activity:
private void updateUI(String statusValue){
broadcastIntent.putExtra("status", statusValue);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);
}
Within the Main Activity I added a BroadcastReceiver to pick up the value and update the UI accordingly:
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Get extra data included in the Intent
String brStatus = intent.getStringExtra("status");
if(brStatus != null){
//Update UI
}
}
}
};
When the user navigates to another activity the receiver for the Broadcasts is unregistered as the user wont see the UI. Then onResume() when the user returns to the activity the receiver is reregistered:
LocalBroadcastManager.getInstance(this)
.registerReceiver(mMessageReceiver, new IntentFilter("speed-stats"));
UPDATE:
Whilst the activity is paused, the user can make actions (such as 'Pause') by clicking on Pending Intents on the ongoing notification. This action is handled within onStartCommand() of the Service:
case PAUSE_SERVICE :
Log.i(LOG_TAG, "Pause Foreground service.");
startForeground(NOTIF_ID,makeNotification(isRunning = false));
updateUI("paused");
stopSpeed();
break;
This works fine, however i have noticed that the UI is not updated as the receiver is unregistered whilst the activity is paused.
Is it possible to continue these UI updates despite pausing? Or is it possible to apply the UI updates as soon as the activity is resumed?
When the user navigates to another activity the receiver for the
Broadcasts is unregistered as the user wont see the UI.
This is the reason why your Activity's data is not up to date. Since the broadcast receiver is unregistered, data sent by the service won't be received.
Since its not a great idea keep the receiver registered, one solution would be:
Bind the service in on onResume() an unBind() it in onStop().
Inside service maintain data object, which will hold the latest data.
After service is binded, call the service method through Binder which
will return the the data Object with latest data.
Update the data in your Activity accordingly.
You can refer this SO for binding/unbinding service

Send broadcast intent from service to Application Class

Is it possible to send an intent from a service to an Application class? Not Activity?
I wouldn't know what activity would be running at a particular time, so I am adding a boolean flag in the activity class that detects the activity and sends the appropriate data based on the broadcast received.
If your Service is active, then your Application class is active as well.
Otherwise you wouldn't be able to use getApplicationContext().
Although I'm skeptic about a service that runs forever there is a very clean way to make the Service communicate with a certain Activity, should the last one be currently active.
Such clean way is called LocalBroadcastManager.
The Activity meant to receive the data should register a BroadcastReceiver in onResume() and unregister it in onPause().
You instantiate your BroadcastReceiver in your Activity's onCreate()
this.localBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Do what you have to do here if you receive data from the Service.
}
}
You create a Filter so your Activity only listens to a certain type of signals.
private IntentFilter notifIntentFilter new IntentFilter("com.you.yourapp.MY_SIGNAL");
in onResume()
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(this.localBroadcastReceiver, notifIntentFilter);
in onPause()
LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(this.localBroadcastReceiver);
Now whenever you want to send data to your Activity, your Service can call:
final Intent intent = new Intent();
intent.setAction("com.you.yourapp.MY_SIGNAL");
// put your data in intent
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
If your Activity is awake, it will respond to the signal. Otherwise, if it's in the background, or it is not instantiated it won't.
You can apply this pattern to as many Activities as you wish.
Still, I have never used this inside the Application class. But you can try to register your receiver there. It might work, since if the Application class is destroyed, the BroadcastReceiver is destroyed too and thus probably unregistered as well.
The point is, if your Application gets destroyed, your Service will be killed as well. Unless you launched it in another process. But then it will have it's own instance of Application; and this is a complex thing you probably do not want to get into details now...
Important: since the Application class is not tied to any UI component, you can do whatever you need directly inside your service. If you need to manipulate the UI, then the pattern described above will work for you.
Please read about new Android's background limitations.
Edit:
Oh yeah right, if you need your Service to call a function declared in your Application class, you can just do
((MyApplication) getApplication()).myFunctionToHandleData(Intent intent);
I didn't really understand your question though, but either of the methods described above should work for you.

What can I expect when onCreate() calls startService()

I am trying to understand the Service Life Cycle while working through some Android Open Source Code.
I was looking at a Service implementation which I distilled down to something like the following...
public class MyService extends Service {
public MyService() { super(); }
#Override
public void onCreate() {
super.onCreate();
init();
//==this seems odd to me
//comment in AOSP says startService() is called to make
//sure Service stays around long enough for the async call
//to complete.
startService(new Intent(this, myservice.class()));
doSomeMoreInitAsync();
}
#Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
if(actionableIntent(intent,flags,startId)) {
//do something
//NOTE: the Intent passed to startService() in onCreate()
//above will go around this block of code, doing nothing
//except returning START_STICKY
}
return START_STICKY;
}
public void onDestroy() {
//destroy stuff
}
#Override
public IBinder onBind(final Intent intent) {
return mBinder; //an instance of android.os.Binder derivative
// created when this service was instantiated
}
//other stuff
}
Why would someone want to have onCreate() call startService() on itself like above, doing nothing? The comment in code sheds some light, but it's like assumptions are being made about the Life Cycle that I don't understand. I.e., is it reasonable to expect onCreate() to effectively start its own service?
I know that if a service has already been started then onCreate() will only be called once (unless destroyed and restarted, then a new instance of the service is created and onCreate() is called once on it). My first concern with this example would be that there is an expectation placed upon the underlying Service API implementation that the Service is already in the initialized state before onCreate() is called (else there be an infinite recursion, but there is not).
But isn't onCreate() supposed to be part of the initialization (albeit an optional part for the subclass)?
Is this coding logic a reasonable way of making sure the Service is forced to be an Unbounded Service? Or am I looking at a bad example in the AOSP which may have undefined behavior in the future?
You are correct in that a Service will call onCreate and onStartCommand if it is started via Context.startService. So in this sense, when you return START_STICKY, the Service will continually run until an explicit call to stopService() is called. It will also be destroyed and restarted during this lifecycle.
Another way to create a Service, is by binding to it. As per the docs:
Clients can also use Context.bindService() to obtain a persistent connection to a service. This likewise creates the service if it is not already running (calling onCreate() while doing so), but does not call onStartCommand().
So, it's possible for a Service to be created by simply binding to it. However, the lifecycle of a Service indicates that it will remain if it is started or a client is still bound to it. Meaning, that if it was created by a bind command, it will immediately be destroyed as soon as the client unbinds.
So, if a Service starts itself in the onCreate(), it will ensure that it puts itself in the started state regardless of whether it was created by binding or by an explicit call to startService. Since there's no actionable intent, the onStartCommand will just pass straight through. An clients that call startSevice will, presumably, have actionable Intents in which case the Service will perform its duties.

How can a background service know if activity is killed or not

I have an activity that starts a background service. Once it is started, it runs forever.
Lets say the background service needs the activity that started it to update something. Then how can I start the activity again "If it is not started" however if it is already started then send a broadcast?
Thanks
You would have to bind to the service in your activity. Then, in the service, implement the onBind and onUnbind methods to set a boolean "bound". Check the boolean to see whether the activity is active.
With activity you mean the service's process. If the service is started forever, then its process its started foerever (except when the system kill its for recovering memory purpose and recreates it later). That doesnt mean the rest of activities/fragments/services are not kill. A service is just an entry point for your application and it gives your process a position into the process priority ladder.
Its hard to say, since I don't know the details of your app, but I think that you may want to consider a bit of a redesign.
As you have noticed, Activities are ephemeral. Generally speaking, a service should not (cannot) depend on a particular Activity being active.
In fact, a well-designed service should not depend on any particular activity at all.
I'm not sure I completely follow your question. However, when communicating from a Service to an Activity I use a broadcast receiver in the Activity class.
This can be created as follows:
// register the BroadcastReceiver in your activity class
this.receiver = new NotificationReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("com.example.LISTENER");
registerReceiver(receiver, filter);
// insert in your activity class
class MyReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
if(intent.hasExtra("command")) {
if(intent.getStringExtra("command").equals("userRegistered")) {
// insert code to do something when this intent is received
}
}
}
}
// insert in your service class to send message
Intent i = new Intent("com.example.LISTENER");
i.putExtra("command", "userRegistered");
sendBroadcast(i);

Service notification when an activity is killed

My application has an activity and a service running in the same process. When the user terminates the activity by clicking the STOP button, the activity causes the service to terminate.
From the Android documentation I understand that the system can kill a process to reclaim resources:
"Once your activity is stopped, the system might destroy the instance if it needs to recover system memory. In extreme cases, the system might simply kill your app process without calling the activity's final onDestroy() callback, ..."
Question 1: Is it possible for my activity to be killed but not the service? I would prefer my service remain alive.
If the answer is 'yes' then
Question 2: Is there a mechanism where the service can be notified that the activity has been killed?
IBinder.linkToDeath has the functionality I need but seems to apply to a process, not an activity.
I've thought of an indirect method like having the activity take ownership of semaphore and having the service use a thread to wait on it. Then when the activity gets killed it will release the semaphore and the service will get it, providing the notification. But I was wondering if there wasn't an android-centric technique I could use.
[begin edit]
After reading the very helpful comments you've provided I'd like to clarify the scenario I'm presenting.
I've started two components: an activity and a service. The application is configured so that service can continue to run after the activity has stopped and been destroyed. The user can restart/start the activity multiple times and it will use the same instance of the service.
Normally the activity will notify the service it has been destroyed during onDestroy(). But onDestroy() may not be called.
I'd like to know whether there is some android-specific mechanism I can use to notify the service that the activity has been killed without the call to its onDestroy() method having been made.
[end edit]
Thanks for your help.
Will
As I said before, single activity won't be killed by android without calling onDestroy(). If Android needs more memory it kills whole process(with all activities and services). Here is description, documentation is wrong about this.
Service can be also created in other process but it has to be set int the manifest. And then you can use iBinder to get notification when process(with all activities) is killed by Android
Yes services and activities can run independently of each other.
To achieve what you're trying to do I would explicitly start your service using startService() in your activity's onStart() function (or wherever you wish to launch it) and also bind to it at the same point. Binding without an explicit startService will cause the service to stop when you kill the activity (unless some other activity is still bound to it).
In your activity's onStop() call a function on your service to tell it the activity has been killed.
In your Activity:
private ServiceRecordJourney yourService;
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
YourServiceBinder binder = (YourServiceBinder) service;
yourService= binder.getService();
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
}
};
#Override
public void onStart(){
super.onStart();
startService(new Intent(this, YourService.class));
// Bind to Service
Intent intent= new Intent(this, YourService.class);
bindService(intent, serviceConnection , Context.BIND_AUTO_CREATE);
}
#Override
public void onStop(){
super.onStop();
if(yourService != null){
yourService.activityKilledFunction();
unbindService(serviceConnection);
}
}
Your service will need to be foreground (and display a notification) to further stop it from being killed off by the OS. Also as it's been explicitly started you will need to handle an explicit stop on the service (either call stopSelf() on the service or stopService() in a context object.
You can handle communication to the service using multiple startService() calls with different intents but I prefer the above approach (and I'm sure that in the case binding is the preferable approach).
Also whilst the service is running the activity will bind and unbind to the service each time the activity is started or stopped by the user.

Categories

Resources