Photo: pixabay.com
Is your brand new Xiaomi quickly discharged for no apparent reason? This is because some applications can run in the background, consuming not only energy, but also traffic. One such program is Xiaomi Service Framework. You may have seen this app on your phone, but you've definitely never downloaded it yourself. Why it is even needed and whether it can be removed without consequences for the smartphone, Xiaomi keeps it almost a secret from users. But we'll figure it out now.
Why Xiaomi Service Framework eats up traffic and your smartphone's battery
The Xiaomi Service Framework application is designed to synchronize and deliver alerts from gadgets “tied” to the phone, such as smart watches. This is a system application, and it constantly communicates with the Mi Cloud, as a result of which it consumes Internet traffic and drains battery power even in the background.
Finding out how much traffic a program actually spends is very simple:
1. Go to the “Settings” of your smartphone to the “All applications” section.
2. Find Xiaomi Service Framework in the list and click on it.
3. Look in the application information that opens to see how much traffic was spent.
To understand how significantly Xiaomi Service Framework drains the battery, you need to open the settings again:
1. In “Settings”, go to the “Security” category.
2. Find the “Traffic consumption” tab.
3. Open System Applications and find Service Framework in the list.
Here you will see exactly how much charge the system program used and for how long.
Disable smart card-only authentication
If you manually manage the profiles that are installed on the computer, you can remove the smart card-only profile in two ways. You can use the Profiles pane of System Preferences, or you can use the /usr/bin/profiles command-line tool. For more information, open Terminal and enter man profiles .
If your client computers are enrolled in Mobile Device Management (MDM), you can restore password-based authentication. To do this, remove the smart card configuration profile that enables the smart card-only restriction from the client computers.
To prevent users from being locked out of their account, remove the enforceSmartCard profile before you unpair a smart card or disable attribute matching. If a user is locked out of their account, remove the configuration profile to fix the issue.
If you apply the smart card-only policy before you enable smart card-only authentication, a user can get locked out of their computer. To fix this issue, remove the smart card-only policy:
- Turn on your Mac, then immediately press and hold Command-R to start up from macOS Recovery. Release the keys when you see the Apple logo, a spinning globe, or a prompt for a firmware password.
- Select Disk Utility from the Utilities window, then click Continue.
- From the Disk Utility sidebar, select the volume that you're using, then choose File > Mount from the menu bar. (If the volume is already mounted, this option is dimmed.) Then enter your administrator password when prompted.
- Quit Disk Utility.
- Choose Terminal from the Utilities menu in the menu bar.
- Delete the Configuration Profile Repository. To do this, open Terminal and enter the following commands. In these commands, replace with the name of the macOS volume where the profile settings were installed. rm /Volumes/ /var/db/ConfigurationProfiles/MDM_ComputerPrefs.plist rm /Volumes/ /var/db/ConfigurationProfiles/.profilesAreInstalled rm /Volumes/ /var/db/ConfigurationProfiles/Settings/.profilesAreInstalled rm /Volumes/ /var/db /ConfigurationProfiles/Store/ConfigProfiles.binary rm /Volumes/ /var/db/ConfigurationProfiles/Setup/.profileSetupDone
- When done, choose Apple () menu > Restart.
- Reinstall all the configuration profiles that existed before you enabled smart card-only authentication.
How to disable Service Framework in Xiaomi smartphone
It is not recommended to remove the application, since it is a system application, but you can simply disable it. Feel free to do this if you do not use Mi Cloud features. There are two ways to disable Service Framework: without using root rights or with them.
Disabling Service Framework without root rights
1. In “Settings” you need the “Battery and Performance” section.
2. Find the item “Power” or “Battery consumption” (depending on the software version).
3. Click on “Enable” five times.
4. The “Maximum” mode will appear; it must be activated.
5. Select the Xiaomi Service Framework application and activate the “Limit background activity” option.
Important detail: this method will work if the Xiaomi smartphone is not updated to MIUI 10. Otherwise, you can disable the Xiaomi Service Framework only with root rights.
Disabling Service Framework without root rights (look at how much obscene amount of energy this system application sometimes consumes!)
Disable with root rights
1. In the “Settings” of your smartphone, find the “About phone” section.
2. Press briefly on the line with the MIUI system version until a notification about obtaining root rights appears.
3. Now find the “For Developers” item in the same section.
4. Enable USB Debugging.
Disabling Service Framework with root rights using TWRP and Root Uninstaller applications
For further actions, you need two applications: TWRP (Team Win Recovery Project) and Root Uninstaller:
1. Install Team Win Recovery Project.
2. Go back to the “About phone” section in the settings.
3. Find “Firmware update” and click on “…”.
4. Reboot into Recovery.
5. Install the Root Uninstaller application.
6. Launch it, allow access to root rights.
Next, all that remains is to find the Xiaomi Service Framework in the list of applications and “freeze” the hated utility.
Enable smart card-only login
Make sure that you carefully follow these steps to ensure that users will be able to log in to the computer.
- Pair a smart card to an admin user account or configure Attribute Matching.
- If you've enabled strict certificate checks, install any root certificates or intermediates that are required.
- Confirm that you can log in to an administrator account using a smart card.
- Install a smart-card configuration profile that includes » enforceSmartCard
For more information about smart card payload settings, see the Apple Configuration Profile Reference.
For more information about using smart card services, see the macOS Deployment Guide or open Terminal and enter man SmartCardServices .
How else can you reduce the traffic consumption of a Service Framework application?
If you don’t want to disable the application, you can simply set a traffic limit for it:
1. Find the “Data Transfer” section in “Settings”. In some versions of the software it is called “Internet”.
2. Open "Traffic Control".
3. Click on “Tariff plan” and then go to “Traffic limit”.
4. Determine the traffic limit for the application.
As soon as the program uses up the allocated megabytes, Internet access for it will be automatically limited.
Configure Secure Shell Daemon (SSHD) to support smart card-only authentication
Users can use their smart card to authenticate over SSH to the local computer or to remote computers that are correctly configured. Follow these steps to configure SSHD on a computer so that it supports smart card authentication.
Update the /etc/ssh/sshd_config file:
- Use the following command to back up the sshd_config file: sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config_backup_`date "+%Y-%m-%d_%H:%M"`
- In the sshd_config file, change "#ChallengeResponseAuthentication yes" to "ChallengeResponseAuthentication no" and change "#PasswordAuthentication yes" to "#PasswordAuthentication no."
Then, use the following commands to restart SSHD:
sudo launchctl stop com.openssh.sshd
sudo launchctl start com.openssh.sshd
If a user wants to authenticate SSH sessions using a smart card, have them follow these steps:
- Use the following command to export the public key from their smart card: ssh-keygen -D /usr/lib/ssh-keychain.dylib
- Add the public key from the previous step to the
/.ssh/authorized_keys file on the target computer.
- Use the following command to back up the ssh_config file: sudo cp /etc/ssh/ssh_config /etc/ssh/ssh_config_backup_`date "+%Y-%m-%d_%H:%M"`
- In the /etc/ssh/ssh_config file, add the line "PKCS11Provider=/usr/lib/ssh-keychain.dylib."
If the user wants to, they can also use the following command to add the private key to their ssh-agent:
ssh-add -s /usr/lib/ssh-keychain.dylib
Optimizing the Xiaomi Service Framework
If synchronization is important to you, then you can simply limit the “field of activity” of the Xiaomi Service Framework so that the application works, but does not consume a lot of Internet traffic and battery power. This is easy to do:
1. In “Settings”, open the “Advanced” or “Advanced” item (the names differ in different versions of the software).
2. Select "Privacy".
3. Go to the “Notification Access” tab.
4. Find Xiaomi Service Framework in the list and disable it.
5. Go back to your privacy settings and select “Apps with data access.”
6. Also find Service Framework and limit its activity.
Now Xiaomi Service Framework will not “read” all messages, service notifications and your personal data.
By making a few taps on the screen, you will greatly limit the work of the Xiaomi Service Framework, while leaving it enabled
Conditions and implementation of the attack
To send malicious OMA CP messages, an attacker only needs to have a GSM modem and be within reach of the victim/victims. In this case, both targeted attacks and broadcast requests to change settings are possible.
NETWPIN authentication
With rare exceptions (discussed below about Samsung), messages from the operator are authenticated by providing the device with its own IMSI (Mobile Subscriber Identity, a unique 64-bit device identifier, similar to an IP address on “these Internets of yours”).
How difficult it is to “get” IMSI is a separate question, but there are at least the following methods:
- a malicious application on the device (the permission in the permission.READ_PHONE_STATE manifest is sufficient);
- view the victim's SIM card;
- using ISMI-catcher that imitates mobile towers, which will require certain investments, but is quite possible.
Upon receiving a CP message, the user is not given any information about the sender, and the legitimacy decision is purely up to the victim.
USERPIN authentication
Even if the attacker does not have ISMI, the following attack vector can be carried out:
- sending a message on behalf of the operator with a request to apply the settings;
- automatic request of the user for a PIN code by the system;
- sending a CP message with settings, protected by a PIN code specified by the user.
Dive into Android Services
Translation of the article “Deep Dive into Android Services” by Nazmul Idris. I left the original title of the author, although it is more likely not “immersion”, but “acquaintance”. I think the text will be useful for novice developers. The article perfectly complements the off. Android services documentation. The article examines the features of interaction with running and bound services. The advantage of the article is that it takes into account changes in working with services in Android O. The translation contains minor changes compared to the original, added for greater clarity.
Introduction
Most modern Android applications perform some tasks in the background. This means that tasks are executed on a background thread rather than on the user interface thread (UI thread).
If you create a Thread or Executor in a specific Activity of your application, this can lead to unpredictable results. For example, during a simple change of screen orientation, your Activity is created anew and the threads attached to the old Activity have nowhere to return the result.
To handle this you could use an AsyncTask. But what if your application needs to run this background thread not only from an Activity, but also from a notification or another component?
In this case, the service is a suitable Android component that will bind the thread's lifecycle to its own lifecycle, and thus not lose it.
A service is a component of an Android application without a visible interface that runs on the application's main thread. The service must be declared in the manifest. If you need the service to run on a background thread, you must implement this yourself.
The terms background and foreground are overloaded, and can be applied to:
- life cycle of Android components
- flows
In this article, by default we will assume that the terms background and foreground refer to the life cycle. But when we talk about threads, we will explicitly say background thread or foreground thread.
There is a subclass of android services called IntentService that runs tasks on a background thread out of the box. But we will not talk about such services in this article.
Threads, Services, and the Lifecycle of Android Components
Let's take a step back and look at the bigger picture of what services should do. Your code that runs on a background thread, such as a Thread or Executor, is not actually associated with the lifecycle of any Android component. If we talk about an Activity, then it has a specific starting and stopping point based on user interaction. However, these Activity start and end points are not necessarily associated with the Thread or Executor lifecycle.
Below are explanations of the main timing points of this Gantt chart. Details of these points (and explanations for them) are given in the rest of the article.
The service's onCreate() method is called when it is created (either by running or being bound to it).
Then, after some time, the service starts a Thread or Executor. When a Thread exits, it lets the service know so it can call the stopSelf() method. This is a fairly common service implementation pattern.
The code you write in your Thread or Executor must tell the service to start or stop a background thread.
- When a thread starts running, it must set the initial state of the service by calling startService()
- When a thread exits, it must call stopSelf() on the service.
The service's onDestroy() method is called by the system only when you have told 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 responsibility. Thus, the programmer’s task is to inform the service about the start and completion of work.
Services are divided into two types: running and bound. Alternatively, the service may be running and allow binding. We will look at each of the cases:
- Started service
- Bound Service
- Bound and running service at the same time
Changes in Android O
In Android O (API 26), there have been significant changes to how the system regulates background services. One of the main changes is that a running service that is not in the white list (services whose work is visible to the user are placed in the white list; see the official documentation for more details) or that does not explicitly inform the user about its work will not run in the background thread after closing the Activity. In other words, you must create a notification to which you attach the running service. And you should start the service using the new startForegroundService() method (not using startService()). And, after creating the service, you have five seconds to call the startForeground() method of the running service and show a user-visible notification. Otherwise, the system stops the service and displays ANR (“application not responding”). Below we explain these points 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 must be explicit. This means that you must explicitly specify the class name of the service you are starting in the Intent. Or, if it's important to you to allow some ambiguity about which service is being started, you could provide intent filters for your services and exclude the bean name from the Intent, but then you should set the package to the intent using setPackage(), which provides sufficient disambiguation for the target service. Below we provide 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; } }
To get a service started, you must call startService() with an explicit intent. If you don't do this, then the service will not go into a running state. And thus it won't be able to come to the foreground, and stopSelf() won't actually accomplish anything.
So, unless you put the service in a running state, you won't be able to attach it to a notification. These are pretty important things that you should keep in mind when you need to put a service into a running state.
The service can be started multiple times. Every time it starts, onStartCommand() is called. This method is passed several parameters along with an explicit Intent. Even if you start a service multiple times, it only calls onCreate() once (unless the service has already been bound before, of course). To shut down, the service must call stopSelf(). After the service has been stopped (when you stop it), and if there is nothing else associated with it, onDestroy() is called. Keep this in mind when allocating resources to your running service.
Intent
To start a running service, an Intent is required. The Android component that starts the service doesn't actually keep a connection to it, and if it needs to tell the running service something, it can start it again using a different Intent. This is the main difference between a running and a bound service. Bound services, for their part, implement the client-server pattern. Where the client (Android UI component or other service) stores the connection and can call methods directly from 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 Android O has changed a lot in terms of running services. They can no longer run long enough in the background without a persistent notification mechanism. And the method for starting a running service in the background in Android O is startForegroundService(Intent).
Foreground and persistent notification mechanism
A running service can run in the foreground. Again, the term foreground does not refer to whether the service is running on a background thread or on 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 removal by the system if it runs out of memory. You should only put a service in the foreground if it is truly necessary to create a modern and responsive application.
Examples of use of foreground services:
- Applications that play media files in the background.
- Apps that update location data in the background.
When a running service is brought to 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 separate from the lifecycle of the UI components (except, of course, for the persistent notification itself). And there is no other way to inform the user that something is running on their phone (and potentially consuming a lot of resources) other than to display a persistent notification in 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 for creating a persistent notification in versions
prior to 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.BigTextS style() ) .build(); context.startForeground(ONGOING_NOTIFICATION_ID, mNotification); } }
in Android O, via 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; } }
Also, here's another article that goes into more detail about creating notifications in MediaStyle (since background playback of audio files requires both notifications and services bound and running)
Stopping running services
Note that the piStopService parameter of type PendingIntent (which is passed to the notification constructor) actually passes an Intent with a Command.STOP constant of type Integer. Remember that startService(Intent) can be called multiple times? This is an example of such behavior. To stop a service, we launch an 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 must be able to handle Intents. Using this mechanism we can "tell" the service to stop running. Below is code that illustrates these capabilities:
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 terminate a running service in the foreground, you must call stopForeground(true). This method will also terminate the persistent notification. However, this will not stop the service itself. To do this, call stopSelf().
To stop the service you can do one of the following:
- 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 there are no other components bound to the service, this will call onDestroy() and the service will terminate.
- 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() in the same way as step 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 your running service is in the foreground):
public class MyService extends Service{ private void stopCommand(){ stopForeground(true); stopSelf(); } }
Bound Services
Unlike running services, bound services allow a connection to be established between the Android component bound to the service and the service. This connection is provided by an implementation of the IBinder interface, which defines methods for interacting with the service. A simple example of this would be to implement a bound service in the same process as the client (i.e. within your own application). In this case, a Java object, a subclass of Binder, is passed to the client, which can use it to call methods on the service.
In more complex scenarios where you want the service interface to be accessible to different processes, you can use a Messenger object (which is a reference to a Handler object that receives a callback for each call from the client) to expose the service interface to the client so that the service can be interacted with using Message objects. The Messenger object is actually based on AIDL (Android Interface Definition Language). Messenger queues all client requests within a single thread, so the service only receives one request at a time. If you need a service to process multiple requests at once, you can use AIDL directly.
Differences between bound and running services:
- The client component does not have a connection to the running service. It simply uses Intent objects via startService() or stopService(), which are handled by the service in the onStartCommand() method.
- When a client component (Activity, Fragment, or other service) connects to a bound service, they receive an IBinder implementation through which they can call methods on the bound service.
In any case, when a service (bound or running) needs to send messages to a bound client, it should use something like LocalBroadcastManager (in case the client and service are running in the same process). Bound services typically do not connect directly to the bound client bean.
bindService() and onCreate()
In order for a client bean to become bound to a service, you must call bindService() with an explicit Intent, just as you would with a 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 common flag when calling bindService(). There are other flags (such as BIND_DEBUG_UNBIND or BIND_NOT_FOREGROUND). In the case of BIND_AUTO_CREATE, onCreate() is called on the bound service if the service has not yet been created. This essentially means that the service is created the first time you bind to it.
Once bindService() is called, the service needs to respond to the client's request and provide it with an IBinder instance through which the client can call methods on the bound service. In the example above, this is implemented using the mServiceConnection link. This is the ServiceConnection callback that the bound service will use to notify the client that the binding has completed. It will also let the client know that the connection to the service has been lost.
In other words, the binding is done asynchronously. bindService() returns immediately and does not return an IBinder object to the client. To obtain an IBinder object, the client must instantiate a ServiceConnection and pass it to the bindService() method. The ServiceConnection interface includes a callback method that the system uses to issue an IBinder object.
Below is an example implementation of ServiceConnection:
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 look at what happens on the bound service side when the client calls bindService(Intent).
In the bound service, you must implement the onBind() method for the client to obtain the IBinder instance. The 'onBind()' method will only be called once, the first time the client is bound. For subsequent clients, the system will issue the same IBinder instance:
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 discussed above, the simplest way to implement IBinder is to extend 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 bound service to the client bean. By referencing this IBinder instance, the client can call public methods on the bound service directly. Note that these methods run on the client thread. And in case of Activity or Fragment, these methods will be executed on the main thread. Therefore, you should be careful with methods in a bound service that may block a thread or may cause an ANR.
Unbinding from the service and calling onDestroy()
To unbind a bound service, the client simply calls unbindService(mServiceConnection). The system will then call onUnbind() on the service itself. And, if the bound service no longer has clients, and also if the service is not a running service, then the system calls onDestroy.
Here's what the unbindService() call looks like in the client component:
public class MyActivity extends Activity{ protected void onStop(){ if (mServiceBound){ unbindService(mServiceConnection); mServiceBound = false; } } }
In the code above, the onStop() method in the Activity is overridden to call unbindService(). Depending on the UX requirements of the application, your client bean can bind and unbind to the service in the onStart() and onStop() methods respectively, or in any other client bean lifecycle methods of your choice.
Here's an example of what onUnbind() might look like in the bound service code:
public class MyService extends Service{ public boolean onUnbind(Intent i){ return false; } }
Typically you will return false. But, if you return true, then when binding the next client to the service, the onBind() method will be called instead of onBind().
Bound and running services at the same time
There are situations when you may need services that are running and can still be bound. In the previous sections, we showed the operating features of each type of service. And from these features it can be understood that the creation of bound and running services is simultaneously necessary to implement special behavior at the moment of starting to work with the service and when finishing working with it.
If the service is not running, then a client that wants to bind to it will call onCreate() on the service. If the service is already running, this method is not called. On the other hand, if the client disconnects from the service and the service is not running, then onDestroy() is called and the service is destroyed.
You can start a service by calling the startService() method, bring it to the foreground, and show a persistent notification. This way you'll implement everything we talked about about creating running services. But you can also implement methods that allow clients to bind to a service by calling the bindService() method. The peculiarity of such a “double” service is that even if all clients are disconnected, the service continues its work and runs until it stops itself using the stopSelf() method, or until another component calls the stopService method ().
Transition to running state
Since a client, when binding to a service, will not put it into a running state, for services that are bound and running at the same time, it is required that the service transition to the running state on its own. Here's how to do it with Android O in mind:
a lot of code
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); } } /*…*/ }
In the code under the spoiler:
- The commandStart() method can be called by the client that binds to the service.
- Or commandStart() is called via the startService() or startForegroundService() methods (for Android O).
But, before actually doing the work, the service first puts itself into a running state.
So, when the client binds to the service, commandStart() is called. The service has not yet started. Let's look at the code and see what happens:
- If the service is bound to the client, then it is not started (and mServiceStarted contains false)
- In this case, moveToStarted() is called and an explicit Intent with Extras Command.START is created there, and then startService() or startForegroundService() is called.
- This causes onStartCommand() to be called, which calls commandStart() again.
- But now in commandStart() the value of the mServiceIsStarted variable is true, and therefore the commandStart() method fulfills its intended purpose, i.e. starts useful service work.
Terminating a service and unbinding it
If the service is not running and the client component is unlinked from the service, then the service is destroyed and onDestroy() is called
But if it is in a neglected state, it is not destroyed. And it will only be "killed" if the running state is stopped (via a call to stopService(Intent) or a call to startService(Intent) with Extras in the Intent that indicate the intent to stop the service, such as Command.STOP).
Here's a diagram that summarizes service states and transitions between them for a running and bound service at the same time:
Examples
The implementation of most of what was discussed in the article can be found on GitHub.
This is a small utility for Android O and N that keeps the phone active when it is charging.