Android6.0之App的Service组件运行机制之bindService

bindService的过程要比startService的过程复杂一些,因为bingService之后,发起者可以跨进程调用service的某些方法。而startService启动service之后,发起者仅能去终止service,而不能调用service的方法。

既然发起者可以跨进程调用service中的方法,那么肯定要用到binder了。本文也会重点分析binder的传递过程,至于binder的机制,后面在单独开篇介绍。使用bindService启动的service的开发过程中一般都会借助aidl。这里不介绍aidl,因为这不是本文的重点。

当一个app进程bindService()时,它需要先准备好一个实现了ServiceConnection接口的对象。ServiceConnection的定义如下:

public interface ServiceConnection {

    public void onServiceConnected(ComponentName name, IBinder service);

    public void onServiceDisconnected(ComponentName name);
}

bindService()见名知意即绑定服务,建立一个逻辑连接。当连接建立之后,AMS会回调ServiceConnection接口对象的onServiceConnected()方法。把远端service的代理binder传递过来,这样就可以通过这个代理binder跨进程调用service中的方法了。

ServiceConnection接口的onServiceDisconnected()方法并不会在unbindService()操作断开逻辑连接时执行。而是在远端service进程终止时,AMS才会回调onServiceDisconnected()。

另外要注意的是纵然service进程的因为某些原因被终止,之前的绑定在该service上的逻辑连接仍处于激活状态,当service再次运行的时候,AMS会回调onServiceConnected。

bindService的过程,我们要关心的binder有两个,一个是远端service的binder,有了它客户端进程才能跨进程调用service中的方法;另一个是binder是客户端传递给AMS的,有了这个binder,AMS才会在逻辑连接建立之后,跨进程调用客户端中的ServiceConnection接口的对象中的onServiceConnected()和onServiceDisconnected()。

要明白bindservice的过程,就必须搞清楚这两个binder的传递过程他。

另外bindservice在建立连接时,如果发现service还没启动,会根据flag是否设置BIND_AUTO_CREATE,决定是否启动这个service。

同startService()一样,bindService()也是一个异步的过程,也就是说当该方法返回时,逻辑连接很可能还没有建立,通俗的说就是该方法返回时,onServiceConnected()方法很可能还没执行。

app的service和系统的service

系统中的servcice都是由ServiceManager负责管理的。系统中的service在启动的时候,都会向ServiceManager注册。

App进程要使用这些系统service时,比如AMS,可以向ServiceManager通过service名字查询。ServiceManager会返回这个service的一个代理binder。这样便可以向service发起调用请求了。

但是app是没有权限向ServiceManager注册service的。所以只能另辟蹊径。bindService要负责找到service的代理binder并传递到app进程。这期间涉及到binder实体的跨进程传输,也就是所谓的匿名binder。因为这些binder并没有在ServiceManager中注册。

bindService的过程

整个过程大体上是这样的:

  1. 检查要绑定的额service是否启动,没有的话,要先启动service,然后执行service的生命周期方法。

  2. 执行绑定,先将远端service的binder传递到AMS中,然后AMS在将其传递到客户端组件进程中

ContextImpl.bindService():

public boolean bindService(Intent service, ServiceConnection conn,
            int flags) {
        warnIfCallingFromSystemProcess();
        return bindServiceCommon(service, conn, flags, Process.myUserHandle());
    }

ContextImpl.bindServiceCommon():

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
            UserHandle user) {
        IServiceConnection sd;
      ................
        if (mPackageInfo != null) {
          //从LoadedApk中拿到一个binder实体
            sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
                    mMainThread.getHandler(), flags);
        } else {
            throw new RuntimeException("Not supported in system context");
        }
        //  Android5.0之后不允许使用隐式调用
        // 这里对传入的intetn进行检查,如果是android5.0以上,
        //是隐式intent的话抛出异常
        validateServiceIntent(service);
        try {
            IBinder token = getActivityToken();
            ....................
            service.prepareToLeaveProcess();
            // 向AMS发起跨进程调用其bindService方法
            int res = ActivityManagerNative.getDefault().bindService(
                mMainThread.getApplicationThread(), getActivityToken(), service,
                service.resolveTypeIfNeeded(getContentResolver()),
                sd, flags, getOpPackageName(), user.getIdentifier());
          ..............................
            return res != 0;
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
    }

这里便引出了第一个binder:IServiceConnection sd。这是一个实体binder,也就是说跨进程传输到AMS进程之后,AMS进程中最终得到的是一个代理binder。而且binder实体跨进程传输过程中,会在binder驱动层中为其创建相应的数据结构,这样binder便在binder驱动中扎根了。

那么这个binder实体最初是从哪里来的呢?

客户端组件中用来调度绑定操作的ServiceDispatcher对象

此处的mPackageInfo是用户进程里和apk对应的LoadedApk对象,前面所说的第一个binder实体由LoadedApk.getServiceDispatcher()获得:

