文档章节

Android Service演义

悠然红茶
 悠然红茶
发布于 2016/07/11 20:48
字数 5905
阅读 1581
收藏 3

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,其继承关系如下图所示:

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

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

当然,如果用户进程和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 2.3版示意图)


(Android 5.1版示意图)

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

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

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

请注意上图中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()的调用示意图如下:

看到了吧,无非是利用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表格去,于是我们可以在前文示意图的基础上,再画一张新图:

这就是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()动作的用户进程中的样子:

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

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

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

说完了发起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也可以被多个用户进程绑定,这就是网状结构。示意图如下:

前文我们已经说过,绑定时使用的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<ConnectionRecord>记录下每个绑定动作对应的逻辑连接节点。

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


在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。我们不妨再画一张图看看:

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

© 著作权归作者所有

悠然红茶
粉丝 344
博文 20
码字总数 106144
作品 0
西安
高级程序员
私信 提问
加载中

评论(16)

我叫半桶水
我叫半桶水

引用来自“我叫半桶水”的评论

多谢大师的文章,收获很多。

有几处笔误,但不影响阅读:
---可以看到,token记录到ServiceRecord的mToken域了。
+++可以看到,token记录到Service的mToken域了。

---相传对于一个Service而言
+++相对于一个Service而言

---就是为了快速地找出所有使用相同IServiceConnection.Stub对象,完成绑定动作的ConnectionRecord节点
+++就是为了快速地找出所有使用相同IServiceConnection.Stub对象的ConnectionRecord节点,完成绑定动作

引用来自“悠然红茶”的评论

很少有读者这么仔细看我的文章了,特此感谢。关于你说的三处地方,前两处的确是笔误,但第三处似乎应该是这样的意思:就是为了快速地找出所有使用相同IServiceConnection.Stub对象,并且已完成绑定动作的ConnectionRecord节点。
你是对的。读文章的时候,我把接下来的connect调用当做“绑定”(想当然)了,其实绑定已经完成了。
悠然红茶
悠然红茶 博主

引用来自“我叫半桶水”的评论

多谢大师的文章,收获很多。

有几处笔误,但不影响阅读:
---可以看到,token记录到ServiceRecord的mToken域了。
+++可以看到,token记录到Service的mToken域了。

---相传对于一个Service而言
+++相对于一个Service而言

---就是为了快速地找出所有使用相同IServiceConnection.Stub对象,完成绑定动作的ConnectionRecord节点
+++就是为了快速地找出所有使用相同IServiceConnection.Stub对象的ConnectionRecord节点,完成绑定动作
很少有读者这么仔细看我的文章了,特此感谢。关于你说的三处地方,前两处的确是笔误,但第三处似乎应该是这样的意思:就是为了快速地找出所有使用相同IServiceConnection.Stub对象,并且已完成绑定动作的ConnectionRecord节点。
我叫半桶水
我叫半桶水
多谢大师的文章,收获很多。

有几处笔误,但不影响阅读:
---可以看到,token记录到ServiceRecord的mToken域了。
+++可以看到,token记录到Service的mToken域了。

---相传对于一个Service而言
+++相对于一个Service而言

---就是为了快速地找出所有使用相同IServiceConnection.Stub对象,完成绑定动作的ConnectionRecord节点
+++就是为了快速地找出所有使用相同IServiceConnection.Stub对象的ConnectionRecord节点,完成绑定动作
悠然红茶
悠然红茶 博主

引用来自“左衽余人”的评论

