Android Service演义

Stella981
• 阅读 664

Android Service演义

(本文以Android 5.1为准)

侯亮

1.概述

在Android平台上,那种持续性工作一般都是由service来执行的。不少初学者总是搞不清service和线程、进程之间的关系,这当然会影响到他们开展具体的开发工作。

其实,简单说起来,service和线程、进程是没什么关系的。我们知道,在Android平台上已经大幅度地弱化了进程的概念,取而代之的是一个个有意义的逻辑实体,比如activity、service等。Service实体必然要寄身到某个进程里才行,它也可以再启动几个线程来帮它干活儿。但是,说到底service只是一个逻辑实体、一个运行期上下文而已。

相比activity这种“操控UI界面的运行期上下文”,service这种上下文一般是没有界面部分的。当然这里说的只是一般情况,有些特殊的service还是可以创建自己的界面的,比如当一个service需要显现某种浮动面板时,就必须自己创建、销毁界面了。

在Android系统内部的AMS里,是利用各种类型的Record节点来管理不同的运行期上下文的。比如以ActivityRecord来管理activity,以ServiceRecord来管理service。可是,线程这种东东可没有对应的Record节点喔。一些初学者常常会在activity里启动一个线程,从事某种耗时费力的工作,可是一旦activity被遮挡住,天知道它会在什么时候被系统砍掉,进而导致连应用进程也退出。从AMS的角度来看,它压根就不知道用户进程里还搞了个工作线程在干活儿,所以当它要干掉用户进程时,是不会考虑用户进程里还有没有工作没干完。

但如果是在service里启动了工作线程,那么AMS一般是不会随便砍掉service所在的进程的,所以耗时的工作也就可以顺利进行了。

可是,我们也常常遇到那种“一次性处理”的工作,难道就不能临时创建个线程来干活吗?关于这一点,请大家留意,在Android平台上应对这种情况的较好做法是创建一个IntentService派生类,而后覆盖其onHandleIntent()成员函数。IntentService内部会自动为你启动一个工作线程,并在工作线程里回调onHandleIntent()。当onHandleIntent()返回后,IntentService还会自动执行stopSelf()关闭自己。瞧瞧,多么自动化。

关于service,有两个动作是谁都绕不开的,那就是startService()和bindService()。我们想知道,它们的内部机制到底是怎样的?虽然网上已经有不少文章讲述过这两个动作,但是不同人理解技术的视角往往是不一样的,本文我将以自己的视角来阐述它们。

2.Service机制

我们先来看一下Service类的代码截选:
【frameworks/base/core/java/android/app/Service.java】

public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
    ......
    ......
    private ActivityThread mThread = null;
    private String mClassName = null;
    private IBinder mToken = null;
    private Application mApplication = null;
    private IActivityManager mActivityManager = null;
    private boolean mStartCompatibility = false;
}

Service是个抽象类,它间接继承于Context,其继承关系如下图所示:

Android Service演义

看了这张图,请大家务必理解,Service只是个“上下文”(Context)对象而已,它和进程、线程是没什么关系的。

在AMS中,负责管理service的ServiceRecord节点本身就是个binder实体。当AMS向应用进程发出语义,要求其创建service对象时,会把ServiceRecord通过binder机制“传递”给应用进程。这样,应用进程的ActivityThread在处理AMS发来的语义时,就可以得到一个合法的binder代理,这个binder代理最终会被记录在Service对象中,如此一来,Service实体就和系统里的ServiceRecord关联起来了。我们画个图来说明,假如一个应用进程里启动了两个不同的Service,那么当service创建成功之后,AMS和用户进程之间就会形成如下关系示意图:

Android Service演义

当然,如果用户进程和service再多一点儿也完全没问题,此时会形成下图:

Android Service演义

我们对图中的ApplicationThread并不陌生,它记录在ActivityThread中mAppThread域中。每当系统新fork一个用户进程后,就会自动执行ActivityThread的attach()动作,里面会调用:

final IActivityManager mgr = ActivityManagerNative.getDefault();
. . . . . .
    mgr.attachApplication(mAppThread);
. . . . . .

将ApplicationThread对象远程“传递”给AMS,从而让AMS得到一个合法的代理端。而当系统要求用户进程创建service时,就会通过这个合法的代理端向用户进程传递明确的语义。现在我们就从这里开始讲解细节吧。