public final IServiceConnection getServiceDispatcher(ServiceConnection c,
           Context context, Handler handler, int flags) {
       synchronized (mServices) {
           LoadedApk.ServiceDispatcher sd = null;
            // 先从LoadedApk.mService中查找,
            // 看看发起绑定操作的组件是否已经存在一个用来处理传入的
            //ServiceConnection接口对象的ServiceDispatcher
           ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
           if (map != null) {
              // 存在的话直接返回这个ServiceDispatcher
               sd = map.get(c);
           }
           if (sd == null) {
              // 不存在的话创建一个ServiceDispatcher
              // 这里要注意第三个参数handler,是主线程的handler
              // 保存在了ServiceDispatcher.mActivityThread中
               sd = new ServiceDispatcher(c, context, handler, flags);
               if (map == null) {
                   map = new ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>();
                   mServices.put(context, map);
               }
                // 将创建的ServiceDispatcher缓存到LoadedApk.mService
               map.put(c, sd);
           } else {
               sd.validate(context, handler);
           }
           return sd.getIServiceConnection();
       }
   }

LoadedApk.ServiceDispatcher从它的名字中可以看出它类似于一个Dispatcher的作用,这个类的作用就是当client bind某个service成功之后,负责向client分配service IBinder的;以及当client unbind service时,负责通知client unbind service的状态。

对于ServiceDispatcher的管理,是以apk,即package为载体的,也就是说对于某个package,定义在其中的component如果请求去bind一个service,那么LoadedApk将为这个component分配一个ServiceDispatcher。

component每请求bind一个service,都会为其指定一个ServiceConnection接口的对象,同一component组件内,LoadedApk将会为这个ServiceConnection接口的对象分配一个唯一的ServiceDispatcher。但是不同component组件中,就算是使用同一个ServiceConnection接口的对象,LoadedApk中也会为这个对象分配两个ServiceDispatcher对象。

也就是说,ServiceDispatcher是客户端组件相关的一个概念。

每个app进程中是可以加载多个apk包的,记录在ActivityThread.mPackages,整个关系如下所示:

android_app_service-6.png

packages中的组件component,例如某个activity中使用bindService去绑定一个service时,都需要提供一个实现ServiceConnection接口的对象,每一个这样的对象都会与一个ServiceDispatcher对象绑定。

LoadedApk.mServices中存储了本package中所有组件中所有的ServiceDispatcher。它是一个ArrayMap,key是context,实际上就是组件,因为组件继承自Context。value又是一个ArrayMap,因为一个组件中使可以绑定多个service的嘛。这个map的可以是实现了ServiceConnection接口的对象,value是与之绑定的ServiceDispatcher对象。

现在可以看看这个ServiceDispatcher到底是个什么东东了:

static final class ServiceDispatcher {
    // 一个binder实体对象
    private final ServiceDispatcher.InnerConnection mIServiceConnection;
    // 逻辑连接建立时,执行的回调接口对象
    private final ServiceConnection mConnection;
    // 所在的组件,即client
    private final Context mContext;
    // 进程主线程即UI线程中的handler
    private final Handler mActivityThread;
    private final ServiceConnectionLeaked mLocation;
    // 绑定service时,传入的flag,例如BIND_AUTO_CREATE
    private final int mFlags;

    private RuntimeException mUnbindLocation;
    private boolean mDied;
    private boolean mForgotten;
    // 因为一个ServiceConnection可以被bindService方法多次调用,用来启动不同的service,
    // 那么ServiceDispatcher中自然要存储这些连接信息了:
    // ConnectionInfo很简单只有两个属性成员
    // IBinder binder;远端service的引用binder
    // IBinder.DeathRecipient deathMonitor; 远端binder的死亡通知方法
    private final ArrayMap<ComponentName, ServiceDispatcher.ConnectionInfo> mActiveConnections
       = new ArrayMap<ComponentName, ServiceDispatcher.ConnectionInfo>();
................
  }

ServiceDispatcher.InnerConnection是一个继承自 IServiceConnection.Stub的binder实体类:

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

其中ServiceDispatcher.connected():

public void connected(ComponentName name, IBinder service) {
    // 这里的mActivityThread是主线程的handler
    // 也就是说,实际上ServiceConnection中的回调是在主线程中执行的
      if (mActivityThread != null) {
          mActivityThread.post(new RunConnection(name, service, 0));
      } else {
          doConnected(name, service);
      }
}

ServiceDispatcher.InnerConnection是一个继承自 IServiceConnection.Stub的binder实体类,从其名字上来看是一个内部连接,该怎么理解呢?

因为bindService()传递的ServiceConnection接口对象,并没有跨进程传输到AMS中,从bindServiceCommon()方法可以看出最终传递到AMS的是LoadedApk.getServiceDispatcher()返回的ServiceDispatcher.InnerConnection。AMS通过它跨进程间接调用ServiceConnection中的方法。

因为一个ServiceConnection可以在同一个客户端组件内被bindService方法多次调用,用来绑定不同的service,那么ServiceDispatcher中自然要存储这些使用同一个serviceConnection绑定的service的连接信息了——ServiceDispatcher.mActiveConnections。

它是ArrayMap<ComponentName, ServiceDispatcher.ConnectionInfo>类型对象,


private static class ConnectionInfo {
  // 远端service的代理binder
  IBinder binder;
  // 远端service 死亡通知回调
  IBinder.DeathRecipient deathMonitor;
}

AMS中绑定service过程

ContextImpl.bindServiceCommon()方法中得到ServiceConnection对应的ServiceDispatcher对象之后,便向AMS发起请求,跨进程调用AMS.bindService():


