📜 ⬆️ ⬇️

Immersion in Android services

image


Translation of the article "Deep Dive into Android Services" by Nazmul Idris. I left the original name of the author, although it is rather not "immersion", but "acquaintance". I think the text will be useful for novice developers. The article perfectly complements the off. service documentation on Android . The article deals with the features of interaction with running and associated services. Plus, the articles are that changes in the work with services in Android O are taken into account. There are minor changes in comparison with the original, added for greater clarity.


Introduction


Most modern android applications perform part of the tasks in the background. This means that tasks are performed in the background thread, and not in the user interface thread (UI thread).


If you create Thread (thread) or Executor (thread control wrapper) in a specific Activity your application, this can lead to unpredictable results. For example, during a simple reorientation of the screen, your Activity is recreated, and for threads attached to the old Activity , there is no place to return the result.


To deal with this you could use AsyncTask . But what if your application needs to run this background thread not only from the Activity , but also from notification (or notification) or from another component?


In this case, the service is a suitable component of Android, which will link the life cycle of the thread with its life cycle, and thus will not lose it.


The service is a component of an android application without a visible interface that runs in the main application thread. The service must be declared in the manifest. If you need the service to work in a background thread, you must implement it yourself.


The terms background and foreground are overloaded, and can be applied to:


  1. the life cycle of Android components
  2. streams

In this article, by default we will assume that the terms background and foreground refer to the life cycle. But when it comes to streams, we will clearly speak of a background stream or a foreground stream .


There is a subclass of android services called IntentService , which runs tasks in the background stream out of the box. But we will not talk about such services in this article.



Android threads, services, and life cycle


Let's take a step back and look at a more general picture of what services should do. Your code that runs in a background thread, such as Thread or Executor , is not actually related to the life cycle of any Android component. If we are talking about an Activity , then it has a specific point of starting and stopping work, based on user interaction. However, these start and end points of an Activity not necessarily related to the life cycle of a Thread or an Executor .


image


Below are explanations of the main time points of this Gantt chart. Details of these points (and explanations of them) are given in the rest of the article.


The onCreate() service method is called at the time of its creation (by launching or linking to it).


Then, after a while, the service starts Thread or Executor . When Thread finishes, it lets the service know so that it can call the stopSelf() method. This is a fairly common service implementation pattern.


The code that you write in your Thread or Executor should inform the service about starting or stopping the background thread.



The onDestroy() service method is called by the system only when you inform the service that it is time to shut down. The service does not know what will happen in the code of your Thread or Executor - this is your area of ​​responsibility. Thus, the task of the programmer is to inform the service about the beginning and completion of work.


Services are divided into two types: running and associated . In addition, the service may be running and allow binding. We will consider each of the cases:


  1. Running service
  2. Tied service
  3. Linked and running service at the same time


Android O changes


In Android O (API 26), there have been significant changes in the regulation of background services by the system. One of the main changes is that a running service that is not in the white list (services that are visible to the user are placed on the white list; see the off-line documentation for details) or that does not explicitly inform the user about its work will not run in the background flow after closing Activity . In other words, you must create a notification (notification) to which you attach the running service. And you must start the service with the new startForegroundService() method (and not with startService() ). And, after creating the service, you have five seconds to call the startForeground() method of the running service and show the notice visible to the user. Otherwise, the system stops the service and shows ANR ("application does not respond"). Below we explain these provisions using code examples.


')

Running services


Started services begin their work after calling the startService(Intent) method in your Activity or service. In this case, the Intent should be explicit. This means that you must explicitly indicate in Intent the class name of the service you are running. Or, if it is important for you to allow some ambiguity about which service is running, you can provide intent filters for your services and eliminate the component name from the Intent, but then you must install the intent package with setPackage() , which provides sufficient ambiguity resolution for the target service. Below we give an example of creating an explicit Intent :


 public class MyIntentBuilder{ public static MyIntentBuilder getInstance(Context context) { return new MyIntentBuilder(context); } public MyIntentBuilder(Context context) { this.mContext = context; } public MyIntentBuilder setMessage(String message) { this.mMessage = message; return this; } public MyIntentBuilder setCommand(@Command int command) { this.mCommandId = command; return this; } public Intent build() { Assert.assertNotNull("Context can not be null!", mContext); Intent intent = new Intent(mContext, MyTileService.class); if (mCommandId != Command.INVALID) { intent.putExtra(KEY_COMMAND, mCommandId); } if (mMessage != null) { intent.putExtra(KEY_MESSAGE, mMessage); } return intent; } } 

For the service to start, you must call startService() with explicit intent. If you do not do this, then the service will not go into a running state. And thus, it will not be able to go to the fore, and stopSelf() not actually do anything.


So, if you did not put the service into a running state, you will not be able to attach it to the notification. These are quite important things that you need to remember when you need to put a service into a running state.