3.先说startService()

我们先来看启动service的流程。要启动一个service,我们一般是调用startService()。说穿了只是向AMS发起一个请求,导致AMS执行如下的startService()动作:
【frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java】

public ComponentName startService(IApplicationThread caller, Intent service,
        String resolvedType, int userId) {
    . . . . . .
        ComponentName res = mServices.startServiceLocked(caller, service,
                resolvedType, callingPid, callingUid, userId);
        . . . . . .
        return res;
    }
}

其中的mServices域是ActiveServices类型的,其startServiceLocked()函数的代码截选如下:
【frameworks/base/services/core/java/com/android/server/am/ActiveServices.java】

ComponentName startServiceLocked(IApplicationThread caller,
        Intent service, String resolvedType,
        int callingPid, int callingUid, int userId) {
. . . . . .
    // 必须先通过retrieveServiceLocked()找到(或创建)一个ServiceRecord节点
    ServiceLookupResult res = retrieveServiceLocked(service, resolvedType,
                callingPid, callingUid, userId, true, callerFg);
    . . . . . .
    ServiceRecord r = res.record;
    . . . . . .
    r.lastActivity = SystemClock.uptimeMillis();
    r.startRequested = true;
    r.delayedStop = false;
    r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
            service, neededGrants));

    final ServiceMap smap = getServiceMap(r.userId);
    . . . . . .
    . . . . . .
    return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
}

看到了吗?必须先通过retrieveServiceLocked()找到(或创建)一个ServiceRecord节点,而后才能执行后续的启动service的动作。请大家注意,在Android frameworks里有不少这样的动作,基本上都是先创建xxxRecord节点,而后再向目标用户进程传递语义,创建具体的对象。

retrieveServiceLocked()的代码截选如下:

private ServiceLookupResult retrieveServiceLocked(Intent service,
        String resolvedType, int callingPid, int callingUid, int userId,
        boolean createIfNeeded, boolean callingFromFg) {
    ServiceRecord r = null;
    . . . . . .
    ServiceMap smap = getServiceMap(userId);
    final ComponentName comp = service.getComponent();
    if (comp != null) {
        r = smap.mServicesByName.get(comp);
    }
    if (r == null) {
        Intent.FilterComparison filter = new Intent.FilterComparison(service);
        r = smap.mServicesByIntent.get(filter);
    }
    if (r == null) {
        . . . . . .
            // 从PKMS处查到ServiceInfo
            . . . . . .
            ComponentName name = new ComponentName(
                    sInfo.applicationInfo.packageName, sInfo.name);
            . . . . . .
            r = smap.mServicesByName.get(name);
            if (r == null && createIfNeeded) {
                . . . . . .
                r = new ServiceRecord(mAm, ss, name, filter, sInfo, callingFromFg, res);
                . . . . . .
                smap.mServicesByName.put(name, r);
                smap.mServicesByIntent.put(filter, r);
                . . . . . .
            }
        . . . . . .
    }
    if (r != null) {
        . . . . . .
        return new ServiceLookupResult(r, null);
    }
    return null;
}

总之就是希望在AMS内部的相关表格里找到对应的ServiceRecord节点,如果找不到,就创建一个新节点,并插入到相应的表格中。

我手头参考的是Android5.1的代码,可以说这张表格大体上就是ServiceMap里的mServicesByName啦。当然,ServiceRecord节点一般还会同时记录到其他表格里,比如mServicesByIntent表格,但现在我们不妨先只考虑mServicesByName,一切以便于理解为上。

事实上,在早期的Android代码(比如Android 2.3版的代码)中,是没有那个ServiceMap的,那时的mServicesByName表格直接位于ActivityManagerService里,而且对应的域名叫作mServices。后来随着Android的发展,需要支持多用户以及其他一些概念,于是就搞出了个ServiceMap,并把原来的这张ServiceRecord表格搬到ServiceMap里了。我们可以画一张示意图,来说明Android代码的变迁:

Android Service演义

(Android 2.3版示意图)

Android Service演义
(Android 5.1版示意图)

但是,不管ServiceRecord表格被放到哪里,其本质都是一致的。而AMS必须保证在实际启动一个Service之前查到或创建对应的ServiceRecord节点。