楼主属于有钻研精神也有能力的人,这么优秀的程序员搞android也就太可惜了。android源码越读越令人失望,进行调试的时候发现很多地方的代码都不会按作者的本意去运行,最后竟然也相安无事。给人的感觉就是庞大而杂乱,谷歌工程师自己也搞不明白了。TODO一大堆,从L版本的TODO,到O版本还是TODO。
就设计本身也跟玩笑一样,一个系统光溜溜连资源的保护机制都没有,我打个比方,我一个进程注册100个广播接收器,就能让整个系统都卡顿起来,虚拟机造成的性能损失结结实实的吃下了,它的沙盒机制带来的好处却形同虚设。
想想看世界上的三流应用该有多少啊,任何一个都可能把你的系统搞挂。android就好像一个艾滋病人,失去了全部免疫力,却赤身裸体在黑暗的雨林里游荡。
前段时间读View模块,measure和layout存在许多冗余,就更不一一细表,几个基础模块代码零零碎碎,写得跟狗一样,比如WM,窗口管理机制中的壁纸和输入法就很不明确很不健壮,容易出问题。各种场景的relayout等造成的动画也常出问题。一些N版本和O版本的新特性也是坑,例如startingWindow采用thumbnail,这种脑残的设计估计国内的三流产品都不会设计出来。总之种种问题实在多如牛毛,令人深深失望。
呵呵,是啊,如果google能写得好点儿,我们也不用那么幸苦了。
左衽余人
左衽余人
楼主属于有钻研精神也有能力的人,这么优秀的程序员搞android也就太可惜了。android源码越读越令人失望,进行调试的时候发现很多地方的代码都不会按作者的本意去运行,最后竟然也相安无事。给人的感觉就是庞大而杂乱,谷歌工程师自己也搞不明白了。TODO一大堆,从L版本的TODO,到O版本还是TODO。
就设计本身也跟玩笑一样,一个系统光溜溜连资源的保护机制都没有,我打个比方,我一个进程注册100个广播接收器,就能让整个系统都卡顿起来,虚拟机造成的性能损失结结实实的吃下了,它的沙盒机制带来的好处却形同虚设。
想想看世界上的三流应用该有多少啊,任何一个都可能把你的系统搞挂。android就好像一个艾滋病人,失去了全部免疫力,却赤身裸体在黑暗的雨林里游荡。
前段时间读View模块,measure和layout存在许多冗余,就更不一一细表,几个基础模块代码零零碎碎,写得跟狗一样,比如WM,窗口管理机制中的壁纸和输入法就很不明确很不健壮,容易出问题。各种场景的relayout等造成的动画也常出问题。一些N版本和O版本的新特性也是坑,例如startingWindow采用thumbnail,这种脑残的设计估计国内的三流产品都不会设计出来。总之种种问题实在多如牛毛,令人深深失望。
悠然红茶
悠然红茶 博主

引用来自“LEO9527”的评论

非常好啊.
楼主还在搞android么?不搞可惜了啊.
呵呵,还在搞Android,只是自己比较懒散,文章写得比较少。
LEO9527
LEO9527
非常好啊.
楼主还在搞android么?不搞可惜了啊.
悠然红茶
悠然红茶 博主

引用来自“PTrain”的评论

您好!我想问下bindService时候,onServiceConnected函数中会有个(IBinder service)这个参数,我看很多博客说,这个service对象如果是同一进程就是返回的binder对象,如果不同进程就是BinderProxy对象,这方面的具体流程和源码是什么样子的呢?
我看了看bindService源码的一些文章,onServiceConnected函数中(IBinder service)这个参数,就是Service的onBind方法返回的对象,那么这个对象怎么有时是Binder,有时是BinderProxy呢?具体是在哪里判断是否是同一进程,究竟返回哪个对象的呢?
刚入门不久,不知道问题是不是很蠢,希望博主解惑
你说的问题其实更应归入binder范畴,而不是service范畴。在Android平台上,只能对binder实体进行“逻辑上的”跨进程调用。如果调用方是在另一个进程,那么调用方操作的就是binder代理。而如果调用方和binder实体处于同一进程,那么其操作的就是binder实体的指针。具体到bindService时,onServiceConnected()中的service参数,的确对应的就是onBind()方法返回的对象,但是请注意,只是“对应关系”,并不是对象本体。onBind()返回的对象正是个binder实体,经由系统binder机制,最终传给onServiceConnected()时,就会按binder的标准行为运作。那么为什么位于不同进程的调用方操作的是binder代理,而相同进程的操作的就是指针呢?这涉及到binder机制的运作机理,有兴趣可以看看BBinder::transact()函数,注意其中调用的onTransact()的Parcel& data参数。该参数来源于binder驱动。
PTrain
PTrain
您好!我想问下bindService时候,onServiceConnected函数中会有个(IBinder service)这个参数,我看很多博客说,这个service对象如果是同一进程就是返回的binder对象,如果不同进程就是BinderProxy对象,这方面的具体流程和源码是什么样子的呢?
我看了看bindService源码的一些文章,onServiceConnected函数中(IBinder service)这个参数,就是Service的onBind方法返回的对象,那么这个对象怎么有时是Binder,有时是BinderProxy呢?具体是在哪里判断是否是同一进程,究竟返回哪个对象的呢?
刚入门不久,不知道问题是不是很蠢,希望博主解惑
悠然红茶
悠然红茶 博主

引用来自“weiyuxia”的评论