public int bindService(IApplicationThread caller, IBinder token, Intent service,
        String resolvedType, IServiceConnection connection, int flags, String callingPackage,
        int userId) throws TransactionTooLargeException {
    enforceNotIsolatedCaller("bindService");

    ..............
    synchronized(this) {
      // mServices是ActiveService
        return mServices.bindServiceLocked(caller, token, service,
                resolvedType, connection, flags, callingPackage, userId);
    }
}

AMS.bindService()对参数做了简单检查之后,又调用ActiveService.bindServiceLocked()方法。

这里先对ActiveService.bindServiceLocked()方法的几个重要参数做一些说明:

int bindServiceLocked(
         IApplicationThread caller, // 客户端进程ActivityThread.mAppThread的代理binder
         IBinder token,
         Intent service,// 客户端绑定service时的intent
         String resolvedType,
         IServiceConnection connection, //客户端进程的中ServiceDispatcher.InnerConnection的代理binder
         int flags,
         String callingPackage, int userId) throws TransactionTooLargeException {

ActiveService.bindServiceLocked()与上一篇介绍的startService的ActiveService.startServiceLocked()方法中有很多相似的逻辑。

比如同样是要先查找要启动的servicee在AMS中的代表ServiceRecord是否存在,存在的话,意味着service已经启动;不存在的话,要创建一个ServiceRecord对象,这些操作还是由retrieveServiceLocked()方法负责的。然后通过bringUpServiceLocked()调用service的生命周期方法。只不过bindservice除了这两个基本操作外,还要执行绑定操作,即将service的binder传跨进程传输到app客户端组件中。

绑定前的准备工作

执行绑定操作前,要先做一些准备工作,比如在AMS中记录客户端和service的一些信息,下面的代码就是做这些事情的:

int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
           String resolvedType, IServiceConnection connection, int flags,
           String callingPackage, int userId) throws TransactionTooLargeException {
  ...............
  // 得到service对应的ServiceRecord
  ServiceLookupResult res =
      retrieveServiceLocked(service, resolvedType, callingPackage,
              Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg);
  ServiceRecord s = res.record;
  ....
  // 根据传入的intent和发起者进程,查找到一个合适的AppBindRecord对象,
  // 查找不到就创建一个,下面会介绍规则
  AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
  // 为本次连接创建ConnectionRecord对象
  ConnectionRecord c = new ConnectionRecord(b, activity,
          connection, flags, clientLabel, clientIntent);
  // 客户端进程的中ServiceDispatcher.InnerConnection的代理binder
  IBinder binder = connection.asBinder();
  // 将创建的逻辑连接对象ConnectionRecord,记录在ServiceRecord中
  ArrayList<ConnectionRecord> clist = s.connections.get(binder);
  if (clist == null) {
      clist = new ArrayList<ConnectionRecord>();
      s.connections.put(binder, clist);
  }
  clist.add(c);
  // 同时记录在AppBindRecord.connections中
  b.connections.add(c);
  // AppBindRecord.client是ProcessRecord,代表客户端的进程
  b.client.connections.add(c);
  .......
  // 除了ServiceRecord.connections记录了该service连接信息外
  // ActiveService.mServiceConnections记录了当前系统中所有的连接信息
  // 所以也要将创建的连接对象,加入ActiveService.mServiceConnections
  clist = mServiceConnections.get(binder);
  if (clist == null) {
      clist = new ArrayList<ConnectionRecord>();
      mServiceConnections.put(binder, clist);
  }
  clist.add(c);
 ............

在前面关于service机制介绍的文章中提到过ServiceRecord中有一些数据成员是bindservice时用到的:

final class ServiceRecord extends Binder {
    ...............
    final ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections
                = new ArrayMap<IBinder, ArrayList<ConnectionRecord>>();// IBinder -> ConnectionRecord of all bound clients
    // service所在的进程
    ProcessRecord app;      // where this service is running or null.
}

AMS描述绑定service时的intent的IntentBindRecord类

app客户端组件中发起绑定service操作时,一定要用到intent,一般是显示的intent(android 5.0 之后要求必须是显示intent),而且只设置intetn.mComponent字段。

AMS中对绑定操作传入的intent是用Intent.FilterComparison类来描述,也就是说当客户端绑定service时使用的intent指定的参数都一致的话,AMS会将其看做是同一类intent。

AMS会为这类intent为其创建一个IntentBindRecord对象。

final class IntentBindRecord {
    // 绑定的service在AMS中的代表
    final ServiceRecord service;
    /** The intent that is bound.*/
    final Intent.FilterComparison intent; //
    /** All apps that have bound to this Intent. */
    final ArrayMap<ProcessRecord, AppBindRecord> apps
            = new ArrayMap<ProcessRecord, AppBindRecord>();
    // service的binder代理binder
    IBinder binder;

其中IntentBindRecord.service 最终会保存绑定的service的ServiceRecord;

IntentBindRecord.intent 就是前面所说的绑定service时设定的参数一致的intent在AMS中的表示Intent.FilterComparison对象;

IntentBindRecord.apps用来记录所有使用该类intetn绑定同一个service的客户端信息。key是客户端进程ProcessRecord,value是AppBindRecord。因为不同的客户端可能使用相同的intent参数来绑定同一个service,所以IntentBindRecord要记录下这些客户端信息;

IntentBindRecord.binder最终保存service的代理binder;

AMS通过ServiceRecord.retrieveAppBindingLocked()方法为判断是否为传入的intent创建一个IntentBindRecord:

public AppBindRecord retrieveAppBindingLocked(
           Intent intent,// 客户端发起绑定操作时传入的intent
           ProcessRecord app // 客户端组件进程) {
       // 为传入的intent创建一个Intent.FilterComparison
       Intent.FilterComparison filter = new Intent.FilterComparison(intent);
       // 查找传入的intent是否已经有IntentBindRecord
       IntentBindRecord i = bindings.get(filter);
       if (i == null) {
          // 没有的话创建
           i = new IntentBindRecord(this, filter);
           //并缓存到ServiceRecord.bings中
           bindings.put(filter, i);
       }
       // 查找客户端的组件是否已经绑定过该service
       AppBindRecord a = i.apps.get(app);
       // 绑定过的话,返回找到的AppBindRecord
       if (a != null) {
           return a;
       }
       // 没有的话创建一个AppBindRecord对象
       a = new AppBindRecord(this, i, app);
       // 并缓存到IntentBindRecord.apps中
       i.apps.put(app, a);
       return a;
   }

retrieveAppBindingLocked()另一个主要作用是查找并创建AppBindRecord对象。

AMS用于描述绑定service的客户端整体信息的AppBindRecord类

对于一个Service来说,有多少app客户端进程和它建立了绑定关系,就会有多少个AppBindRecord对象。一个app客户端进程里可以有多个地方发起绑定动作,所以AppBindRecord里需要用一个ArraySet<ConnectionRecord>记录下每个绑定动作对应的逻辑连接对象。

final class AppBindRecord {
    // 所在的service
    final ServiceRecord service;    // The running service.
    // 客户端发起的bindservice时传入的intent,AMS会为其创建一个对应的IntentBindRecord
    final IntentBindRecord intent;  // The intent we are bound to.
    // 客户端进程
    final ProcessRecord client;     // Who has started/bound the service.
    // 客户端所在的app,其他组件绑定该service的逻辑连接
    final ArraySet<ConnectionRecord> connections = new ArraySet<>();
                                    // All ConnectionRecord for this client.

AppBindRecord.service 用于描述客户端绑定的service;

AppBindRecord.intent 用于描述客户端绑定该service时使用的intent;

AppBindRecord.client 用于描述客户端的进程;

AppBindRecord.connections 用于描述客户端中所有组件绑定该service时创建的逻辑连接;

AMS通过ServiceRecord.retrieveAppBindingLocked()来查找并创建一个合适的AppBindRecord对象。

描述绑定连接的ConnectionRecord类

AMS为每次绑定过程中创建的连接分配一个ConnectionRecord类型对象.

ConnectionRecord用来描述一个连接信息,即绑定信息,要对客户端和service端进行描述。

/**
* Description of a single binding to a service.
*/
final class ConnectionRecord {
   // 记录客户端绑定的信息
   final AppBindRecord binding;    // The application/service binding.
   // 这里是绑定服务的客户端的一个binder,通过它可以调用客户端的方法
   // ServiceConnection.onServiceConnected()
   final IServiceConnection conn;  // The client connection.
   final int flags;                // Binding options.

ConnectionRecord.binding代表这个连接所在的AppBindRecord.

ConnectionRecord.coon是一个binder代理对象,其实体binder是app进程中ServiceDispatcher.mIServiceConnection。用来回调客户端进程中当连接成功建立时的回调方法。

客户端组件在bindservice时,都要创建一个实现ServiceConnection接口的对象,每个这样的对象在客户端组件所在的LoadedApk中都会分配一个ServiceDispatcher对象,这个对象用于处理所有使用该ServiceConnection接口的对象绑定service的客户端组件的回调方法的执行。

ConnectionRecord.flags 用于描述绑定该service时,指定的flags,例如BIND_AUTO_CREATE.

AMS创建的ConnectionRecord对象会存储在ServiceRecord.connections成员变量中.

ServiceRecord.connections是一个map,key是ServiceDispatcher.InnerConnection的代理binder,value是ArrayList<ConnectionRecord>类型的。

因为客户端进程中一个Component中可能使用同一个ServiceConnection接口对象来多次绑定同一个service,因为每次绑定都会创建一个ConnectionRecord对象,那么这些ConnectionRecord需要使用ArrayList<ConnectionRecord>来保存。

value中ConnectionRecord.IServiceConnection实际上一致与key是一致的。

AMS除了将这个"逻辑连接"ConnectionRecord对象,记录在ServiceRecord.connections中外,还要向至少下面的几处位置做记录:

  1. 客户端所在的进程在AMS中的代表ProcessRecord.connections

  2. ActiveService.mServiceConnections,这里面记录了AMS中所有app的service的连接

  3. AppBindRecord.connections中

之所以要在这么多地方做记录,可能是为了在不同的场合下迅速查找到连接吧。

了解了以上内容后,就可以通过下图简明的描述客户端进程和AMS之间的关系:

android_app_service-7.png

执行service生命周期方法

在做好前面的准备工作之后,binderservice()就开始准备与Service建立连接了。那么自然要先对service进行一些操作,说白了就是执行service的生命周期方法,这是由bringUpServiceLocked()方法来负责的。

int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
           String resolvedType, IServiceConnection connection, int flags,
           String callingPackage, int userId) throws TransactionTooLargeException {
.................
if ((flags&Context.BIND_AUTO_CREATE) != 0) {
    s.lastActivity = SystemClock.uptimeMillis();
    // 只有当要绑定的service所在的进程还启动的时候,该方法返回非null
    // 因为启动进程需要一段时间,所以就要先退出来
    // 这里暂时假设service所在的进程已经启动
    if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {
        return 0;
    }
}
....................

此时可以分为两大情况:

  1. service进程还没启动

  2. service进程已经启动,此时有可分为两种情况:service还没启动和service已经运行。
    bringUpServiceLocked()方法依据ServiceRecord.app区分以上两大情况:

1.ServiceRecord.app不为null,而且ServiceRecord.app.thread也不为null,预示着service已经运行了,但是这时候并不会向startService()启动service那样跨进程调用service.onStartCommand()生命周期方法,因为bindService启动service时没有将信息记录到ServiceRecord.pendingStarts。

2.ServiceRecord.app为null,说明ServiceRecord还没有和service所在的进程关联。

此时在依据ServiceRecord.processName,也就是service要求运行在的进程的名字,在AMS中查找是否有这样的进程存在,如果有的话,只需要启动service,也就是在找到的进程中创建service对象,并执行service.onCreate()生命周期方法。

以上两种情况的详细过程,参考前一篇文章。

如果没有在AMS中找到名字为ServiceRecord.processName的进程,那么就要先创建进程了,这里不考虑这种情况。

绑定service

绑定实际上就是想办法拿到service的binder,并将其传递到客户端组件进程,另外还要对前面准备工作期间创建的数据结构设置相关的字段。

这部分代码如下:

int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
           String resolvedType, IServiceConnection connection, int flags,
           String callingPackage, int userId) throws TransactionTooLargeException {
.................
// b.intent.received为true,表明已经拿到了service的binder
f (s.app != null && b.intent.received) {
        // Service is already running, so we can immediately
        // publish the connection.
        try {
            // 这里就可以直接远程调用客户端组件中onServiceConnected()方法将service的binder传递过去了
            c.conn.connected(s.name, b.intent.binder);
        } catch (Exception e) {
            Slog.w(TAG, "Failure sending service " + s.shortName
                    + " to connection " + c.conn.asBinder()
                    + " (in " + c.binding.client.processName + ")", e);
        }

        // 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) {
            requestServiceBindingLocked(s, b.intent, callerFg, true);
        }
    } else if (!b.intent.requested) {
        // 之前没绑定过,那么就调用下面的额方法进行绑定
        requestServiceBindingLocked(s, b.intent, callerFg, false);
    }

    getServiceMap(s.userId).ensureNotStartingBackground(s);

} finally {
    Binder.restoreCallingIdentity(origId);
}

这里分两种情况:

  1. 客户端组件之前已经绑定过该service,现在这个组件又要再次绑定,也就是客户端同一组件重复绑定

  2. 客户端组件首次绑定该service

这两种情况的区分是依据AppBindRecord.intent,即AMS为bindservice()传入的intent分配的IntentBindRecord对象来决定的。

final class IntentBindRecord {
  /** Binder published from service. */
  IBinder binder;
  /** Set when we have initiated a request for this binder. */
  boolean requested;
  /** Set when we have received the requested binder. */
  boolean received;
  /** Set when we still need to tell the service all clients are unbound. */
  boolean hasBound;
}

IntentBindRecord.received为true,表明IntentBindRecord.binder已经指向远端service的binder;

IntentBindRecord.requested为false,表明IntentBindRecord.binder还没有指向远端service的binder;

这里分析第二种情况,时序图如下:

android_app_service-9.png

requestServiceBindingLocked()方法中跨进程调用service所在的进程中方法,最终会导致service.onBind()执行,该方法返回service的实体binder。

这里有一点要贴别注意,那只要service被某个客户端组件绑定过了,就不会再执行service.onBind()方法了,原因就在requestServiceBindingLocked()方法中:

private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
            boolean execInFg, boolean rebind) throws TransactionTooLargeException {

        ..............
        // 传入的rebind为false还是true,取决于AMS调用service.onUnbind()返回值
        // 如果希望客户端下一次绑定到服务时接收 onRebind() 调用(而不是接收 onBind() 调用),onUnbind()返回true
        // service首次被绑定时,rebind肯定为false
        if ((!i.requested || rebind) && i.apps.size() > 0) {
            try {
                bumpServiceExecutingLocked(r, execInFg, "bind");
                r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
                // 跨进程调用service所在进程的scheduleBindService()方法,执行绑定操作,这是一个异步方法,会立即返回
                r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
                        r.app.repProcState);
                if (!rebind) {
                    // 只要service被绑定过了,IntentBindRecord.requested就会被设置为true
                    i.requested = true;
                }
                i.hasBound = true;
                i.doRebind = false;
            } catch (TransactionTooLargeException e) {
              .................
            } catch (RemoteException e) {
              .................
            }
        }
        return true;
    }