在ServiceRecord类中有不少成员变量,其中有一个app域,专门用来记录Service对应的用户进程的ProcessRecord。当然,一开始这个app域是为null的,也就是说ServiceRecord节点还没有和用户进程关联起来,待Service真正启动之后,ServiceRecord的app域就有实际的值了。

现在我们继续看startServiceLocked()函数,它在找到ServiceRecord节点之后,开始调用startServiceInnerLocked()。我们可以绘制一下相关的调用关系:

Android Service演义

请注意上图中bringUpServiceLocked()里的内容。此时大体上分三种情况:
1)如果ServiceRecord已经和Service寄身的用户进程关联起来了,此时ServiceRecord的app域以及app.thread域都是有值的,那么只调用 sendServiceArgsLocked()即可。这一步会让Service间接走到大家熟悉的onStartCommand()。
2)如果尚未启动Service寄身的用户进程,那么需要调用mAm.startProcessLocked()启动用户进程。
3)如果Service寄身的用户进程已经启动,但尚未和ServiceRecord关联起来,那么调用realStartServiceLocked(r, app, execInFg);这种情况下,会让Service先走到onCreate(),而后再走到onStartCommand()。

我们重点看第三种情况,realStartServiceLocked()的调用示意图如下:

Android Service演义

看到了吧,无非是利用scheduleXXX()这样的函数,来通知用户进程去做什么事。以后大家看到这种以schedule打头的函数,可以直接打开ActivityThread.java文件去查找其对应的实现函数。比如scheduleCreateService()的代码如下:
【frameworks/base/core/java/android/app/ActivityThread.java】

public final void scheduleCreateService(IBinder token,
        ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
    updateProcessState(processState, false);
    CreateServiceData s = new CreateServiceData();
    s.token = token;
    s.info = info;
    s.compatInfo = compatInfo;

    sendMessage(H.CREATE_SERVICE, s);
}

它发出的CREATE_SERVICE消息,会由ActivityThread的内嵌类H处理,H继承于Handler,而且是在UI主线程里处理消息的。可以看到,处理CREATE_SERVICE消息的函数是handleCreateService():

case CREATE_SERVICE:
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceCreate");
    handleCreateService((CreateServiceData)msg.obj);
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    break;

另外,请大家注意realStartServiceLocked()传给scheduleCreateService()函数的第一个参数就是ServiceRecord类型的r,而ServiceRecord本身是个Binder实体噢。待“传到”应用进程时,这个Binder实体对应的Binder代理被称作token,记在了CreateServiceData对象的token域中。现在CreateServiceData对象又经由msg.obj传递到消息处理函数里,并进一步作为参数传递给handleCreateService()函数。

handleCreateService()的代码如下:

private void handleCreateService(CreateServiceData data) {
    . . . . . .
    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    . . . . . .
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        service = (Service) cl.loadClass(data.info.name).newInstance();
    . . . . . .
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        context.setOuterContext(service);

        Application app = packageInfo.makeApplication(false, mInstrumentation);
 
        // 注意,ServiceRecord实体对应的代理端,就是此处的data.token。
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManagerNative.getDefault());
        service.onCreate();
        mServices.put(data.token, service);
    . . . . . .
}

简单地说就是,先利用反射机制创建出Service对象:

service = (Service) cl.loadClass(data.info.name).newInstance();

然后再调用该对象的onCreate()成员函数:

service.onCreate();

而因为handleCreateService()函数本身是在用户进程的UI主线程里执行的,所以service的onCreate()函数也就是在UI主线程里执行的。常常会有人告诫新手,不要在onCreate()、onStart()里执行耗时的操作,现在大家知道是为什么了吧,因为在UI主线程里执行耗时的操作不但会引起界面卡顿,严重的还会导致ANR报错。

另外,在执行到上面的service.attach()时,那个和ServiceRecord对应的代理端token也传进来了:

public final void attach(
        Context context,
        ActivityThread thread, String className, IBinder token,
        Application application, Object activityManager) {
    attachBaseContext(context);
    mThread = thread;           
    mClassName = className;
    mToken = token;     // 注意这个token噢,它的对端就是AMS里的ServiceRecord!
    mApplication = application;
    mActivityManager = (IActivityManager)activityManager;
    mStartCompatibility = getApplicationInfo().targetSdkVersion
            < Build.VERSION_CODES.ECLAIR;
}

