Why Android Foreground Service Started Again After Stopping It
Deep Dive into Android Services
Android O, N and beneath component lifecycles and groundwork tasks
This article was commencement published on developerlife.com . Yous can read all my content on Android, Kotlin, React, Firebase, and UX design on developerlife.com . To acquire more about me, please visit nazmulidris.com .
Introduction
Nearly sophisticated Android apps have to practice something that requires groundwork execution. This means in a Groundwork Thread, and not the primary thread (which is used for all UI updates).
If you create a Thread or an Executor in an Activity of your app, this leads to unpredictable results, since a elementary screen orientation change volition disrupt things, since the Activeness will no longer be around when the Thread completes its task.
You could use AsyncTask to handle this, but what if your app needs this Background Thread to be started from non just an Action, just a notification or another component?
In these cases, Android Services are the right Android component to use to match up the Thread'southward lifecycle with that of the Service's lifecycle.
A Service is an Android application component without a UI that runs on the main thread (of the hosting process). It besides has to be declared in the AndroidManifest.xml. If you want the service code to run in a Background Thread, so you must manage that yourself. The terms background
and foreground
are overloaded, and tin can apply to:
- Lifecycle of Android components.
- Threads.
In this commodity, we are going to use the terms groundwork and foreground to refer to the lifecycle of Android components. And when referring to Threads, we will use Background Thread
, and Foreground Thread
explicitly.
There is a bracket of Service that handles it's own Background Thread chosen IntentService which we are not going to cover in this article.
Threads, Services, and Android component lifecycles
Allow'southward take a footstep back and look at a larger picture of what Services are meant to do. Your lawmaking that runs in a Background Thread, like a Coffee Thread or an Executor isn't really bound to whatever the lifecycle of any Android components. If y'all think about an Activeness, it has a discrete starting and catastrophe point, based on user interaction. However, these starting and ending points don't necessarily connect with a Thread'due south lifecycle.
The following are loftier level points to notation in this diagram. The details for all of these points (and clarifications to them) are going to be provided in the rest of the article.
The Service's onCreate()
method is called when it is created (by starting it or bounden to it).
- Information technology then spawns a Thread or an Executor some time subsequently it has been created.
- When this Thread finishes, information technology lets the Service know that by calling
stopSelf()
. This is a very mutual design in Service implementations.
The code that you write in your Thread or Executor task will accept to tell the service whether the Background Thread has started or stopped.
- When the Thread starts up information technology has to set up the started state of the service (by calling
startService()
). - And when the Thread stops it has to call
stopSelf()
.
The Service's onDestroy()
method is called by the Android system only when you lot've let the service know that information technology's time stop. The Service doesn't know what is going on in the lawmaking running in your Thread or Executor task – it is your responsibility to let it know when you've started, and when you lot've finished.
In that location are 2 kinds of services — Started Services, and Jump Services. And a Service can exist both at the same time. We will at present cover the behaviors of three types of services:
- Started Services
- Bound Services
- Bound and Started Services
Android O Changes
A lot has changed with background Services in Android O. One of the main differences is that a Started Service that does not have a persistent notification won't be allowed to run in the background when the Activity goes away. In other words, you must have a persistent notification that you lot adhere the Started Service to. And yous too start Started Services with a dissimilar method — startForegroundService()
. And you have 5 seconds in gild to move this Started Service to the foreground and adhere a persistent notification to it, otherwise you will get an ANR. All of this will exist explained below with examples.
Started Services
A Started Service can be started by calling the startService(Intent)
method in your Activity or Service. This Intent has to be an explicit Intent, which means that you either make a reference to the Service's class, or you include the parcel proper noun of your app in the Intent. Hither'south some code to create an explicit Intent.
In gild to motion a Service to the Started state, you must telephone call startService()
with an explicit Intent. If you don't do this, then the service will not exist in a Started State. If the Service isn't in a Started Country, then it can't exist moved into the foreground, and stopSelf()
won't actually do anything.
So if y'all've not put the Service in the Started State, so you won't be able to attach a persistent notification to it either. These are all important things to go on in mind when you lot think about letting the Service know when it should put itself in the Started State.
A Service can be started multiple times. Each time information technology is started, then onStartCommand()
is called. A few parameters are passed to this command, along with any extras that are passed from your explicit Intent. Even if you lot start a Service multiple times, information technology volition simply phone call onCreate()
one time (unless of course this Service has already been bound to). In order to impale this Service, you have to ask it to finish by calling stopSelf()
. When the Service does stop (after you've asked it) and there's cypher else bound to it, then information technology will call onDestroy()
. Keep this in heed when allocating resources for your Started Service.
Intent
A Started Service is launched with an Intent. And the component that launches the Service doesn't really keep a connection to it, and if it needs to communicate something to the Started Service then it tin can get-go it again and pass it a different Intent. This is 1 of the main differences between Started and Bound Services. Bound Services on the other mitt follow a customer-server pattern. Where the customer (Android UI component or another Service) retains a stub (or folder) that information technology can utilize to phone call methods direct on the Service (in this case the server).
public class MyActivity extends Activity{
@TargetApi(Build.VERSION_CODES.O)
private void moveToStartedState() {
Intent intent = new MyIntentBuilder(this)
.setCommand(Command.Offset).build();
if (isPreAndroidO()) {
Log.d(TAG, "Running on Android Northward or lower");
startService(intent);
} else {
Log.d(TAG, "Running on Android O");
startForegroundService(intent);
}
}
}
Keep in mind that things have changed regarding Started Services in Android O. They are no longer allowed to run in the background without having a persistent notification. And the method to kickoff a groundwork Started Service in O, is startForegroundService(Intent)
.
Foreground and persistent notification
Started Services can run in the foreground. Once again, the term foreground
doesn't utilise to whether the Service is running on the primary Thread or a Groundwork Thread. It ways that the Android system will give this service highest priority and will effort not to destroy information technology when t is running low on organization resources. Yous should merely put a Started Service in the foreground
if it is critical to exercise this in order to evangelize a compelling user feel.
Sample use cases are:
- Apps that need to record or play media (sound/video) in the groundwork.
- Apps that need to capture fine grained location in the background.
When a Started Service moves into the foreground, it must brandish a persistent notification, explicitly notifying the user that the service is running. This is important because a Started Service in the foreground is detached from the lifecycle of UI components (with the exception of the persistent notification). And there's no way to let the user know that something is running on their phone without displaying any UI. And potentially consuming
lots of resource on their phone.
Here's an example of running an already Started Service in the foreground.
public class MyActivity extends Action{
private void commandStart() { if (!mServiceIsStarted) {
moveToStartedState();
return;
}
if (mExecutor == cipher) {
// Get-go Executor task in Groundwork Thread.
}
}
}
Here's the code to display the persistent notifications before Android O.
Here's the code to brandish the persistent notifications on Android O (using Notification channels).
Also, here's some other article going into more details of MediaStyle notifications (since sound background playback needs both notifications and leap and started services).
Stopping Started Services
Notation that the PendingIntent piStopService
(that is passed to the mNotification
) would actually pass an Intent with the Command.STOP
integer. Remember that startService(Intent)
can be chosen multiple times? This is an case of that. In gild to STOP the service, nosotros are firing an Intent on it via startService(Intent)
that volition be handled in the onStartCommand()
method of the Started Service.
public class HandleNotifications{
private static PendingIntent getStopServicePI(Service context) {
PendingIntent piStopService;
{
Intent iStopService = new MyIntentBuilder(context)
.setCommand(Command.STOP).build();
piStopService = PendingIntent.getService(
context, getRandomNumber(), iStopService, 0);
}
return piStopService;
}
}
This is why the onStartCommand()
of the Started Service needs to be able to handle Intents that might cause the service to stop itself. Hither's some code to illustrate this.
When you want to take your Started Service out of foreground execution, you tin call stopForeground(true)
. This will as well accept away the persistent notification. However, this will not stop the service. In order to practise that you still take to call stopSelf()
.
To stop a service, you tin do any of the following:
- As shown higher up, laissez passer an Intent with an actress to
startService()
that volition exist candy byonStartCommand()
, which will really phone callstopSelf()
on the service. If there are no clients spring to it, then it will result inonDestroy()
existence called, and the service shutting downwardly. - You tin as well create an explicit Intent (pointing to the Service class) and pass it to
stopService()
, which will causestopSelf()
to exist called, and thenonDestroy()
in case there are no bound clients.
Hither'south some lawmaking samples for stopping a Started Service from an Activity.
public class MyActivity extends Action{
void stopService1(){
stopService(new MyIntentBuilder(this).build());
}
void stopService2(){
startService(new MyIntentBuilder(this)
.setCommand(Command.Cease).build());
}
}
And hither's the code in your Started Service that would respond to these (assuming that your Started Service has been moved to the foreground).
public class MyService extends Service{
private void stopCommand(){
stopForeground(true);
stopSelf();
}
}
Jump Services
Different Started Services, Bound Services let a connexion to exist established between the Android component binding to the Service, and the Service. This connection is an IBinder which allows methods to be called on the Service. The simplest example of this is a Leap Service that has a client in a local process. In this case a Coffee object (an Binder subclass) is exposed to the customer which can be used to access public methods on the Service.
In more complex scenarios where the Bound Service might exist executing in a different process from the customer, a Bulletin handler or AIDL stub would have to be created. Nonetheless, for local processes, it'due south really straightforward.
Here are a list of differences between Bound Services and Started Services.
- A client component doesn't have a connection to a Started Service. It just fires Intents via
startService()
orstopService()
that are candy by the Started Service'sonStartCommand()
. - When a customer component (Activity, Fragment, or another Service) connects to a Bound Service, it has an IBinder object which tin can be used to telephone call methods on the Bound Service.
In either instance, if the Service (Bound or Started) needs to send messages to the bound client or whatsoever component started a Service, it has to use something like LocalBroadcastManager when the the client and Service are local to one process. Bound Services don't typically connect directly to a spring customer component.
bindService() and onCreate()
In order for a customer component (Activity, Fragement, another Service) to demark to a Jump Service, bindService()
must exist chosen, with an explicit Intent just like with Started Services.
Hither's a code example.
public grade MyActivity extends Activity{
void bind(){
bindService(
new MyIntentBuilder(this).build(),
mServiceConnection,
BIND_AUTO_CREATE);
}
}
BIND_AUTO_CREATE
is a very common flag to pass to the bindService()
method. There are other flags that y'all can pass. What auto create does is that it calls onCreate()
on the Jump Service if that hasn't happened yet, at the time bindService()
is called. This essentially automatically creates the Leap Service upon the kickoff customer connecting to it.
Once bindService()
is chosen, the service needs a way to react out to the customer, and give it the IBinder object which it tin so apply to call methods on the Spring Service. This happens in the code above via the mServiceConnection
reference. This is a ServiceConnection callback which the Spring Service will use to notify the customer near the completion of the binding process. It will allow the customer know as well if the Leap Service has asunder.
Here's an example of a ServiceConnection
implementation.
public class MyActivity extends Activeness{
private ServiceConnection mServiceConnection =
new ServiceConnection(){
public void onServiceConnected(
ComponentName cName, IBinder service){
MyBinder binder = (MyService.MyBinder) service;
mService = binder.getService();
// Get a reference to the Bound Service object.
mServiceBound = truthful;
}
public void onServiceDisconnected(ComponentName cName){
mServiceBound= false;
}
};
}
Service folder
Let's look at what happens on the Leap Service side, when a client calls bindService(Intent).
In the Leap Service you take to implement the onBind() method. This gets called simply in one case, when the very beginning client component connects to this Bound Service.
Hither's an example of this.
public class MyService extends Service{
public IBinder onBind(Intent intent){
if (mBinder == null){
mBinder = new MyBinder();
}
return mBinder;
}
}
The Bound Service creates a mBinder
object of type IBinder. And then what is this IBinder?
Binder is an Android base class that allows a remotable object to be created. It implements a lightweight RPC machinery for loftier operation in-process and cantankerous-process calls (between clients and Jump Services).
Here'due south an example.
public course MyService extends Service{
public class MyBinder extends android.os.Binder {
MyService getService(){
// Simply return a reference to this instance
//of the Service.
return MyService.this;
}
}
}
In the instance to a higher place, we simply expose a method called getService()
which exposes the Java object for the Bound Service to the customer component. With a reference to this IBinder, the client can call public methods straight on the Bound Service object. Note that these methods will execute on the thread of the client component that is calling these methods. In the instance of an Activity or Fragment, these methods will run on the master thread, then exist conscientious of calling blocking methods on the Spring Service and causing ANRs in your app.
unBind and onDestroy
In order to unBind() from a Leap Service, a calling simply calls unBindService(mServiceConnection
). The system will then telephone call onUnbind()
on the Bound Service itself. If there are no more bound clients, then the system volition call onDestroy()
on the Bound Service, unless it is in the Started State. If the Service is not in a Started State, and then onDestroy()
gets called immediately, and the Bound Service will be killed.
Hither'due south what a client component's call to unbindService()
looks similar.
public class MyActivity extends Action{
protected void onStop(){
if (mServiceBound){
unbindService(mServiceConnection);
mServiceBound = fake;
}
}
}
In the code to a higher place, the Activity's onStop()
method is overridden to call unbindService()
. Depending on the UX requirements for your app, your client components can bind and unbind to a Spring Service in onStart()
and onStop()
, or whatever other Android Activity or Fragment of Service lifecycle methods y'all choose to hook into.
Here'south an example of what onUnbind()
looks like (in the Bound Service).
public class MyService extends Service{
public boolean onUnbind(Intent i){
render false;
}
}
Typically, you will return false
. If you don't, then when the adjacent customer binds to the Bound Service, then onRebind()
will be called, instead of onBind()
.
Leap and Started Services
There are many use cases for your app that will require a service to be both Spring and Started. In the sections above, lots of detail have already been provided that highlight the implications to services beingness both. They usually involve the cosmos and devastation hooks for a Service.
A Service that is both Bound and Started tin can take methods within of that tin be chosen by leap client components. Since a Service doesn't have to be started for a client to bind to it, this is something that you must be enlightened of. This means that a customer binding to a Service will call onCreate()
. If you don't move your Service to a Started Land, and then when the client unbinds from the Service, information technology will be killed and it's onDestroy()
method will be called.
This is what happens with a UI component will demark to the Service and create it. Then at some indicate the UI unbinds from the Service, and if it'due south in the middle of performing some long running task, and so it's onDestroy()
will be created and it will be killed. If your app requirements are that the Spring Service should go on running past the end of the UI component's lifecycle, then you lot have to start information technology, move it to the foreground and accept it show a persistent notification. This will ensure that the Bound and Started Service will proceed running as long as it has to, or until the user decides to kill it by firing the PendingIntent
to stop the Service (as shown in the examples above).
Moving to Started State
Since a client binding to a Service will not move it to the Started Land, for Bound and Started services, information technology is prophylactic to have the Service movement itself into the Started Country but in case.
In the case higher up:
-
commandStart()
can be called by a customer that binds to the Service. - Or information technology can be chosen via an Intent that is passed to
startService()
orstartServiceInForeground()
(for Android O).
Regardless, what the case shows is the Service putting itself in the Started Country starting time, earlier actually creating the Executor.
Let's say that commandStart()
is called, afterwards a client component binds to the Service. The Service hasn't been started even so.
Let'south walk thru this code.
- If the Service is Spring to a client, then it is non started (and mServiceStarted is false).
- In this instance the telephone call to
moveToStarted()
) state only creates an explicit Intent with an Extra (Command.Starting time
), and callsstartService()
orstartForegroundService()
. - This ends up calling
onStartCommand()
which routes tocommandStart()
again! - Notwithstanding, this time in
commandStart()
mServiceIsStarted
is set totrue
and this volition really practice the work ofcommandStart()
which is to create the Executor.
Destruction and unbinding
When a customer component unbinds from your Service, if information technology is Not in the Started State then it will be killed and onDestroy()
volition be chosen.
However, if it is in the Started State, then it will not be killed. It will simply be killed if the Started Service is stopped (either by calling stopService(Intent)
or past calling startService(Intent)
with Extras to let the service know it should stop, for eg: Command.Terminate
)).
Here'southward a diagram that summarizes these states and transitions betwixt them for a bound and started service.
Source code example
Yous can meet examples of most of the things outlined in this article in the source code for the Awake app. It is a simple utility for Android O and N that keeps your screen on while charging. Here are some links:
- Awake Android App on Google Play Store
- Source code for Awake app on GitHub
This article was beginning published on developerlife.com . You tin read all my content on Android, Kotlin, React, Firebase, and UX design on developerlife.com . To learn more about me, please visit nazmulidris.com .
Source: https://proandroiddev.com/deep-dive-into-android-services-4830b8c9a09
0 Response to "Why Android Foreground Service Started Again After Stopping It"
Postar um comentário