Android Service演義

轉自:https://my.oschina.net/youranhongcha/blog/710046
摘要: 本文基於Android 5.1代碼,介紹了Android Service的運作機理。按理說,網上此類文章已經很多了,本不需我再贅述。但每個人理解技術的方式多少會有所不同,我多寫一篇自己理解的service,也未嘗不可吧。

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記錄到ServiceRecord的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的基本機制和啓動流程,我們就先說這麼多吧。以後我們再補充其他方面的內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章