可以看到,token记录到Service的mToken域了。

最后,新创建出的Service对象还会记录进ActivityThread的mServices表格去,于是我们可以在前文示意图的基础上,再画一张新图:

Android Service演义

这就是startService()启动服务的大体过程。上图并没有绘制“调用startService()的用户进程”,因为当Service启动后,它基本上就和发起方没什么关系了。

4.再说bindService()

接下来我们来说说bindService(),它可比startService()要麻烦一些。当一个用户进程bindService()时,它需要先准备好一个实现了ServiceConnection接口的对象。ServiceConnection的定义如下:

public interface ServiceConnection {
    public void onServiceConnected(ComponentName name, IBinder service);
    public void onServiceDisconnected(ComponentName name);
}

在Android平台上,每当用户调用bindService(),Android都将之视作是要建立一个新的“逻辑连接”。而当连接建立起来时,系统会回调ServiceConnection接口的onServiceConnected()。另一方面,那个onServiceDisconnected()函数却不是在unbindService()时发生的。一般来说,当目标service所在的进程意外挂掉或者被杀掉时,系统才会回调onServiceDisconnected(),而且,此时并不会销毁之前的逻辑连接,也就是说,那个“逻辑连接”仍然处于激活状态,一旦service后续再次运行,系统会再次回调onServiceConnected()。

在Android平台上,要和其他进程建立逻辑连接往往都需要利用binder机制。那么,发起bindService()的用户进程又是在哪里创建逻辑连接需要的binder实体呢?我们可以看看bindServiceCommon()函数的代码截选:
【frameworks/base/core/java/android/app/ContextImpl.java】

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
        UserHandle user) {
    IServiceConnection sd;
    . . . . . .
        sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
                mMainThread.getHandler(), flags);   // 请注意返回的sd!
    . . . . . .
        IBinder token = getActivityToken();
        . . . . . . 
        int res = ActivityManagerNative.getDefault().bindService(
            mMainThread.getApplicationThread(), getActivityToken(),
            service, service.resolveTypeIfNeeded(getContentResolver()),
            sd, flags, user.getIdentifier());
    . . . . . .
}

请大家注意mPackageInfo.getServiceDispatcher()那一句,它返回的就是binder实体。此处的mPackageInfo是用户进程里和apk对应的LoadedApk对象。getServiceDispatcher()的代码如下:
【frameworks/base/core/java/android/app/LoadedApk.java】

public final IServiceConnection getServiceDispatcher(ServiceConnection c,
        Context context, Handler handler, int flags) {
    synchronized (mServices) {
        LoadedApk.ServiceDispatcher sd = null;
        ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = 
                                                                         mServices.get(context);
        if (map != null) {
            sd = map.get(c);
        }
        if (sd == null) {
            sd = new ServiceDispatcher(c, context, handler, flags);
            if (map == null) {
                map = new ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>();
                mServices.put(context, map);
            }
            map.put(c, sd);
        } else {
            sd.validate(context, handler);
        }
        return sd.getIServiceConnection();  // 注意,返回ServiceDispatcher内的binder实体
    }
}

也就是说,先尝试在LoadedApk的mServices表中查询ServiceDispatcher对象,如果查不到,就重新创建一个。ServiceDispatcher对象会记录下从用户处传来的ServiceConnection引用。而且,ServiceDispatcher对象内部还含有一个binder实体,现在我们可以通过调用sd.getIServiceConnection()一句,返回这个内部的binder实体。

现在我们画一张示意图,来说明发起bindService()动作的用户进程中的样子:

Android Service演义

LoadedApk中的mServices是常见的表中表形式,定义如下:

private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices

这里比较有趣的是,第一层表的key类型为Context,大家不妨想一想,如果我们的应用里有两个activity都去绑定同一个Service会怎么样?很明显,在mServices表里就会有两个不同的子表,也会创建出两个不同的ServiceDispatcher对象,而且即便我们在调用bindService()时传入同一个ServiceConnection对象,依旧会有两个ServiceDispatcher对象。示意图如下:

Android Service演义

说完了发起bindService()动作的用户进程一侧,接下来我们再来看AMS一侧的代码。对应的bindService()代码如下:
【frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java】

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

    // Refuse possible leaked file descriptors
    if (service != null && service.hasFileDescriptors() == true) {
        throw new IllegalArgumentException("File descriptors passed in Intent");
    }

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