The service can be started several times. Every time it is launched, onStartCommand() called. Several parameters are passed to this method along with explicit Intent . Even if you start a service several times, it calls onCreate() only once (of course, if the service was not already attached before). To shut down, the service must call stopSelf() . After the service is stopped (when you stop it), and if nothing else is associated with it, onDestroy() called. Keep this in mind when allocating resources for your running service.



Intent


In order to start a running service, an Intent required. The Android component in which the service starts does not actually store the connection with it, and if it needs to inform something about the running service, it can start it again using another Intent . This is the main difference between a running and anchored service. Bound services for their part implement the client-server pattern. Where the client (component of the Android UI or another service) stores the connection and can call methods directly at the service through it.


 public class MyActivity extends Activity{ @TargetApi(Build.VERSION_CODES.O) private void moveToStartedState() { Intent intent = new MyIntentBuilder(this) .setCommand(Command.START).build(); if (isPreAndroidO()) { Log.d(TAG, "Running on Android N or lower"); startService(intent); } else { Log.d(TAG, "Running on Android O"); startForegroundService(intent); } } } 

Remember that in Android O, much has changed in terms of running services. They can no longer work long enough in the background without a mechanism for constant notification. And the start method of the running service in the background in Android O is startForegroundService(Intent) .



Foreground and mechanism for continuous notification


The running service can run in the foreground. Again, the term foreground does not refer to whether the service is running in the background thread or in the main thread. But this means that the system will give the service the highest priority, and therefore the service is not a candidate for the system to delete in case of insufficient memory. Putting service to the forefront is only when it is really necessary to create a modern and responsive application.


Examples of using front-end services:


  1. Applications that play media files in the background.
  2. Applications that update location data in the background.

When a running service is placed in the foreground, it should display a notification, explicitly telling the user that the service is running. This is important because the running service in the foreground is separated from the life cycle of the UI components (with the exception, of course, of the constant notification itself). And there is no other way to tell the user that something is working on his phone (and potentially consuming a lot of resources) other than to send a permanent notification to the UI.


