Today we’ll look at the Service binding principle and fill in the holes I dug in the Service Startup Process.

BindService: bindService: bindService: bindService: bindService

Workflow of bindService

Take a look at the overall sequence diagram:

  1. The application makes a bindService call to AMS
  2. If Service is not started, start the Service
  3. If AMS does not hold a Binder handle to Service
    1. AMS first requests a Binder handle from the Service
    2. The Service publishes its own Binder handle to AMS upon receiving the request
  4. AMS will call back Binder handles to the application
  5. An application with a Binder handle can make a Binder call to a Service

Now that we understand the flow of bindService, let’s look at how the relevant lifecycle callbacks work in the bindService flow

ServiceConnection

The absolute protagonist of this article, bindService complete method is as follows:

public abstract boolean bindService(@RequiresPermission Intent service,
            @NonNull ServiceConnection conn, @BindServiceFlags int flags);
Copy the code

We need to pass in a non-empty ServiceConnection to listen for connection status when we bind the Service

public interface ServiceConnection {
    // When connecting, AMS will eventually call the Binder handle (IBinder) back and forth through this connection.
    void onServiceConnected(ComponentName name, IBinder service);
    void onServiceDisconnected(ComponentName name);
}
Copy the code

Follow the code call to bindService:

   Context.bindService(...)
-> ContextImpl.bindService(...)  
-> ContextImpl.bindServiceCommon(...)
  
