StartService can only be used to start a Service, but we cannot establish a connection with it. If you start a Service in bindService mode, you can not only start the Service, but also establish a connection with it, which is convenient to call each other. Today we’ll take a look at how bindService works.

I suggest reading the following two articles first. I wrote them in order, step by step. Maybe some things have been introduced before, and I will not repeat them again. Thank you for your understanding.

  • Android_App startup process (including Activity startup process)
  • Android_Service startup process analysis

1. Usage

Just to recap, call the bindService method in your Activity and pass in a ServiceConnection.

Intent intentService = new Intent(this,MyService.class);
bindService(intentService,mServiceConnection,BIND_AUTO_CREATE);
Copy the code

2. Source code analysis

Like startService,bindService is a method within the ContextImpl call.

@Override
public boolean bindService(Intent service, ServiceConnection conn,
        int flags) {...return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), getUser());
}

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler
        handler, UserHandle user) {
    
    IServiceConnection sd;
    
    // Note that ServiceConnection cannot pass null, otherwise it will throw an exception to you
    if (conn == null) {
        throw new IllegalArgumentException("connection is null");
    }
    if(mPackageInfo ! =null) {
        // Will walk here Generate a IServiceConnection, it is actually a ServiceDispatcher InnerConnection, used to establish the connection with the Service, establish the connection with the Service may need to remote invocation, so ServiceConnectio N is not going to work. Requires ServiceDispatcher InnerConnection to remote calls, then notify the ServiceConnection.
        sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags);
    } else {
        throw new RuntimeException("Not supported in system context");
    }
    
    validateServiceIntent(service);
    try {
        // Get the token of the current Activity
        IBinder token = getActivityToken();
        if (token == null && (flags&BIND_AUTO_CREATE) == 0&& mPackageInfo ! =null
                && mPackageInfo.getApplicationInfo().targetSdkVersion
                < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            flags |= BIND_WAIVE_PRIORITY;
        }
        service.prepareToLeaveProcess(this);
        
        // The AMS bindService also needs AMS
        int res = ActivityManager.getService().bindService(
            mMainThread.getApplicationThread(), getActivityToken(), service,
            service.resolveTypeIfNeeded(getContentResolver()),
            sd, flags, getOpPackageName(), user.getIdentifier());
        if (res < 0) {
            throw new SecurityException(
                    "Not allowed to bind to service " + service);
        }
        returnres ! =0;
    } catch (RemoteException e) {
        throwe.rethrowFromSystemServer(); }}Copy the code

BindService is the ContextImpl method that eventually calls the bindServiceCommon method. The bindServiceCommon method first converts the ServiceConnection to a ServiceDispatcher.in NerConnection (aka IServiceConnection SD above). We know that binding services can be cross-process, so cross-process communication is required, using a Binder approach. IServiceConnection here is actually a ServiceDispatcher InnerConnection, where ServiceDispatcher. InnerConnection is cross-process communication, because ServiceCon Nection cannot directly to communicate across processes, so need ServiceDispatcher. InnerConnection to act as the role of the Binder. ServiceDispatcher then connects InnerConnection, which is an internal class of ServiceDispatcher, to ServiceConnection.

The mPackageInfo above is LoadedApk, so follow along and look at the getServiceDispatcher method

public final IServiceConnection getServiceDispatcher(ServiceConnection c,
        Context context, Handler handler, int flags) {
    synchronized (mServices) {
        ServiceDispatcher is an inner class of LoadedApk
        LoadedApk.ServiceDispatcher sd = null;
        
        / / mServices is a map, is defined as a private final ArrayMap < Context, ArrayMap < ServiceConnection, LoadedApk ServiceDispatcher > > mServices
        = new ArrayMap<>();
        // It stores the mapping between the current Activity and ServiceConnection and ServiceDispatcher
        // If ServiceDispatcher has been created before, use it. If not, create one
        ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
        if(map ! =null) {
            sd = map.get(c);
        }
        if (sd == null) {
            // Create a ServiceDispatcher
            sd = new ServiceDispatcher(c, context, handler, flags);
            if (map == null) {
                map = new ArrayMap<>();
                // Cache it in mServices
                mServices.put(context, map);
            }
            map.put(c, sd);
        } else {
            sd.validate(context, handler);
        }
        
        // Get the InnerConnection object in ServiceDispatcher
        returnsd.getIServiceConnection(); }}Copy the code

First, ServiceDispatcher is an inner class of LoadedApk. The system caches the mapping between the current Activity and ServiceConnection and ServiceDispatcher. The current binding will not be cached the first time, so you need to create a ServiceDispatcher. The InnerConnection is created when ServiceDispatcher is created. ServiceDispatcher contains ServiceConnection and InnerConnection, so it is convenient to call the methods in ServiceConnection.

Then we continue to look at the process of AMS binding Service, which calls AMS bindService method

public int bindService(IApplicationThread caller, IBinder token, Intent service,
        String resolvedType, IServiceConnection connection, int flags, String callingPackage,
        int userId) throws TransactionTooLargeException {
    return mServices.bindServiceLocked(caller, token, service,
            resolvedType, connection, flags, callingPackage, userId);
}
Copy the code

MServices is ActiveServices. The ActiveServices class helps AMS manage services, including starting, binding, and stopping services.

Call ActiveServices bindServiceLocked,bindServiceLocked With bringUpServiceLocked, bringUpServiceLocked will call realStartServiceLocked method again, The execution logic of the realStartServiceLocked method is similar to the logic in the “Android_Service startup process analysis”, and ultimately passes ApplicationThread completes the creation of the Service instance and executes its onCreate method, which I won’t repeat here.

