preface

The previous blog post covered the basics of services, including what a Service is, how to create a Service, and how to start a Service once it is created. There are a few things in this post that need to be covered in the previous post, so I suggest you take a look at the previous post: Service in Android: Silent Devotee (1).

But there are a few things missing from the previous blog post — mainly the specifics of the bindService() section. Since there are still a lot of things involved in this section, it is presented separately here. Without further ado, let’s proceed to the main body.

The body of the

1, bindService ()

To recap a few points from my last post:

This is a more complex way to start a service than startService, and a service that starts in this way can do more things, such as send requests to it from other components, receive responses from it, even conduct IPC through it, and so on. We usually refer to the component to which it is bound as the client and call it the server. To create a service that supports bindings, we would have to override its onBind() method. This method returns an IBinder object, which is the interface the client uses to interact with the server. To get the IBinder interface, there are usually three ways: inherit Binder classes, use Messenger classes, and use AIDL.Copy the code

To complete the client-server binding, there are two things to do. One is to complete the call and configuration of bindService on the client side. The other is to rewrite the onBind() method on the server side and return an IBinder interface used for information interaction. Let’s take a look at how it’s implemented piece by piece.

1.1. Client Configuration

In principle, the client should just call the bindService() method, but it’s not that simple. The reason is the bindService() method. Let’s look at this method in detail:

public boolean bindService(Intent service, ServiceConnection conn, int flags) {
    return mBase.bindService(service, conn, flags);
}Copy the code

As you can see, the bindService() method takes three parameters. The first is an intent, familiar as the intent in startService(), that specifies which service to start and to pass some data to. The second parameter is a little bit unfamiliar. What is this? This is a key class for client-server communication. To implement this, two callback methods must be overridden: OnServiceConnected () and onServiceDisconnected(), which can be used to retrieve the IBinder object on the server for communication (more on this later). Here’s an example:

ServiceDemo mService;


ServiceDemo.BinderDemo mBinder;

private ServiceConnection mConnection = new ServiceConnection() {

    
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        
        mBinder = (ServiceDemo.BinderDemo) service;
        
        mService = mBinder.getService();
        
        Log.d(this.getClass().getSimpleName(), "onServiceConnected");
    }

    
    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.d(this.getClass().getSimpleName(), "onServiceDisconnected");
    }
};Copy the code

The above example implements a fairly common ServiceConnection. We can use it to get the object of the target service and then call its common methods to achieve the purpose of client-server interaction.

The third argument to the bindService() method is an int called flag. What does this flag do? It is a flag indicating binding options, which should normally be BIND_AUTO_CREATE to create services that have not yet been activated. Other possible values are BIND_DEBUG_UNBIND and BIND_NOT_FOREGROUND, or 0 (for none).

Ok, now that the client configuration is almost done, let’s look at what the server needs to do.

1.2. Server Configuration

To create a service that supports bindings, we would have to override its onBind() method. This method returns an IBinder object, which is the interface the client uses to interact with the server. To get the IBinder interface, there are usually three ways: inherit Binder classes, use Messenger classes, and use AIDL.Copy the code

As you can see, the concept of an IBinder interface is presented here. So what is this IBinder interface? It is something that is very important throughout the Android system and is a core part of a lightweight remote call mechanism designed for high performance. , of course, it not only can be used for a remote call, can also be used for in-process calls – in fact, we now call the service the IBinder of both possible remote invocation scenarios, such as to use it for the IPC, is also likely to be in-process call scenarios, such as use it to do with the in-process client and server interaction. I won’t go into details on how IBinder works here, but I’ll probably write a series of separate blogs about this area in the future. Just know that it’s the interface that clients use to interact with servers, and how they can interact with each other through IBinder.

In general, there are three ways to obtain IBinder objects: inherit Binder classes, use Messenger classes, and use AIDL. I’m going to expand on these three approaches.

1.2.1, inherit the Binder class

Some of you might say, what’s a Binder? I’m not going to give an exhaustive, definitive answer here – it’s too complicated, and digging into it will cause the post to lose its focus. All we need to know here is that it implements the IBinder interface, and by implementing the Binder class, our clients can directly invoke the public methods on the server side. In addition, Binder is a cross-process communication method in Android from an IPC point of view, but Binder in service is not used for interprocess communication, so it is relatively simple in this case.

Let’s take a look at client/server communication by inheriting Binder classes:

  • In the Service class, create a Binder instance that meets any of the following requirements:

    • Contains public methods that the client can call
    • Returns the current Service instance, which contains public methods that can be invoked by the client
    • Returns instances of other classes hosted by the current service, containing public methods that can be invoked by the client
  • Return this Binder instance in the onBind() method
  • The passed Binder instance is received on the client side through the onServiceDisconnected() method and subsequent operations are performed through the methods it provides