主要工作集中在mServices.bindServiceLocked()一句。请注意bindService()的倒数第三个参数,它对应的就是上图中ServiceDispatcher的mIServiceConnection域记录的binder实体。至于bindService()的第二个参数IBinder token,其实记录的是发起绑定动作的Activity的token,当然,如果发起者是其他非Activity型的对象,那么这个参数应该是null。

上面的代码最终走到mServices.bindServiceLocked()一句。我们摘录一下bindServiceLocked()的代码:
【frameworks/base/services/core/java/com/android/server/am/ActiveServices.java】

int bindServiceLocked(IApplicationThread caller, IBinder token,
            Intent service, String resolvedType,
            IServiceConnection connection, int flags, int userId) {
    . . . . . .
    final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
    . . . . . .
    ActivityRecord activity = null;
    . . . . . .
        activity = ActivityRecord.isInStackLocked(token);
    . . . . . .
    ServiceLookupResult res = retrieveServiceLocked(service, resolvedType,
                Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg);
    . . . . . .
    ServiceRecord s = res.record;
    . . . . . . 
        AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);  // 注意这个b!
        ConnectionRecord c = new ConnectionRecord(b, activity,
                connection, flags, clientLabel, clientIntent);

        IBinder binder = connection.asBinder();
        ArrayList<ConnectionRecord> clist = s.connections.get(binder);
        . . . . . .
        clist.add(c);
        b.connections.add(c);
        . . . . . .
        b.client.connections.add(c);
        . . . . . .
        clist = mServiceConnections.get(binder);
        . . . . . .
        clist.add(c);

        if ((flags&Context.BIND_AUTO_CREATE) != 0) {
            . . . . . .
            if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {
                return 0;
            }
        }
        . . . . . .
        if (s.app != null && b.intent.received) {
            . . . . . .
                c.conn.connected(s.name, b.intent.binder);  // 注意这个!
            . . . . . .
            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);  
        }
    . . . . . .
}

尽管这个函数里有不少技术细节,而且涉及到多个映射表,但它的总体意思大概是这样的。每当用户调用bindService()时,Android都将之看作是要建立一个新的“逻辑连接”,而每个逻辑连接都对应一个ConnectionRecord节点,所以最终的表现肯定是向ServiceRecord内部的某张映射表里添加一个新的ConnectionRecord节点。当然,在实际运作时,这个节点还会记录进其他几个映射表里(比如系统总映射表),但这不影响我们理解问题。

那么为什么系统里会有那么多映射表呢?这大概是为了在复杂的网状联系中快速便捷地找到相关的节点。我们知道,一个用户进程可以绑定多个Service,而一个Service也可以被多个用户进程绑定,这就是网状结构。示意图如下:

Android Service演义

前文我们已经说过,绑定时使用的ServiceConnection对象在发起端其实对应了一个ServiceDispatcher对象,现在,ServiceDispatcher的mIServiceConnection传递给bindServiceLocked(),也就是那个IServiceConnection connection参数。如果系统要建立“逻辑连接”,那么就需要把IServiceConnection代理端记录到ConnectionRecord节点里。

ConnectionRecord c = new ConnectionRecord(b, activity, connection, flags, 
                                                  clientLabel, clientIntent);

另外,请大家注意bindServiceLocked()里那个重要的AppBindRecord节点。

AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);  // 注意这个b!

相对于一个Service而言,有多少应用和它建立了绑定关系,就会有多少个AppBindRecord节点,要不怎么叫App-Bind呢?当然,一个应用里可以有多个地方发起绑定动作,所以AppBindRecord里需要用一个ArraySet记录下每个绑定动作对应的逻辑连接节点。

有了这些认识后,我们可以画一张“发起端用户进程”和“系统进程”之间的示意图:

Android Service演义

在ConnectionRecord被记录进合适的表后,要开始和目标service建立连接了。我们可以看到,bindServiceLocked()会尝试呼叫起目标service。

        if ((flags&Context.BIND_AUTO_CREATE) != 0) {
            . . . . . .
            if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {
                return 0;
            }
        }

看到这句if语句,大家应该知道调用bindService()时为什么常常要加上BIND_AUTO_CREATE了吧:

bindService(intent, conn, Context.BIND_AUTO_CREATE);

假设目标Service之前已经启动过,那么现在的绑定流程会走到if (s.app != null && b.intent.received)分支里,于是调用到c.conn.connected()。

        if (s.app != null && b.intent.received) {
            . . . . . .
                c.conn.connected(s.name, b.intent.binder);  // 注意这个!
            . . . . . .
            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);  
        }

但是,如果Service之前并未启动,而且这次是我们首次绑定service,那么不就到不了c.conn.connected()了吗?此时应该会走到else if 分支,调用到requestServiceBindingLocked(),该函数主要是向目标service发起绑定的请求,但是现在连service寄身的进程可能都还没有启动,request又有什么意义呢?所以,此处调用requestServiceBindingLocked()也许不会有什么重大意义。这并不是说requestServiceBindingLocked()不重要,而是说它真正起作用的地方也许不在这里。为了说明问题,我们得看一下刚刚调用的bringUpServiceLocked()函数:

private final String bringUpServiceLocked(ServiceRecord r,
        int intentFlags, boolean execInFg, boolean whileRestarting) {
    . . . . . .
    ProcessRecord app;
    . . . . . .
    if (app == null) {
        if ((app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                "service", r.name, false, isolated, false)) == null) {
            . . . . . .
            bringDownServiceLocked(r);
            return msg;
        }
        . . . . . .
    }

    // 注意此处的mPendingServices!
    if (!mPendingServices.contains(r)) {
        mPendingServices.add(r);
    }
    . . . . . .
}

bringUpServiceLocked()通过调用mAm.startProcessLocked()启动目标service寄身的进程,而后会把ServiceRecord节点记入mPendingServices数组列表中。

待后续service寄身的进程成功启动后,会辗转调用到attachApplicationLocked(),该函数的代码截选如下:

boolean attachApplicationLocked(ProcessRecord proc, String processName)
        throws RemoteException {
    . . . . . .
    if (mPendingServices.size() > 0) {
        ServiceRecord sr = null;
        . . . . . .
            for (int i=0; i<mPendingServices.size(); i++) {
                sr = mPendingServices.get(i);
                . . . . . .
                mPendingServices.remove(i);
                i--;
                proc.addPackage(sr.appInfo.packageName, sr.appInfo.versionCode,
                        mAm.mProcessStats);
                realStartServiceLocked(sr, proc, sr.createdFromFg);
            . . . . . .
    }
    . . . . . .
}

也就是说,当目标service寄身的进程启动后,会从mPendingServices数组列表中把ServiceRecord节点删除,并进一步调用realStartServiceLocked():

private final void realStartServiceLocked(ServiceRecord r,
        ProcessRecord app, boolean execInFg) throws RemoteException {
    . . . . . .
    r.app = app;
    . . . . . .
    app.services.add(r);
    . . . . . .
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);
    . . . . . .
    requestServiceBindingsLocked(r, execInFg);
    . . . . . .
    sendServiceArgsLocked(r, execInFg, true);
    . . . . . .
}

此处调用的app.thread.scheduleCreateService()会间接导致目标service走到大家熟悉的onCreate()。而后还会调用requestServiceBindingsLocked()。这里大概才是requestServiceBindingLocked()真正起作用的地方。

requestServiceBindingsLocked()的代码如下:

private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg) {
    for (int i=r.bindings.size()-1; i>=0; i--) {
        IntentBindRecord ibr = r.bindings.valueAt(i);
        if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {
            break;
        }
    }
}

private final boolean requestServiceBindingLocked(ServiceRecord r,
        IntentBindRecord i, boolean execInFg, boolean rebind) {
    . . . . . .
            r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
            r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
                    r.app.repProcState);
            if (!rebind) {
                i.requested = true;
            }
            i.hasBound = true;
            i.doRebind = false;
    . . . . . .
}

终于看到调用scheduleBindService()了。

到了“Service所属的进程”里,scheduleBindService()执行的代码如下:

public final void scheduleBindService(IBinder token, Intent intent,
        boolean rebind, int processState) {
    updateProcessState(processState, false);
    BindServiceData s = new BindServiceData();
    s.token = token;        // 对应AMS里的ServiceRecord
    s.intent = intent;
    s.rebind = rebind;
    ......
    sendMessage(H.BIND_SERVICE, s);
}