通过以上代码可知,只要service被绑定过一次,那么IntentBindRecord.requested救回被设置为true,其他客户端组件再次绑定的时候,由于if条件为假,所以不会再次跨进程调用service进程中的scheduleBindService()方法,也就不会调用service.onBind()方法了。

因为bindservice时传入的intent一般都是显示intent,不会设置其他参数,所以只要客户端绑定的是同一个service,那么在AMS中只会有一个IntentBindRecord对象。

除非该servcie绑定过之后,所有绑定它的客户端组件都执行了unbindService(),那么最终会导致service.onUnbind()方法执行,如果该方法返回了true,那么下次再有客户端组件绑定该service时,rebind会被设置为true,这导致不会调用service.onBind(),而是调用service.rebind()方法。如下代码所示:

private void handleBindService(BindServiceData data) {
        Service s = mServices.get(data.token);
        if (DEBUG_SERVICE)
            Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind);
        if (s != null) {
            try {
                data.intent.setExtrasClassLoader(s.getClassLoader());
                data.intent.prepareToEnterProcess();
                try {
                    // 传入flase时执行onBind()
                    if (!data.rebind) {
                        IBinder binder = s.onBind(data.intent);
                        ActivityManagerNative.getDefault().publishService(
                                data.token, data.intent, binder);
                    } else {
                      // 传入true时执行onRebind()
                        s.onRebind(data.intent);
                        ActivityManagerNative.getDefault().serviceDoneExecuting(
                                data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
                    }
                    ensureJitEnabled();
                } catch (RemoteException ex) {
                }
            } catch (Exception e) {
                if (!mInstrumentation.onException(s, e)) {
                    throw new RuntimeException(
                            "Unable to bind to service " + s
                            + " with " + data.intent + ": " + e.toString(), e);
                }
            }
        }
    }