感谢,你的讲的靠谱又概况,比一些出书的扣代码强多了,请教两个问题:
1.ams是怎么查询service在哪个进程然后去启动那个进程的,是问pkms吗
2.看到好多书说binder相关操作都是在binder线程中进行的,server我知道,可client也是吗,client也会造两个binder线程然后专门去执行binder操作吗,那bindservice的时候还是主线程,它是如何切换线程的呢
2.看到好多书说binder相关操作都是在binder线程中进行的,server我知道,可client也是吗,client也会造两个binder线程然后专门去执行binder操作吗,那bindservice的时候还是主线程,它是如何切换线程的呢?

binder的通信分为“代理端”和“实体端”。代理端发起调用,而实体端执行具体的动作。你所说的“binder相关操作都是在binder线程中进行”指的其实是实体端的动作。也就是说实体端的那个onTransact()是在某个binder线程里调用的。我们也不太关心具体是哪个binder线程执行了动作,反正onTransact()被执行了就可以。
而在代理端一侧,情况简单一些,基本上在哪个线程调用了代理端接口,就在调用的地方阻塞,直到有结果return。所以你所说的client是不会造什么binder线程的,更没有什么切换线程的动作。
学习android之Service

学习android之Service 综述 Service是android 系统中的一种组件,它跟Activity的级别差不多,但是他不能自己运行,只能后台运行,并且可以和其他组件进行交互。service可以在很多场合的应用中...

长平狐
2012/10/08
526
0
Android SDK Document 框架导读的翻译和注解[6]——Activating components: intents【用Intent激活组件】

Activating components: intents【组件激活】 这里提到的Intent,类似于Content Provider,也是用户消息传递的, 但两者存在区别: 当一个从ContentResolver的请求的目标是Content Provider时,...

晨曦之光
2012/03/09
81
0
Android SDK Document 框架导读的翻译和注解[6]——Activating components: intents【用Intent激活组件】

Activating components: intents【组件激活】 这里提到的Intent,类似于Content Provider,也是用户消息传递的, 但两者存在区别: 当一个从ContentResolver的请求的目标是Content Provider时,...

晨曦之光
2012/03/07
148
0
Android应用是否可以只有一个Service或Broadcast Reciver,而没有Activity?

作者:chenjieb520 Service是android四大组件中与Activity最相似的组件,都可以代表可执行的程序。 Service与Activity的区别在于: (1)、Service一直在后台运行,没有用户界面。 (2)、一旦...

晨曦之光
2012/03/14
8.5K
3
Service的生命周期与Activity生命周期区别

碰到一面试题 简述activity/service生命周期? 组件的生命周期 应用程序组件都有一个生命周期,从响应Intent的Android实例开始到这个实例被销毁。在这期间,他们或许有效或许无效,有效时或许...

xiahuawuyu
2012/07/24
1K
0

没有更多内容

加载失败,请刷新页面

加载更多

数据流中的中位数

吐出的较小的N/2 个,都在大根堆里,较大的 N/2 个,都在小根堆里。 此时 5、4,都在大根堆,小根堆没有数。 此时应该从大根堆的堆顶弹出来,扔到小根堆里。 比如:先把 5 拿出来,再把堆最后...

Garphy
11分钟前
4
0
微服务下配置管理中心 SCCA

SCCA 简介 SCCA 全称 spring-cloud-config-admin,微服务下 Spring Boot 应用(包含 Spring Cloud)统一的配置管理平台。 Github 地址:spring-cloud-config-admin 核心贡献者:程序员DD | ...

SpringForA
13分钟前
5
0
spring 是如何注入对象的和bean 创建过程分析

文章目录: beanFactory 及 bean 生命周期起步 BeanFactory refresh 全过程 BeanFactoryPostProcessor 和 BeanPostProcessor 解析 使用 BeanPostProcessor 实现 aop 和 springboot Cache 相关......

sanri1993
16分钟前
8
0
@PathVariable使用

public interface ProductInfoRepository extends JpaRepository<ProductInfo, String>{ List<ProductInfo> findByProductId(String productId);} ProductInfoController @Autowired ......

观海562
27分钟前
6
0
利用CSS禁止手机长按出现气泡: 复制、选择等功能

可以用 * ,也可作用于一个div div{  -webkit-touch-callout:none;  /*系统默认菜单被禁用*/  -webkit-user-select:none; /*webkit浏览器*/  -khtml-user-select:none; /*早期浏览...

蓝小驴
今天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部