As you can see, a cast is required for client-server interaction using this method — a converted IBinder object is obtained in onServiceDisconnected(), We must convert it to the type of a Binder instance in the Service class to call its methods correctly. The cast implicitly requires that the client and server be in the same process! Otherwise there might be a problem with type conversions — is there a Binder instance in another process? Without it, the cast cannot be completed.

Here’s an official Google example:

public class LocalService extends Service { private final IBinder mBinder = new LocalBinder(); private final Random mGenerator = new Random(); /** * Class used for the client Binder. Because we know this service always * runs in the same process as its clients, we don't need to deal with IPC. */ public class LocalBinder extends Binder { LocalService getService() { return LocalService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } /** method for clients */ public int getRandomNumber() { return mGenerator.nextInt(100); }}Copy the code

LocalBinder provides clients with a getService() method to retrieve the current instance of LocalService. In this way, the client can invoke public methods in the service. For example, the client can call getRandomNumber() in the service:

public class BindingActivity extends Activity { LocalService mService; boolean mBound = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onStart() { super.onStart(); Intent intent = new Intent(this, LocalService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); if (mBound) { unbindService(mConnection); mBound = false; } } /** Called when a button is clicked (the button in the layout file attaches to * this method with the android:onClick attribute) */ public void onButtonClick(View v) { if (mBound) { int num = mService.getRandomNumber(); Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show(); } } /** Defines callbacks for service binding, passed to bindService() */ private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { LocalBinder binder = (LocalBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; }}; }Copy the code

When the Activity enters the onStart() state, it tries to bind to the target service, and when the button is clicked, the method getRandomNumber() in the service is called and printed if the binding is complete.

That’s pretty much it for in-thread communication, nothing complicated. Let’s look at how a service started this way does IPC.

1.2.2, Using Messenger

AIDL(Android Interface Definition Language) is one of the first things that comes to mind when we talk about cross-process communication. In fact, using Messenger is a lot easier than using AIDL in many cases.

It’s easy to think of Messenger as Message, and it’s easy to think of Handler — yes, Messenger is all about messages and handlers to communicate between threads. Here are the steps to implement IPC in this way:

  • The server implements a Handler that accepts callbacks from each call made by the client
  • Create the Messenger object using the implemented Handler
  • Get an IBinder object through Messenger and return it to the client through onBind()
  • The client instantiates Messenger (the Handler that references the service) using IBinder, which then sends the Message object to the service
  • The server receives each Message in its Handler (specifically, the handleMessage() method)

In this way, instead of calling server methods directly as with Binder classes, clients interact with each other by passing messages. Here’s a simple example:


public class MessengerServiceDemo extends Service {

    static final int MSG_SAY_HELLO = 1;

    class ServiceHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    
                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    final Messenger mMessenger = new Messenger(new ServiceHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        
        return mMessenger.getBinder();
    }
}Copy the code

The server basically returns an IBinder instance to the client to construct Messenger and process messages sent by the client. And of course, don’t forget to register in your Sse documentation:


    
        
        
    
Copy the code

As you can see, the registered service is a little different from the original one, mainly because we are communicating across processes here, so there is no instance of our service in the other process, so we must flag other processes so that other processes can find our service. The android:exported attribute does not need to be set. The default android:exported attribute is true if an intent-filter is specified. Silent Devotee (1).

Let’s look at what the client should do:

public class ActivityMessenger extends Activity { static final int MSG_SAY_HELLO = 1; Messenger mService = null; boolean mBound; private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { mService = new Messenger(service); mBound = true; } public void onServiceDisconnected(ComponentName className) { mService = null; mBound = false; }}; public void sayHello(View v) { if (! mBound) return; Message msg = Message.obtain(null, MSG_SAY_HELLO, 0, 0); try { mService.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override protected void onStart() { super.onStart(); Intent intent = new Intent(); intent.setAction("com.lypeer.messenger"); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); if (mBound) { unbindService(mConnection); mBound = false; }}}Copy the code

The client basically initiates the binding to the server and constructs Messenger with the IBinder returned from the server via onServiceConnected() method so that it can communicate with the server by sending messages. The above example is not complete because it only communicates unilaterally from the client to the server, and the server has no ability to send messages to the client – which is obviously unreasonable. This can be done simply by creating an instance of a Handler that receives information from the server and sends a message to the client when the request is completed.

For IPC using Messenger, the overall process is very clear. Message acts as a Messenger, through which the client and the server can communicate with each other.

1.2.3, via AIDL

AIDL stands for Android Interface Definition Language. It is an IDL language that can be used to generate code for IPC. In my opinion, it is a template. Why do you say that? In our case, what actually matters is not the AIDL code we wrote, but the code from an IInterface instance generated by the system. If you generate a few more of these instances and compare them, you’ll see that they all follow a pattern — the same flow, the same structure, with slight variations depending on the specific AIDL file. So AIDL is a template for avoiding the need to write the same code over and over again.

So how do you use AIDL to communicate between threads via bindService()? There are basically the following steps:

  • The server creates an AIDL file in which to declare the interface exposed to the client
  • Implement these interfaces in a service
  • The client binds to the server and converts the IBinder from onServiceConnected() into an AIDL-generated IInterface instance
  • The exposed method is invoked through the resulting instance

The above description is quite abstract, basically the type of person who doesn’t know what to do with it – it would be a long story if I had to expand on it. With that in mind, this is just a brief introduction to AIDL, its syntax, and how to implement IPC. I will write a separate blog about this.

1.2.4, Comparison between Messenger and AIDL

For one thing, Messenger is certainly much easier to implement — at least without writing an AIDL file (though if you dig deep, the underlying implementation is still AIDL). Another significant benefit of using Messenger is that it queues all requests, so you almost never have to worry about multithreading.

But then, is the AIDL IPC useless? Of course not. In that case it would have been obsolete. For one thing, if there is a need for concurrent processing in a project, or if there are a large number of concurrent requests, Messenger doesn’t work — its nature means that it can only handle requests serially. In addition, when using Messenger, we can only communicate through Message, but what if we need to call server-side methods directly across processes? Only AIDL can be used.

Therefore, each of these two IPC methods has its own advantages and disadvantages, and the specific use of which depends on the specific needs – of course, use as simple as possible.

1.2.5, Service lifecycle

When all bindings between the service and all clients are unbound, the Android system destroys the service (unless it is also started with onStartCommand()). Therefore, if a service is purely a bound service, in principle we don’t need to manage its life cycle – The Android system manages it for us depending on whether it is bound to any client. In practice, however, we should always unbind when the interaction with the service is complete or when the Activity is paused, so that the service can be shut down when it is not occupied.

If you only need to interact with the service while the Activity is visible, you can bind it during onStart() and unbind it during onStop(). If you want your Activity to receive responses when it is stopped in the background, you can bind it during onCreate() and unbind it during onDestroy(). Note, however, that this means that your Activity needs to use the service throughout its entire run (even during background runs), so if the service is in another process, it’s more likely that the system will terminate that process when you increase the weight of that process. In general, do not bind and unbind during the Activity’s onResume() and onPause() phases, because these callbacks occur during each lifecycle transition, and we should keep processing that occurs during these transitions to a minimum. In addition, if multiple activities within our application are bound to the same service, and there is a transition between two of them, the system may destroy the service and rebuild it if the current Activity is unbound (during pause) before the next binding (during recovery).

In addition, if our service is started and binding is accepted, we have the option to return true when the system calls the onUnbind() method if we want to receive the onRebind() call instead of the onBind() call the next time the client binds to the service. OnRebind () returns a null value, but the client still receives IBinder in its onServiceConnected() callback. The following diagram illustrates the logic of this lifecycle:

2. When to use startService and when to use bindService?

This can be easily concluded by their characteristics: the main differences between them are actually in two aspects, whether they can interact with each other, and the life cycle. So startService is obviously suitable for a service that runs in the background forever without explicitly stopping it after it starts, and does not require the client to interact with the server. For example, if a service is used to save data to a local database, it will wait in the background for another component, startService, to save the data to the database, which is obviously what startService does. BindService, on the other hand, is something that you can interact with, you can control when it stops and when it starts. In addition, if there is a need for IPC, then of course bindService is essential.

As we explained in our last blog post, in most cases startService and bindService are complementary and not isolated. For example, if I want to make a music player at this time, then background playing is definitely necessary, right? You can’t just turn off your phone and lose your music. Also, controlling the music is necessary, right? Blah, blah, blah, blah, blah, blah. Isn’t that a union? Note, of course, that the life cycle of a service changes when both methods are used to start it. The service must be stopped from both perspectives. Attached is a picture:

conclusion

That’s pretty much the end of my blog on “Services in Android.” These two articles are basically a fairly complete introduction to all aspects of service, but that’s about it — some of the points are too deep to cover, and it’s easy to lose focus, as I’ve learned from reviewing some of my previous posts. Some previous articles, especially source code analysis, I like to get to the bottom of the source, so will have been digging down along the source code, until digging. In this case, I personally can certainly get a lot of gain, but after writing a blog may seem relatively obscure, because I have to follow my thinking, follow me all the way down to understand what I mean – and a good blog should be concise, can let the viewer easily gain.

IBinder, Binder, Messenger, AIDL, IPC, and many more will be covered in a series of articles, including source code parsing.

Thanks again to Google official documentation, thanks to hongyang Brother and Guo Shen and other gods – some of this article references some of their articles.