这里考虑首次绑定时的情况,所以rebind肯定为false,那么service调用过onBind()之后,又通过AMS的代理,跨进程调用AMS的publishService()将service的binder传递到AMS中,然后在传递到客户端。

void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
.......
IntentBindRecord b = r.bindings.get(filter);
// 初次绑定
if (b != null && !b.received) {
      // 保存service的代理binder
      b.binder = service;
      // 设置下面的两个标志为true
      b.requested = true;
      b.received = true;
      // 一般情况下,首次绑定时,connections.size为1
      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)) {
                   if (DEBUG_SERVICE) Slog.v(
                           TAG_SERVICE, "Not publishing to: " + c);
                   if (DEBUG_SERVICE) Slog.v(
                           TAG_SERVICE, "Bound intent: " + c.binding.intent.intent);
                   if (DEBUG_SERVICE) Slog.v(
                           TAG_SERVICE, "Published intent: " + intent);
                   continue;
               }
               if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Publishing to: " + c);
               try {
                   // 将service的binder传递到客户端
                   c.conn.connected(r.name, service);
               } catch (Exception e) {
                   Slog.w(TAG, "Failure sending service " + r.name +
                         " to connection " + c.conn.asBinder() +
                         " (in " + c.binding.client.processName + ")", e);
               }
           }
       }
   }
