startService() 流程分析

總結(jié)了幾篇系統(tǒng)底層相關(guān)的文章掩缓,終于要接觸到應(yīng)用層了揽浙,不過需要提前掌握 Binder架構(gòu)料仗,系統(tǒng)啟動流程湾盗,進(jìn)程啟動流程 的相關(guān)姿勢,不然很難理清整個流程立轧。相對于 startAcitivity()格粪,startService() 的流程相對簡單,因為不涉及界面相關(guān)的操作氛改,便于理清用戶進(jìn)程帐萎,AMS 所在進(jìn)程(也即 SystemServer 進(jìn)程)和 服務(wù)所在進(jìn)程三者之間的關(guān)系。

1. 整體流程

這里借用 gityuan 的一篇文章 里的圖:

startService整體流程

這個圖畫的非常好胜卤,關(guān)鍵點基本上都標(biāo)注出來了疆导,涉及到三個進(jìn)程的交互(當(dāng)然Remote Service 進(jìn)程也可以和 Process A 進(jìn)程是同一個,那樣就省略2葛躏、3創(chuàng)建進(jìn)程的步驟)澈段。重點注意整個流程中涉及到通過 Binder 進(jìn)行跨進(jìn)程交互的部分,如果不熟悉 Binder 架構(gòu)舰攒,很難理解败富。下面以從一個 Activity 里面調(diào)用 startActivity() 啟動服務(wù)為例,進(jìn)行詳細(xì)分析摩窃。

2. ContextImpl.startService()

Activity -> ContextThemeWrapper -> ContextWrapper兽叮,ContextWrapper 是個代理類,實際功能都由其成員變量 Context mBase 實現(xiàn)偶芍,mBase 實際上是一個 ContextImpl 對象(具體怎么來的充择,以后的文章會講到,簡單的說是啟動Activity 時注入的)匪蟀。在 Application 或者 Service 里面啟動 startService 也是一樣椎麦,最終都調(diào)用到 ContextImpl.startService():

 @Override
    public ComponentName startService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, mUser);
    }
 private ComponentName startServiceCommon(Intent service, UserHandle user) {
        try {
            ...
            // Binder 調(diào)用 AMS
            ComponentName cn = ActivityManagerNative.getDefault().startService(
                mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                            getContentResolver()), getOpPackageName(), user.getIdentifier());
            ...
            }
            return cn;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

這里有兩個關(guān)鍵點:

  • ActivityManagerNative.getDefault() 獲取了 ActivityManagerService (AMS)的遠(yuǎn)程代理對象 ActivityManagerProxy,并發(fā)起了 startService 的請求
  • mMainThread.getApplicationThread()材彪,這里 mMainThread 是一個 ActivityThread 對象观挎,getApplicationThread() 返回 ActivityThread 的成員變量 ApplicationThread mAppThread琴儿,ApplicationThread 是 ActivityThread 的一個內(nèi)部類,看定義:
private class ApplicationThread extends ApplicationThreadNative {
        ...
}

明顯也是一個 Binder 架構(gòu)嘁捷,把這個 Binder 對象傳遞給 AMS造成,是為了 AMS 與發(fā)起 startService 的調(diào)用進(jìn)程通信,后面會具體分析雄嚣。

到這里晒屎,發(fā)起 startService 請求的部分就完成了,接下來會通過 Binder 跨進(jìn)程調(diào)用缓升,進(jìn)入到 AMS 中繼續(xù)分析鼓鲁。

3. AMS.startService()

通過 Binder
機(jī)制,ActivityManagerProxy.startService ----跨進(jìn)程---> ActivityManagerNative.onTransact() --> AMS.startService()港谊,AMS 是 AMN 的子類骇吭,實現(xiàn)了其 startService 方法。
AMS 中的 startService 方法:

@Override
    public ComponentName startService(IApplicationThread caller, Intent service,
            String resolvedType, String callingPackage, int userId)
            throws TransactionTooLargeException {
        ...
        synchronized(this) {
            final int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            ComponentName res = mServices.startServiceLocked(caller, service,
                    resolvedType, callingPid, callingUid, callingPackage, userId);
            ...
            return res;
        }
    }

這里 IApplicationThread caller 就是發(fā)起進(jìn)程在 AMS 所在的進(jìn)程中的代理歧寺。mService 是 一個 ActiveServices 類型的成員變量燥狰,其持有 AMS 的引用。