private boolean bindServiceCommon(Intent service, ServiceConnection conn, ...) {
    // ServiceConnection is a normal interface that cannot be used for IPC
    IServiceConnection sd; // Encapsulate a Binder object and pass it to AMSsd = mPackageInfo.getServiceDispatcher(conn, ...) ;// Make a request to AMSActivityManager.getService().bindService(... , service, sd,...) }Copy the code

Let’s look at the relationship between IServiceConnection and ServiceConnection

// mPackageInfo.getServiceDispatcher : LoadApk.java
public final IServiceConnection getServiceDispatcher(ServiceConnection c, Context context,...) {
    ServiceDispatcher sd = null;
    // Get the ServiceDispatcher cache for the context
    ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
    if(map ! =null) {
        // Fetch the cache according to ServiceConnection
        // IServiceConnection corresponds to (context, ServiceConnection)
        // The same connection of the same context corresponds to the same IServiceConnection
        sd = map.get(c);
    }
    if (sd == null) {
        sd = new ServiceDispatcher(c, context, handler, flags);
        // Cache
        if (map == null) {
            map = new ArrayMap<>();
            mServices.put(context, map);
        }
        map.put(c, sd);
    }
    // Finally return IServiceConnection
    return sd.getIServiceConnection();
}

// sd.getIServiceConnection(); Here IServiceConnection is initialized in the sd constructor
ServiceDispatcher(ServiceConnection conn,...) {
    mIServiceConnection = new InnerConnection(this);
    mConnection = conn; // Holds a reference to ServiceConnection
}
private static class InnerConnection extends IServiceConnection.Stub {
    final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;

    InnerConnection(LoadedApk.ServiceDispatcher sd) {
        mDispatcher = new WeakReference<LoadedApk.ServiceDispatcher>(sd);
    }

    public void connected(ComponentName name, IBinder service, boolean dead) {
        LoadedApk.ServiceDispatcher sd = mDispatcher.get();
        if(sd ! =null) { // The AMS call ends up heresd.connected(name, service, dead); }}}Copy the code

In sd. Connected directly or indirectly invoked the ServiceDispatcher. DoConnected:

public void doConnected(ComponentName name, IBinder service, boolean dead) {
ServiceDispatcher.ConnectionInfo old;
ServiceDispatcher.ConnectionInfo info;
    // Fetch the old Service Binder object according to name
    old = mActiveConnections.get(name);
    if(old ! =null && old.binder == service) {
        // Huh, already have this one. Oh well!
        return;// Return to avoid repeating onServiceConnected calls
    }
    if(service ! =null) {
        // A new service is being connected... set it all up.
        info = new ConnectionInfo();
        info.binder = service;
        // Listen for the death of the service. If the service hangs, the onServiceDisconnected method will be called after receiving a callback
        info.deathMonitor = new DeathMonitor(name, service);
        service.linkToDeath(info.deathMonitor, 0);
        mActiveConnections.put(name, info);
    } else {
        // The named service is being disconnected... clean up.
        mActiveConnections.remove(name);
    }

    if(old ! =null) {
        old.binder.unlinkToDeath(old.deathMonitor, 0);
    }

    // If there was an old service, it is now disconnected.
    if(old ! =null) {
        // Service binders do not change. = null
        // The IBinder service normally passed in when this method is called is empty, triggering the disconnected callback
        mConnection.onServiceDisconnected(name);
    }
    // If there is a new viable service, it is now connected.
    if(service ! =null) { mConnection.onServiceConnected(name, service); }}Copy the code

In the previous section, we looked at the ServiceConnection callback, noting that onServiceDisconnected will only callback if the IBinder Service fails. OnServiceDisconnected will not be called back when we actively call unBind.

AMS bindService processing

Without further ado, let’s look at the code:

   ContextImpl.bindServiceCommon(...)
-> ActivityManager.getService().bindService(...)
-> AMS.bindService(...)  
-> ActiveServices.bindServiceLocked(...) 

int bindServiceLocked(IApplicationThread caller,...) {

    if((flags&Context.BIND_AUTO_CREATE) ! =0) { 
        // This method, as mentioned in the previous article, starts the Service when needed
        bringUpServiceLocked(s,...)
    }
    ServiceRecord s = res.record;
    AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
    if(s.app ! =null && b.intent.received) {
        // Service is already running, so we can immediately
        // publish the connection. (Service is ready, directly callback onServiceConnected)
        c.conn.connected(s.name, b.intent.binder, false); 

        // If this is the first app connected back to this binding,
        // and the service had previously asked to be told when
        // rebound, then do so.
        if (b.intent.apps.size() == 1 && b.intent.doRebind) {
            / / callback service. OnRebind ()
            requestServiceBindingLocked(s, b.intent, callerFg, true);
        } 
        // If doRebind is false or apps.size > 1, nothing is done
    } else if(! b.intent.requested) {// If AMS does not request a Binder object, it requests a Binder object from service
        requestServiceBindingLocked(s, b.intent, callerFg, false); }}Copy the code

AMS requests Binder objects from the Service

To get straight to the above method:

private final boolean requestServiceBindingLocked(ServiceRecord r, ... .boolean rebind) {
    if (r.app == null || r.app.thread == null) {
        // If service is not currently running, can't yet bind.
        return false;
    } 
    if((! i.requested || rebind) && i.apps.size() >0) {
        // Bind to Service (onBind/onRebind)r.app.thread.scheduleBindService(r, rebind, ...) ;if(! rebind) { i.requested =true;
        }
        /** hasBound: Set when we still need to tell the service all clients are unbound. */
        OnUnbind is called only after scheduleBindService is unbound
        i.hasBound = true; 
        i.doRebind = false;
    }
    return true;
}
Copy the code

The application of the Service receives a request:

// ActivityThread.java
private void handleBindService(BindServiceData data) {
    Service s = mServices.get(data.token);
    if(! data.rebind) {// Get Binder objects through Service and publish them to AMS
        IBinder binder = s.onBind(data.intent);
        ActivityManager.getService().publishService(
                data.token, data.intent, binder);
    } else {
        / / onRebind callbacks.onRebind(data.intent); }}Copy the code

Binder objects for Services are published to AMS

   ActivityManagerService.publishService()
-> ActiveServices.publishServiceLocked()

void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
    Intent.FilterComparison filter = new Intent.FilterComparison(intent);
    IntentBindRecord b = r.bindings.get(filter);
    if(b ! =null && !b.received) {
        b.binder = service;
        b.requested = true;// The tag requests a Binder object
        b.received = true;// The tag has received the Binder object
        for (int conni=r.connections.size()-1; conni>=0; conni--) {
            ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni);
            for (int i=0; i<clist.size(); i++) {
                ConnectionRecord c = clist.get(i);
                if(! filter.equals(c.binding.intent.intent)) {continue;
                }
                // Find all connection records that match the intent
                // Distribute Service binders to them so that the application receives the callback
                c.conn.connected(r.name, service, false); }}}}Copy the code

At this point, the complete bindService process and code details that we listed at the beginning have been combed out.

unBindService

Having said the process of binding, let’s add the process of unbinding:

// ActiveServices.java
boolean unbindServiceLocked(IServiceConnection connection) {
    IBinder binder = connection.asBinder();
    // Retrieve the list of IServiceConnection
    ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder);
    while (clist.size() > 0) { // Iterate, remove
        ConnectionRecord r = clist.get(0);
        removeConnectionLocked(r, null.null); // Remove operation
        if (clist.size() > 0 && clist.get(0) == r) {
            // In case it didn't get removed above, do it now.
            Slog.wtf(TAG, "Connection " + r + " not removed for binder " + binder);
            clist.remove(0);
        }
    return true;
}    
  
void removeConnectionLocked(ConnectionRecord c,...){...if(s.app ! =null&& s.app.thread ! =null && b.intent.apps.size() == 0
       && b.intent.hasBound) {
        // The onUnbind method does not need to be called in the future
        b.intent.hasBound = false;
        // The service is still there, and the process it belongs to is still there. No process binds the service with the intent
        b.intent.doRebind = false; // Call onRebind the next time the intent is bound
        // Tell the service to call back onUnbinds.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent()); }}Copy the code

Service calls onUnBind:

// ActivityThread.java   
public final void scheduleUnbindService(IBinder token, Intent intent) {
    BindServiceData s = new BindServiceData();
    s.token = token;
    s.intent = intent;
    // Send a message to the main thread to execute unbind
    sendMessage(H.UNBIND_SERVICE, s);
}

private void handleUnbindService(BindServiceData data) {
    Service s = mServices.get(data.token);
    boolean doRebind = s.onUnbind(data.intent);
    if (doRebind) {
        // If doRebind is true, AMS needs to be notified againActivityManager.getService().unbindFinished(doRebind); }}// ActiveServices.java
void unbindFinishedLocked(ServiceRecord r, Intent intent, boolean doRebind){
    Intent.FilterComparison filter = new Intent.FilterComparison(intent);
    IntentBindRecord b = r.bindings.get(filter);
    if (b.apps.size() > 0 && !inDestroying) {
         // Applications have already bound since the last
         // unbind, so just rebind right here.. }else {    
         // Note to tell the service the next time there is
         // a new client.
         b.doRebind = true; // The next time you bind a service with this intent, you will receive an onRebind callback}}Copy the code

Ok, so now we’re done with unBind, and we’ve also looked at how onRebind’s callback condition (doRebind = true) is assigned

Google official documentation error?

Through the above analysis, we can draw some conclusions:

  1. OnBind is called when the same intent requests a Binder object from a Service for the first time. The Binder object and the request state are cached in AMS
  2. OnRebind only calls onRebind when the same intent is used to initiate the binding
  3. After bindService, onUnbind will only be called if onBind/onRebind is called

Let’s take a look at a private message from a friend:

After our previous analysis, we should also know that the friend said exactly right.

The Service, rounding used in figure from Google document: developer.android.com/guide/compo… If you want to force a patch to understand it, it should look like this (ignoring the conflict between the scarlet enforced condition and the previous process node) :

We can also take a look at the picture provided by this student, what a talented treasure reader, remember to give a thumbs-up if you think the best drawing:

The last

After reading this article, you should be familiar with the overall flow of bindService and the related lifecycle calls.

This article source code based on Android – 28