Below is an example of starting a running service in the foreground:


 public class MyActivity extends Activity{ private void commandStart() { if (!mServiceIsStarted) { moveToStartedState(); return; } if (mExecutor == null) { // Start Executor task in Background Thread. } } } 

Here is the code to create a permanent notice in versions


before Android O
 @TargetApi(25) public static class PreO { public static void createNotification(Service context) { // Create Pending Intents. PendingIntent piLaunchMainActivity = getLaunchActivityPI(context); PendingIntent piStopService = getStopServicePI(context); // Action to stop the service. NotificationCompat.Action stopAction = new NotificationCompat.Action.Builder( STOP_ACTION_ICON, getNotificationStopActionText(context), piStopService) .build(); // Create a notification. Notification mNotification = new NotificationCompat.Builder(context) .setContentTitle(getNotificationTitle(context)) .setContentText(getNotificationContent(context)) .setSmallIcon(SMALL_ICON) .setContentIntent(piLaunchMainActivity) .addAction(stopAction) .setStyle(new NotificationCompat.BigTextStyle()) .build(); context.startForeground( ONGOING_NOTIFICATION_ID, mNotification); } } 

in Android O, through NotificationChannel
 @TargetApi(26) public static class O { public static final String CHANNEL_ID = String.valueOf(getRandomNumber()); public static void createNotification(Service context) { String channelId = createChannel(context); Notification notification = buildNotification(context, channelId); context.startForeground( ONGOING_NOTIFICATION_ID, notification); } private static Notification buildNotification( Service context, String channelId) { // Create Pending Intents. PendingIntent piLaunchMainActivity = getLaunchActivityPI(context); PendingIntent piStopService = getStopServicePI(context); // Action to stop the service. Notification.Action stopAction = new Notification.Action.Builder( STOP_ACTION_ICON, getNotificationStopActionText(context), piStopService) .build(); // Create a notification. return new Notification.Builder(context, channelId) .setContentTitle(getNotificationTitle(context)) .setContentText(getNotificationContent(context)) .setSmallIcon(SMALL_ICON) .setContentIntent(piLaunchMainActivity) .setActions(stopAction) .setStyle(new Notification.BigTextStyle()) .build(); } @NonNull private static String createChannel(Service ctx) { // Create a channel. NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); CharSequence channelName = "Playback channel"; int importance = NotificationManager.IMPORTANCE_DEFAULT; NotificationChannel notificationChannel = new NotificationChannel( CHANNEL_ID, channelName, importance); notificationManager.createNotificationChannel( notificationChannel); return CHANNEL_ID; } } 

In addition, here is another article that contains more details about creating notifications in MediaStyle (since for background playback of audio files, both notifications, associated and running services are needed)



Stop running services


Note that the piStopService parameter of type PendingIntent (which is passed to the notification constructor) actually transmits Intent with a Command.STOP constant of type Integer . Remember that startService(Intent) can be called several times? This is an example of this behavior. To stop the service, we start the Intent via startService(Intent) and then process this Intent in the onStartCommand() method of the running 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 explains why the onStartCommand() method should be able to handle Intent s. Using this mechanism, we can "tell" the service to stop work. Below is the code that illustrates these features:


  public class MyService extends Service{ @Override public int onStartCommand(Intent intent, int flags, int startId) { boolean containsCommand = MyIntentBuilder .containsCommand(intent); d(TAG, String.format( "Service in [%s] state. cmdId: [%d]. startId: [%d]", mServiceIsStarted ? "STARTED" : "NOT STARTED", containsCommand ? MyIntentBuilder.getCommand(intent) : "N/A", startId)); mServiceIsStarted = true; routeIntentToCommand(intent); return START_NOT_STICKY; } private void routeIntentToCommand(Intent intent) { if (intent != null) { // process command if (containsCommand(intent)) { processCommand(MyIntentBuilder.getCommand(intent)); } // process message if (MyIntentBuilder.containsMessage(intent)) { processMessage(MyIntentBuilder.getMessage(intent)); } } } } 

If you want to complete the execution of a running service in the foreground, you must call stopForeground(true) . This method will also terminate the ongoing notification. However, the service itself will not stop it. To do this, call stopSelf() .


To stop the service, you can do one of the following:


  1. As shown above, pass an Intent with an additional parameter to startService() , which will then be processed in onStartCommand() and the service will actually call stopSelf() . And, if no other components are attached to the service, it will call onDestroy() and the service will complete its work.
  2. You can also create an explicit Intent (pointing to the service class) and pass it to the stopService() method, which will call stopSelf() and, then, onDestroy() similar to claim 1.

Here are some examples of stopping a service from an Activity :


 public class MyActivity extends Activity{ void stopService1(){ stopService(new MyIntentBuilder(this).build()); } void stopService2(){ startService(new MyIntentBuilder(this) .setCommand(Command.STOP).build()); } } 

And here is the code in your service that will handle these requests (assuming that your running service is in the foreground):


 public class MyService extends Service{ private void stopCommand(){ stopForeground(true); stopSelf(); } } 


Linked Services


Unlike running services, linked services allow you to establish a connection between the Android component associated with the service and the service. This connection is provided by the implementation of the IBinder interface, which defines methods for interacting with the service. A simple example of this would be implementing an associated service in the same process as the client (that is, as part of your own application). In this case, the Java object, a Binder subclass, is passed to the client, which can use it to invoke service methods.


In more complex scenarios, when it is necessary for the service interface to be available for different processes, the Messenger object should be used to provide the client with the service interface (this is a reference to the Handler object that receives a callback for each call from the client), so that you can interact with the service using Message objects. The Messenger object is actually based on AIDL (Android Interface Definition Language). Messenger creates a queue from all client requests within one thread, so the service simultaneously receives only one request. If you want the service to handle several requests at once, you can use AIDL directly.


Differences between linked and running services:


  1. The client component does not have a connection to the running service. It simply uses the Intent objects via startService() or stopService() , which are processed by the service in the onStartCommand() method.
  2. When a client component ( Activity , Fragment or other service) is connected to an IBinder service, they get an IBinder implementation, through which they can call methods on the associated service.

In any case, when a service (linked or running) needs to send messages to a linked client, it should use something like LocalBroadcastManager (if the client and the service are running in the same process). Linked services usually do not connect directly to the bound client component.



bindService () and onCreate ()


In order for the client component to become tied to the service, it is necessary to call bindService() with an explicit Intent , as is the case with the running service.


Example:


 public class MyActivity extends Activity{ void bind(){ bindService( new MyIntentBuilder(this).build(), mServiceConnection, BIND_AUTO_CREATE); } } 

BIND_AUTO_CREATE is the most frequently encountered flag when bindService() called. There are other flags (for example, BIND_DEBUG_UNBIND or BIND_NOT_FOREGROUND ). In the case of BIND_AUTO_CREATE , the BIND_AUTO_CREATE service is called onCreate() , if the service has not yet been created. In fact, this means that the service is created at the time of first binding to it.


As soon as bindService() is called, the service needs to respond to the client's request and provide it with an instance of IBinder , through which the client can call the methods of the bound service. In the example above, this is implemented using the mServiceConnection reference. This is the ServiceConnection callback that the associated service will use to notify the client that the binding has completed. It will also allow the client to know about the disconnection from the service.


In other words, the binding is asynchronous. bindService() returned immediately and does not return an IBinder object to the IBinder . To obtain an IBinder object IBinder client must create an instance of the ServiceConnection and pass it to the bindService() method. The ServiceConnection interface includes a callback method that the system uses to IBinder an IBinder object.


The following is an example of the ServiceConnection implementation:


 public class MyActivity extends Activity{ 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 = true; } public void onServiceDisconnected(ComponentName cName){ mServiceBound= false; } }; } 


Service binding


Let's see what happens on the side of the bindService(Intent) service when a client calls bindService(Intent) .


In an onBind() service, you must implement the onBind() method in onBind() for the client to get an IBinder instance. The 'onBind ()' method will be called only once, at the first binding of the client. For subsequent customers, the system will issue the same copy of IBinder :


 public class MyService extends Service{ public IBinder onBind(Intent intent){ if (mBinder == null){ mBinder = new MyBinder(); } return mBinder; } } 

The IBinder object provides a programming interface through which clients can interact with the service. As mentioned above, the easiest way to implement IBinder is an extension of the Binder class, an instance of which is returned from the onBind() method:


 public class 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 example above, we simply use the getService() method, which simply returns the Java object of the associated service to the client component. Referring to this IBinder instance, the client can call public methods on the IBinder service directly. Note that these methods are executed in the client thread. And in the case of Activity or Fragment these methods will be executed in the main thread. Therefore, it is worthwhile to be careful with methods in an anchored service that can block the stream or can cause ANR.



Unbinding from the service and calling onDestroy ()


, unbindService(mServiceConnection) . onUnbind() . , , , , , onDestroy .


unbindService() :


 public class MyActivity extends Activity{ protected void onStop(){ if (mServiceBound){ unbindService(mServiceConnection); mServiceBound = false; } } } 

, onStop() Activity unbindService() . UX onStart() onStop() , .


onUnbind() :


 public class MyService extends Service{ public boolean onUnbind(Intent i){ return false; } } 

false . , true , onBind() onRebind() .




, , . , . , .


, , , onCreate() . , . , , onDestroy() .


startService(), . , , . , , , bindService() . ''" , , , stopSelf() , , stopService() .




, , , , . , Android O:


 public class MyService extends Service{ private void commandStart() { if (!mServiceIsStarted) { moveToStartedState(); return; } if (mExecutor == null) { mTimeRunning_sec = 0; if (isPreAndroidO()) { HandleNotifications.PreO.createNotification(this); } else { HandleNotifications.O.createNotification(this); } mExecutor = Executors .newSingleThreadScheduledExecutor(); Runnable runnable = new Runnable() { @Override public void run() { recurringTask(); } }; mExecutor.scheduleWithFixedDelay( runnable, DELAY_INITIAL, DELAY_RECURRING, DELAY_UNIT); d(TAG, "commandStart: starting executor"); } else { d(TAG, "commandStart: do nothing"); } } @TargetApi(Build.VERSION_CODES.O) private void moveToStartedState() { Intent intent = new MyIntentBuilder(this) .setCommand(Command.START).build(); if (isPreAndroidO()) { Log.d(TAG, "moveToStartedState: on N/lower"); startService(intent); } else { Log.d(TAG, "moveToStartedState: on O"); startForegroundService(intent); } } @Override public int onStartCommand( Intent intent, int flags, int startId) { boolean containsCommand = MyIntentBuilder .containsCommand(intent); d(TAG, String.format( "Service in [%s] state. id: [%d]. startId: [%d]", mServiceIsStarted ? "STARTED" : "NOT STARTED", containsCommand ? MyIntentBuilder.getCommand(intent) : "N/A", startId)); mServiceIsStarted = true; routeIntentToCommand(intent); return START_NOT_STICKY; } private void routeIntentToCommand(Intent intent) { if (intent != null) { // process command if (containsCommand(intent)) { processCommand(MyIntentBuilder.getCommand(intent)); } // process message if (MyIntentBuilder.containsMessage(intent)) { processMessage(MyIntentBuilder.getMessage(intent)); } } } private void processMessage(String message) { try { d(TAG, String.format("doMessage: message from client: '%s'", message)); } catch (Exception e) { e(TAG, "processMessage: exception", e); } } private void processCommand(int command) { try { switch (command) { case Command.START: commandStart(); break; case Command.STOP: commandStop(); break; } } catch (Exception e) { e(TAG, "processCommand: exception", e); } } /*...*/ } 

:


  1. commandStart() , .
  2. commandStart() startService() startForegroundService() ( Android O).

, , .


, , commandStart() . . , , :


  1. , ( mServiceStarted false )
  2. moveToStarted() Intent Extras Command.START , startService() startForegroundService() .
  3. onStartCommand() , commandStart() .
  4. commandStart() mServiceIsStarted true , commandStart() , .. .



, onDestroy()


. "", ( stopService(Intent) startService(Intent) c Extras Intent , , Command.STOP ).


, :


image



Examples


, , GitHub .


Android O N, , .

Source: https://habr.com/ru/post/349102/


All Articles