...........

那么当首次绑定之后,又有其他客户端组件来绑定这个service,那么在bindServiceLocked()方法中,会直接跨进程调用客户端的connected():

int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
        String resolvedType, IServiceConnection connection, int flags,
        String callingPackage, int userId) throws TransactionTooLargeException {
        ................
        if (s.app != null && b.intent.received) {
              // Service is already running, so we can immediately
              // publish the connection.
              try {
                  // 直接跨进程调用客户端的connected()
                  // 不在需要跨进程调用service.onBind()
                  c.conn.connected(s.name, b.intent.binder);
              } catch (Exception e) {
                  Slog.w(TAG, "Failure sending service " + s.shortName
                          + " to connection " + c.conn.asBinder()
                          + " (in " + c.binding.client.processName + ")", e);
              }
        ..........................

}

在看客户端进程的connected()

public void connected(ComponentName name, IBinder service) throws RemoteException {
    // 先得到处理ServiceConnection的ServiceDispatcher
    LoadedApk.ServiceDispatcher sd = mDispatcher.get();
    if (sd != null) {
        sd.connected(name, service);
    }
}
public void connected(ComponentName name, IBinder service) {
   // mActivityThread是在创建 ServiceDispatcher对象时,传入的组件所在进程的主线程的handler
   // 也就是说RunConnection是在组件所在的主线程中执行的
   if (mActivityThread != null) {
       mActivityThread.post(new RunConnection(name, service, 0));
   } else {
       doConnected(name, service);
   }
}

RunConnection.run()中会调用LoadedApk.doConnected()方法:

public void run() {
    if (mCommand == 0) {
        doConnected(mName, mService);
    } else if (mCommand == 1) {
        doDeath(mName, mService);
    }
}

LoadedApk.doConnected():


public void doConnected(ComponentName name, IBinder service) {
    ServiceDispatcher.ConnectionInfo old;
    ServiceDispatcher.ConnectionInfo info;

    synchronized (this) {
        .....................
        ServiceDispatcher.ConnectionInfo old;
        //很有意思,也就是说同一组件中对同一个service重复绑定,onServiceConnected()只会执行一次
        old = mActiveConnections.get(name);
              if (old != null && old.binder == service) {
                  // Huh, already have this one.  Oh well!
                  return;
        }
        if (service != null) {
            // A new service is being connected... set it all up.
            mDied = false;
            info = new ConnectionInfo();
            info.binder = service;
            info.deathMonitor = new DeathMonitor(name, service);
            try {
                // 设置死亡回调
                service.linkToDeath(info.deathMonitor, 0);
                // 将info保存在LoadedApk.mActiveConnections
                mActiveConnections.put(name, info);
            } catch (RemoteException e) {
                // This service was dead before we got it...  just
                // don't do anything with it.
                mActiveConnections.remove(name);
                return;
            }

        } 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 not disconnected.
    if (old != null) {
        mConnection.onServiceDisconnected(name);
    }
    // If there is a new service, it is now connected.
    if (service != null) {
      // 执行回调
        mConnection.onServiceConnected(name, service);
    }
}

doConnected()比较有意思的是下面的代码:

ServiceDispatcher.ConnectionInfo old;
old = mActiveConnections.get(name);
      if (old != null && old.binder == service) {
          // Huh, already have this one.  Oh well!
          return;
}

mActiveConnections来自ServiceDispatcher:

private final ArrayMap<ComponentName, ServiceDispatcher.ConnectionInfo> mActiveConnections
            = new ArrayMap<ComponentName, ServiceDispatcher.ConnectionInfo>();

ServiceDispatcher.mActiveConnections用来记录该LoadedApk中的组件所绑定的service的连接信息。key是service的组件名,value是ServiceDispatcher.ConnectionInfo。

ConnectionInfo中记录了service的代理binder以及死亡通知回调。


private static class ConnectionInfo {
  // 远端service的代理binder
  IBinder binder;
  // 远端service 死亡通知回调
  IBinder.DeathRecipient deathMonitor;
}

connected(ComponentName name, IBinder service)方法的第一个参数是service的组件名,第二个参数是service的代理binder。

connected()方法中首先以name为key在mActiveConnections查找,如果索引到的ConnectionInfo的binder与传入的binder一致的话,说明是同一组件内使用同一个ServiceConnection接口对象对同一个service重复绑定,此时不会执行onServiceConnected()方法。

unbindeService

现在在看一下unbindeService()的过程,直接看AMS.unbindServiceLocked(),整个过程大体上就是找到相关的连接对象ConnectionRecord,将其从相关的map中移除,然后根据情况决定是否调用serive.onUnbind生命周期方法

boolean unbindServiceLocked(IServiceConnection connection) {
       // 得到ServiceDispatcher.InnerConnection的binder
       IBinder binder = connection.asBinder();
       if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "unbindService: conn=" + binder);
       // 前面说了ActiveService.mServiceConnections,这里面记录了AMS中所有app的service的连接
       // 自然也包括同一组件内使用同一个ServiceConnection绑定同一个service的情况
       ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder);
       // 说明没有使用该ServiceConnection接口对象绑定过service
       // 所以无需unbind
       if (clist == null) {
           Slog.w(TAG, "Unbind failed: could not find connection for "
                 + connection.asBinder());
           return false;
       }

       final long origId = Binder.clearCallingIdentity();
       try {
           // 依次取出同一组件内使用该ServiceConnection接口对象绑定的service的连接信息对象ConnectionRecord
           // 这里要注意的是,这相当于在发起unbindService()操作的组件中,对所有使用该ServiceConnection接口对象绑定的sercvice
           // 发起unbindService操作
           while (clist.size() > 0) {
               ConnectionRecord r = clist.get(0);
               removeConnectionLocked(r, null, null);
               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);
               }
               if (r.binding.service.app != null) {
                   // This could have made the service less important.
                   if ((r.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
                       r.binding.service.app.treatLikeActivity = true;
                       mAm.updateLruProcessLocked(r.binding.service.app,
                               r.binding.service.app.hasClientActivities
                               || r.binding.service.app.treatLikeActivity, null);
                   }
                   mAm.updateOomAdjLocked(r.binding.service.app);
               }
           }
       } finally {
           Binder.restoreCallingIdentity(origId);
       }

       return true;
   }