But notice that in the realStartServiceLocked method, there’s one thing to watch out for when we’re bindService bound to Service

private final void realStartServiceLocked(ServiceRecord r,
        ProcessRecord app, boolean execInFg) throws RemoteException {

    boolean created = false;
    
    / / start the Service
    app.thread.scheduleCreateService(r, r.serviceInfo,
            mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
            app.repProcState);
    created = true;
    
    / / deeprequestServiceBindingsLocked(r, execInFg); . }private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)
        throws TransactionTooLargeException {
    / / deep
    requestServiceBindingLocked(r, ibr, execInFg, false);
}

private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
        boolean execInFg, boolean rebind) throws TransactionTooLargeException {
    
    ScheduleBindService (ActivityThread)
    r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
            r.app.repProcState);
    return true;
}
Copy the code

In realStartServiceLocked method invocation requestServiceBindingsLocked method, requestServiceBindingsLocked method calls another requestServiceBindingLo The scheduleBindService method of ActivityThread is called remotely, and the scheduleBindService method is finally bound, as the name suggests

public final void scheduleBindService(IBinder token, Intent intent,
        boolean rebind, int processState) {
    updateProcessState(processState, false);
    BindServiceData s = new BindServiceData();
    // Note that the Activity token is passed here
    s.token = token;
    s.intent = intent;
    s.rebind = rebind;

    if (DEBUG_SERVICE)
        Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="
                + Binder.getCallingUid() + " pid=" + Binder.getCallingPid());
    sendMessage(H.BIND_SERVICE, s);
}
Copy the code

Then we come to the familiar H Handler, where the BIND_SERVICE message calls the handleBindService() method in ActivityThread

private void handleBindService(BindServiceData data) {
    // In the handleCreateService method, the token and Service mapping is stored in the mServices
    // Final ArrayMap
      
        mServices = new ArrayMap<>();
      ,>
    // The Service can be fetched based on the token
    Service s = mServices.get(data.token);
    if(s ! =null) {
        data.intent.setExtrasClassLoader(s.getClassLoader());
        data.intent.prepareToEnterProcess();
        
        if(! data.rebind) {// If not rebind
            
            // The onBind method,,,, is called from here, so this is the UI thread
            IBinder binder = s.onBind(data.intent);
            // call AMS's publishService method
            ActivityManager.getService().publishService(
                    data.token, data.intent, binder);
        } else {
            s.onRebind(data.intent);
            ActivityManager.getService().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0.0); }}}Copy the code

In the handleCreateService method, the token and Service mappings are stored in the mServices. The mServices definition is final ArrayMap

mServices = new ArrayMap<>(); The onBind method of the Service is called. The onBind method is executed on the UI thread. The onBind method will only be called once when the Service is rebound, unless the Service has been terminated before.
,>

Then came AMS, this time the AMS publishService method.

public void publishService(IBinder token, Intent intent, IBinder service) {
    mServices.publishServiceLocked((ServiceRecord)token, intent, service);
}

//ActiveServices.java => publishServiceLocked
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
    try {
        if(r ! =null) {
            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);
                    // Core code
                    c.conn.connected(r.name, service, false);
                }
            }

            serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false); }}finally{ Binder.restoreCallingIdentity(origId); }}Copy the code

Arthur c. onn is of type ServiceDispatcher InnerConnection, service is the service’s onBind () method returns the Binder object. Look at the ServiceDispatcher InnerConnection connected method

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)
            throws RemoteException {
        LoadedApk.ServiceDispatcher sd = mDispatcher.get();
        if(sd ! =null) { sd.connected(name, service, dead); }}}Copy the code

ServiceDispatcher is passed in to the InnerConnection constructor, so it’s easy to get ServiceDispatcher and call its Connected method

public void connected(ComponentName name, IBinder service, boolean dead) {
    //mActivityThread is an ActivityThread Handler named H
    if(mActivityThread ! =null) {
        // The Handler in the main thread calls POST to indicate that RunConnection is running in the main thread
        mActivityThread.post(new RunConnection(name, service, 0, dead));
    } else{ doConnected(name, service, dead); }}private final class RunConnection implements Runnable {
    RunConnection(ComponentName name, IBinder service, int command, boolean dead) {
        mName = name;
        mService = service;
        mCommand = command;
        mDead = dead;
    }

    public void run(a) {
        if (mCommand == 0) {
            / / deep
            doConnected(mName, mService, mDead);
        } else if (mCommand == 1) { doDeath(mName, mService); }}final ComponentName mName;
    final IBinder mService;
    final int mCommand;
    final boolean mDead;
}

//ServiceDispatcher => doConnected
public void doConnected(ComponentName name, IBinder service, boolean dead) {...//mConnection is a ServiceConnection in ServiceDispatcher. ServiceDispatcher initializes ServiceConnectionmConnection.onServiceConnected(name, service); . }Copy the code

A doConnected method is executed via the Handler#post in ActivityThread. Inside the doConnected method is a callback to the onServiceConnected method from the familiar ServiceConnection object.

You can see that ServiceDispatcher makes a switch. When a Service is connected, the InnerConnection calls the onServiceConnected method of ServiceDispatcher’s ServiceConnection to notify it of a binding success. Let the client know that the Service is bound.

BindService stops and unbinds the bindService. The system stops and unbinds the bindService.