所发出的BIND_SERVICE消息,会导致service寄身的进程走到handleBindService():
【frameworks/base/core/java/android/app/ActivityThread.java】

private void handleBindService(BindServiceData data) {
    Service s = mServices.get(data.token);
    . . . . . .
                if (!data.rebind) {
                    IBinder binder = s.onBind(data.intent);
                    ActivityManagerNative.getDefault().publishService(
                            data.token, data.intent, binder);
                } else {
                    s.onRebind(data.intent);
                    ActivityManagerNative.getDefault().serviceDoneExecuting(
                            data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
                }
    . . . . . .
}

回调了目标service的onBind(),而后向AMS发布自己,即调用publishService()。

publishService()的第一个参数指代AMS里的ServiceRecord节点,而最后一个参数是目标Service的onBind()函数返回的服务对象。因此publish函数其实起的是衔接的作用。

在AMS一侧,publish动作最终会走到publishServiceLocked():
【frameworks/base/services/core/java/com/android/server/am/ActiveServices.java】

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;   // 记录下目标进程对应的binder代理
                b.requested = true;
                b.received = true;
                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);
                        . . . . . . 
                            c.conn.connected(r.name, service);  // 通告bindService发起方
                        . . . . . .
                    }
                }
            }
    . . . . . .
}

这里又牵扯到ServiceRecord的connections映射表,在介绍bindServiceLocked()函数时,我们其实已经看到过几句相关的代码了,现在再列举一下:

        IBinder binder = connection.asBinder();
        ArrayList<ConnectionRecord> clist = s.connections.get(binder);
        . . . . . .
        clist.add(c);

也就是说,我们在绑定服务时创建的那个ConnectionRecord节点,会同时将该节点记录进ServiceRecord的connections映射表,而映射表的key值就是逻辑上指向发起端的IServiceConnection代理。

为什么要有这个映射表呢?很简单,就是为了快速地找出所有使用相同IServiceConnection.Stub对象,并且已完成绑定动作的ConnectionRecord节点。请大家设想,我们完全可以在一个Activity里,使用同一个ServiceConnection对象发起多次绑定同一个service的动作,发起绑定时使用的intent也许会不同,但最终有可能绑定到同一个service,此时,上面代码中s.connections.get(binder)得到的ArrayList就会含有多个元素啦。

好,介绍完connections映射表,我们继续看publishServiceLocked()里的那句c.conn.connected()。ConnectionRecord节点的conn成员也是IServiceConnection类型的代理端,所以这一句调用最终是远程调用到发起端的ServiceDispatcher里的mIServiceConnection对象,该对象是InnerConnection类型的。

InnerConnection的connected()函数如下:
【frameworks/base/core/java/android/app/LoadedApk.java】

private static class InnerConnection extends IServiceConnection.Stub {
    final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;
    . . . . . .
    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) {
    if (mActivityThread != null) {
        mActivityThread.post(new RunConnection(name, service, 0));
    } else {
        doConnected(name, service);
    }
}

private final class RunConnection implements Runnable {
    . . . . . .
    public void run() {
        if (mCommand == 0) {
            doConnected(mName, mService);
        } else if (mCommand == 1) {
            doDeath(mName, mService);
        }
    }
    . . . . . .
}

【frameworks/base/core/java/android/app/LoadedApk.java】

public void doConnected(ComponentName name, IBinder service) {
    . . . . . .
            info = new ConnectionInfo();
            info.binder = service;
            info.deathMonitor = new DeathMonitor(name, service);
            try {
                service.linkToDeath(info.deathMonitor, 0);
                mActiveConnections.put(name, info);
            } catch (RemoteException e) {
                . . . . . .
            }
    . . . . . .
    // If there is a new service, it is now connected.
    if (service != null) {
        mConnection.onServiceConnected(name, service);   // 终于看到onServiceConnected了!
    }
}

至此,我们终于看到大家熟悉的onServiceConnected()回调啦!而传来的service参数,就是我们希望绑定的那个service提供的binder代理。现在我们可以说已经打通了bindService()动作涉及的三方关系:发起方、AMS、目标Service。我们不妨再画一张图看看:

Android Service演义

有关Service的基本机制和启动流程,我们就先说这么多吧。以后我们再补充其他方面的内容。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这