再看removeConnectionLocked():

    void removeConnectionLocked(
        ConnectionRecord c, ProcessRecord skipApp, ActivityRecord skipAct) {
        IBinder binder = c.conn.asBinder();
        AppBindRecord b = c.binding;
        ServiceRecord s = b.service;
        // 从ServiceRecord中取出所有绑定该service的连接
        ArrayList<ConnectionRecord> clist = s.connections.get(binder);
        if (clist != null) {
            // 将使用要unbindService()的ServiceConnection创建的连接
            // 从ServiceRecord.connections中移除
            clist.remove(c);
            if (clist.size() == 0) {
                s.connections.remove(binder);
            }
        }
        // 将使用要unbindService()的ServiceConnection创建的连接
        // 从AppBindRecord.connections中移除
        b.connections.remove(c);
        if (c.activity != null && c.activity != skipAct) {
            if (c.activity.connections != null) {
                c.activity.connections.remove(c);
            }
        }
        if (b.client != skipApp) {
            b.client.connections.remove(c);
            if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
                b.client.updateHasAboveClientLocked();
            }
            if (s.app != null) {
                updateServiceClientActivitiesLocked(s.app, c, true);
            }
        }
        clist = mServiceConnections.get(binder);
        if (clist != null) {
            // 将使用要unbindService()的ServiceConnection创建的连接
            // 从ActiveService.mServiceConnections中移除
            clist.remove(c);
            if (clist.size() == 0) {
                mServiceConnections.remove(binder);
            }
        }

        mAm.stopAssociationLocked(b.client.uid, b.client.processName, s.appInfo.uid, s.name);

        // 如果AppBindRecord.connections.size为0
        // 表示某客户端已经没有组件与该service绑定了
        // 那么将客户端从IntentBindRecord.apps中移除
        if (b.connections.size() == 0) {
            b.intent.apps.remove(b.client);
        }

        if (!c.serviceDead) {
            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Disconnecting binding " + b.intent
                    + ": shouldUnbind=" + b.intent.hasBound);
            // 如果IntentBindRecord.apps.size为0,表示没有客户端与该service绑定了
            // 那么开始回调service进程的scheduleUnbindService(),执行service.unbind()
            if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0
                    && b.intent.hasBound) {
                try {
                    bumpServiceExecutingLocked(s, false, "unbind");
                    if (b.client != s.app && (c.flags&Context.BIND_WAIVE_PRIORITY) == 0
                            && s.app.setProcState <= ActivityManager.PROCESS_STATE_RECEIVER) {
                        // If this service's process is not already in the cached list,
                        // then update it in the LRU list here because this may be causing
                        // it to go down there and we want it to start out near the top.
                        mAm.updateLruProcessLocked(s.app, false, null);
                    }
                    mAm.updateOomAdjLocked(s.app);
                    b.intent.hasBound = false;
                    // Assume the client doesn't want to know about a rebind;
                    // we will deal with that later if it asks for one.
                    b.intent.doRebind = false;
                    s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent());
                } catch (Exception e) {
                    Slog.w(TAG, "Exception when unbinding service " + s.shortName, e);
                    serviceProcessGoneLocked(s);
                }
            }

            if ((c.flags&Context.BIND_AUTO_CREATE) != 0) {
                boolean hasAutoCreate = s.hasAutoCreateConnections();
                if (!hasAutoCreate) {
                    if (s.tracker != null) {
                        s.tracker.setBound(false, mAm.mProcessStats.getMemFactorLocked(),
                                SystemClock.uptimeMillis());
                    }
                }
                // 根据情况决定是否调用service.onDestroy()方法
                bringDownServiceIfNeededLocked(s, true, hasAutoCreate);
            }
        }
    }