4. ActiveServices

4.1 startServiceLocked()

這個方法源碼比較長斜筐,這里只保留關(guān)鍵部分:

ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
            int callingPid, int callingUid, String callingPackage, final int userId)
            throws TransactionTooLargeException {
        ...
        if (caller != null) {
            final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller); // 1. 查找調(diào)用進(jìn)程是否存在
            if (callerApp == null) {
                throw new SecurityException(
                        "Unable to find app for caller " + caller
                        + " (pid=" + Binder.getCallingPid()
                        + ") when starting service " + service);
            }
            callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
        } else {
            callerFg = true;
        }
        ...
        ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType, callingPackage,
                    callingPid, callingUid, userId, true, callerFg, false); // 2. 查找要啟動的服務(wù)是否已經(jīng)存在
        ...
        ServiceRecord r = res.record;
        return startServiceInnerLocked(smap, service, r, callerFg, addToStarting)       

兩個關(guān)鍵點已經(jīng)用注釋中標(biāo)記出來龙致,retrieveServiceLocked() 內(nèi)部流程也比較復(fù)雜,有各種條件奴艾、權(quán)限的檢查等净当,這里不深入分析了,簡單說就是有一個 Map 用來保存用戶所有已啟動的服務(wù)蕴潦,如果不存在符合條件的 ServiceRecord像啼,就新建一個并保存在 Map 中。最后流程進(jìn)入到 startServiceInnerLocked() --> bringUpServiceLocked()

4.2 bringUpServiceLocked()

仍然先貼出簡化后的代碼:

    private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
            boolean whileRestarting, boolean permissionsReviewRequired)
            throws TransactionTooLargeException {

        if (r.app != null && r.app.thread != null) {
            //1.  Service 所在的進(jìn)程和 Service 線程都已經(jīng)啟動的情況
            sendServiceArgsLocked(r, execInFg, false);
            return null;
        }
        ...
        if (!isolated) {
            // 2.查找服務(wù)對應(yīng)的進(jìn)程是否存在
            app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false); 
            ...
            if (app != null && app.thread != null) {
                try {
                    app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
                    // 3. 如果進(jìn)程已經(jīng)存在潭苞,直接啟動服務(wù)
                    realStartServiceLocked(r, app, execInFg);
                    return null;
                } catch (TransactionTooLargeException e) {
                    throw e;
                } catch (RemoteException e) {
                    Slog.w(TAG, "Exception when starting service " + r.shortName, e);
                }
            }
        }
        ...
        if (app == null && !permissionsReviewRequired) {
            // 4. 如果進(jìn)程不存在忽冻,啟動進(jìn)程
            if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                    "service", r.name, false, isolated, false)) == null) {
                ...
            }
            ...
        }        

幾個關(guān)鍵點已經(jīng)注釋標(biāo)明:

  1. 如果服務(wù)所在進(jìn)程和線程都已經(jīng)啟動(r.app != null && r.app.thread != null),那么調(diào)用 sendServiceArgsLocked()此疹,這個方法里的關(guān)鍵代碼只有一句:
    r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
    r.app.thread 就是發(fā)起 startService 的進(jìn)程在 AMS 中的遠(yuǎn)程代理對象僧诚,這里又通過了 Binder 跨進(jìn)程調(diào)用,最終調(diào)用到 ApplicationThreadNative.onTransact() --> ApplicationThread.scheduleServiceArgs():
public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
            int flags ,Intent args) {
            ServiceArgsData s = new ServiceArgsData();
            s.token = token;
            s.taskRemoved = taskRemoved;
            s.startId = startId;
            s.flags = flags;
            s.args = args;

            sendMessage(H.SERVICE_ARGS, s);
        }

調(diào)用該方法后蝗碎,ApplicationThread 會把消息 post 到主線程的 handler 處理湖笨,對應(yīng) SERVICE_ARGS 的處理函數(shù)是 handleServiceArgs(),在這個方法里蹦骑,終于看到了我們熟悉的 Service.onStartCommand() 慈省!這也是為什么當(dāng)反復(fù)啟動 Service 的時候,只有 onStartCommand() 會被反復(fù)調(diào)用眠菇。

  1. 如果服務(wù)所在進(jìn)程已經(jīng)存在边败,那么進(jìn)入 realStartServiceLocked() 啟動服務(wù)袱衷,這里也不貼出大段源碼了,關(guān)鍵代碼有兩處:
app.thread.scheduleCreateService(r, r.serviceInfo,
                    mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                    app.repProcState);
...
sendServiceArgsLocked(r, execInFg, true);

和第一種情況類似笑窜,這里也是兩個 Binder 跨進(jìn)程調(diào)用致燥,最終分別調(diào)用到 Service 的 onCreate() 和 onStartCommand 生命周期,這又是我們非常熟悉的部分了排截。

  1. 如果服務(wù)所在進(jìn)程不存在嫌蚤,那么調(diào)用 AMS.startProcessLocked 啟動進(jìn)程。在上一篇 Android 進(jìn)程啟動流程總結(jié) 中已經(jīng)分析了從 AMS.startProcessLocked 開始的流程断傲,這里終于找到了它的調(diào)用的源頭之一搬葬,就是當(dāng)啟動的服務(wù)所在進(jìn)程還不存在時,就要調(diào)用 AMS.startProcessLocked 先啟動對應(yīng)的進(jìn)程艳悔。在上篇文章的最后提到,進(jìn)程啟動后會調(diào)用 ActivityThread.main() 方法 -->ActivityThread.attach() --> ActivityManagerProxy.attachApplication() --Binder跨進(jìn)程調(diào)用--> AMS.attachApplication() --> AMS.attachApplicationLocked() --> ActiveServices.attachApplicationLocked() --> ActiveServices.realStartServiceLocked()女仰,終于殊途同歸猜年!回到了和第2種情況一樣的函數(shù)。

至此疾忍,startService() 的整個流程就走完了乔外。

5. 總結(jié)

startService() 的流程看起來有些復(fù)雜,但是只要先建立了如第一節(jié)所示的三個進(jìn)程間交互的整體結(jié)構(gòu)圖一罩,那么整體思路還是很清晰的杨幼,這中間涉及了 Binder 和 Socket 兩種跨進(jìn)程調(diào)用(上一篇介紹的進(jìn)程啟動流程里講到),是把前面學(xué)習(xí)的 Android 底層架構(gòu)知識融會貫通的一個非常好的學(xué)習(xí)范例聂渊,也為后面分析更為復(fù)雜的 startActivity() 流程打好了基礎(chǔ)差购。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市汉嗽,隨后出現(xiàn)的幾起案子欲逃,更是在濱河造成了極大的恐慌,老刑警劉巖饼暑,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稳析,死亡現(xiàn)場離奇詭異,居然都是意外死亡弓叛,警方通過查閱死者的電腦和手機(jī)彰居,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撰筷,“玉大人陈惰,你說我怎么就攤上這事”兆ǎ” “怎么了奴潘?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵旧烧,是天一觀的道長。 經(jīng)常有香客問我画髓,道長掘剪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任奈虾,我火速辦了婚禮夺谁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肉微。我一直安慰自己匾鸥,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布碉纳。 她就那樣靜靜地躺著勿负,像睡著了一般。 火紅的嫁衣襯著肌膚如雪劳曹。 梳的紋絲不亂的頭發(fā)上奴愉,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音铁孵,去河邊找鬼锭硼。 笑死,一個胖子當(dāng)著我的面吹牛蜕劝,可吹牛的內(nèi)容都是我干的檀头。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼岖沛,長吁一口氣:“原來是場噩夢啊……” “哼暑始!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起烫止,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤蒋荚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后馆蠕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體期升,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年互躬,在試婚紗的時候發(fā)現(xiàn)自己被綠了播赁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡吼渡,死狀恐怖容为,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤坎背,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布替劈,位于F島的核電站,受9級特大地震影響得滤,放射性物質(zhì)發(fā)生泄漏陨献。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一懂更、第九天 我趴在偏房一處隱蔽的房頂上張望眨业。 院中可真熱鬧,春花似錦沮协、人聲如沸龄捡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽聘殖。三九已至,卻和暖如春行瑞,著一層夾襖步出監(jiān)牢的瞬間就斤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工蘑辑, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坠宴。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓洋魂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喜鼓。 傳聞我的和親對象是個殘疾皇子副砍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容