scheduleUnbindService()会导致下面的方法在service的主线程中执行:

private void handleUnbindService(BindServiceData data) {
        Service s = mServices.get(data.token);
        if (s != null) {
            try {
                data.intent.setExtrasClassLoader(s.getClassLoader());
                data.intent.prepareToEnterProcess();
                // 执行service.onUnbind()生命周期方法
                boolean doRebind = s.onUnbind(data.intent);
                try {
                    if (doRebind) {
                        // 如果onUnbind()的返回值设置为true的话,
                        // 调用AMS.unbindFinished()
                        ActivityManagerNative.getDefault().unbindFinished(
                                data.token, data.intent, doRebind);
                    } else {
                        ActivityManagerNative.getDefault().serviceDoneExecuting(
                                data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
                    }
                } catch (RemoteException ex) {
                }
            } catch (Exception e) {
                if (!mInstrumentation.onException(s, e)) {
                    throw new RuntimeException(
                            "Unable to unbind to service " + s
                            + " with " + data.intent + ": " + e.toString(), e);
                }
            }
        }
    }

unbindeService()代码就说道这里,接下来总结下:

  1. ServiceConnection.onServiceDisconnected()并不会在unbindeService()过程中调用,它只会在service进程被终止时回调通知绑定的客户端;

  2. 只有在绑定这个service的所有客户端中的组件都执行了unbindeService()时,service才会被销毁,执行servcice.onDestroy(),这也就要求暗示我们当完成于服务的交互后,最好unbindeService,这样有利于系统及时回收service资源;

  3. 当目标service所在的进程被杀掉时(即除了正常回收service),系统并不会销毁之前的与该service绑定的组件创建的连接,一旦service后续再次运行,系统会再次回调onServiceConnected();

  4. 如果service是通过startService()启动的,那么service将一直运行到其通过 stopSelf() 自行停止,或其他组件调用 stopService() 为止,无论其是否绑定到任何客户端;

  5. unbindeService()操作也是异步操作的;

  6. 一般情况下,只要service被客户端绑定过了,当其再被绑定时,不会在调用service.onBind()方法了,也就是说通常service.onBind()只会执行一次;

  7. 同一个组件内使用同一个ServiceConnection接口对象,重复绑定一个service时,会在AMS中为起创建连接,但是不会导致ServiceConnection.onServiceConnected()方法执行

  8. 同一个组件内执行unbindeService(ServiceConnection sc)时,相当于对所有使用sc绑定的service执行一次unbindeService操作;

这里比较有趣的是,假设同一组件内使用sc重复绑定的一个servcie N次,那么这一次unbindeService(),当对于该该组件绑定该service来说执行了N次unbindeService操作。

生命周期的总结:

单独使用bindService(),unbindService()会经历:->onCreate()->onBind()->Service running->onUnbind() -> onDestroy();

单独使用startService(),stopService()会经历:->onCreate()->onStartCommand()->Service running-> onDestroy();

先调用startService(),再调用bindService()方法:

a. 如果结束只调用unbindService(),那么只会执行到onUnbind(),将不会执行onDestroy():->onCreate()->onStartCommand()->onBind()->Service running-> onUnbind();

b. 如果在unbindService后,在调用stopService(),那么:->onCreate()->onStartCommand()->onBind()->Service running-> onUnbind()->onDestroy();

service的生命周期方法都运行在主线程中,所以如果要在生命周期中执行耗时操作,请额外开启线程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容