Android進(jìn)階(五):Service啟動過程(最詳細(xì)&最簡單)

1.前言

  • 最近一直在看 《Android進(jìn)階解密》 的一本書恼布,這本書編寫邏輯耳贬、流程都非常好攒射,而且很容易看懂醋旦,非常推薦大家去看看(沒有收廣告費(fèi),單純覺得作者寫的很好)匆篓。
  • 上一篇簡單的介紹了Android進(jìn)階(四):Activity啟動過程(最詳細(xì)&最簡單)浑度。
  • 今天就介紹Service 2種啟動方式中:startService啟動 (基于Android 8.0 系統(tǒng))。
  • 本文提供一種看源碼的思路鸦概,因此bindService啟動流程就沒有再本文展開(與startService流程類似)箩张。
  • 文章中實例 linhaojian的Github

2.Context繼承關(guān)系

  • 在講解Service啟動過程前,先了解一下它的繼承關(guān)系窗市,便于后續(xù)的源碼理解:


    Context繼承關(guān)系.png
  • 從圖中先慷,可以發(fā)現(xiàn):
    • Activity,Service,Application都是ContextWrapper的子類;
    • ContextWrapper里面引用著一個ContextImpl實例咨察;
    • ContextWrapper里所有的方法都是通過調(diào)用ContextImpl進(jìn)行實現(xiàn)论熙;
  • 通過上述3點(diǎn)可以發(fā)現(xiàn),其他就是[裝飾者模式](http://www.reibang.com/p/16e946f42ce1)

2.Service啟動過程的時序圖

Service啟動時序圖.png

3.源碼分析

3.1 startService()啟動分析

3.1.1 ContextImpl

class ContextImpl extends Context {
    @Override
    public ComponentName startService(Intent service) {
        warnIfCallingFromSystemProcess();
        return startServiceCommon(service, false, mUser);// 1
    }
    private ComponentName startServiceCommon(Intent service, boolean requireForeground,
            UserHandle user) {
        try {
            validateServiceIntent(service);
            service.prepareToLeaveProcess(this);
            ComponentName cn = ActivityManager.getService().startService(
                mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                            getContentResolver()), requireForeground,
                            getOpPackageName(), user.getIdentifier());// 2
            // ....
            return cn;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
  • 注釋1:startService最終也是調(diào)用startServiceCommon函數(shù)摄狱;
  • 注釋2:ActivityManager.getService()其實就是ActivityManagerService脓诡,調(diào)用ActivityManagerService類里面的startService方法无午;

3.1.2 ActivityManagerService

    public ComponentName startService(IApplicationThread caller, Intent service,
            String resolvedType, boolean requireForeground, 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;
            try {
                res = mServices.startServiceLocked(caller, service,
                        resolvedType, callingPid, callingUid,
                        requireForeground, callingPackage, userId);// 1
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
            return res;
        }
    }
  • 注釋1:調(diào)用ActiveServices的startServiceLocked()

3.1.3 ActiveServices

    ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
            int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId)
            throws TransactionTooLargeException {
        //.....
        ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType, callingPackage,
                    callingPid, callingUid, userId, true, callerFg, false, false);// 1
        if (res == null) {
            return null;
        }
        if (res.record == null) {
            return new ComponentName("!", res.permission != null
                    ? res.permission : "private to package");
        }
        ServiceRecord r = res.record;
        if (!mAm.mUserController.exists(r.userId)) {
            Slog.w(TAG, "Trying to start service with non-existent user! " + r.userId);
            return null;
        }
        //.....
        ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);// 2
        return cmp;
    }
  • 注釋1:創(chuàng)建 & 封裝啟動Service的相關(guān)數(shù)據(jù)祝谚;
  • 注釋2:調(diào)用自身的startServiceInnerLocked()宪迟;
    ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
            boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
        ServiceState stracker = r.getTracker();
        if (stracker != null) {
            stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
        }
        r.callStart = false;
        synchronized (r.stats.getBatteryStats()) {
            r.stats.startRunningLocked();
        }
        String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false); // 3
        if (error != null) {
            return new ComponentName("!!", error);
        }
        //.....
        return r.name;
    }
  • 注釋3:調(diào)用自身的bringUpServiceLocked()
    private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
            boolean whileRestarting, boolean permissionsReviewRequired)
            throws TransactionTooLargeException {
            // ....
        final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
        final String procName = r.processName;
        String hostingType = "service";
        ProcessRecord app;
        if (!isolated) {
            app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
            if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
                        + " app=" + app);
            if (app != null && app.thread != null) {
                try {
                    app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode, mAm.mProcessStats);
                    realStartServiceLocked(r, app, execInFg);// 4
                    return null;
                } catch (TransactionTooLargeException e) {
                    throw e;
                } catch (RemoteException e) {
                    Slog.w(TAG, "Exception when starting service " + r.shortName, e);
                }
            }
        } else {
            app = r.isolatedProc;
            if (WebViewZygote.isMultiprocessEnabled()
                    && r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) {
                hostingType = "webview_service";
            }
        }
        //.....
    }
  • 注釋4:調(diào)用自身的realStartServiceLocked()交惯;
    private final void realStartServiceLocked(ServiceRecord r,
            ProcessRecord app, boolean execInFg) throws RemoteException {
        //.....
        boolean created = false;
        try {
            if (LOG_SERVICE_START_STOP) {
                String nameTerm;
                int lastPeriod = r.shortName.lastIndexOf('.');
                nameTerm = lastPeriod >= 0 ? r.shortName.substring(lastPeriod) : r.shortName;
                EventLogTags.writeAmCreateService(
                        r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
            }
            synchronized (r.stats.getBatteryStats()) {
                r.stats.startLaunchedLocked();
            }
            mAm.notifyPackageUse(r.serviceInfo.packageName,
                                 PackageManager.NOTIFY_PACKAGE_USE_SERVICE);
            app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
            app.thread.scheduleCreateService(r, r.serviceInfo,
                    mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                    app.repProcState);// 5
            r.postNotification();
            created = true;
        } catch (DeadObjectException e) {
            Slog.w(TAG, "Application dead when creating service " + r);
            mAm.appDiedLocked(app);
            throw e;
        } finally {
            //...
        }
        // 通知ActivityThread調(diào)用Service的onStartCommand方法
        sendServiceArgsLocked(r, execInFg, true);// 6
        //.....
    }
  • 注釋5:通知ActivityThread次泽,真正的創(chuàng)建Service
  • 注釋6:調(diào)用自身的sendServiceArgsLocked()啟動Service的其他生命周期方法席爽,下面會介紹意荤;

3.1.4 ActivityThread

  • scheduleCreateService()其實就是通過Handler機(jī)制進(jìn)行線程切換,最后會調(diào)用handleCreateService();
    private void handleCreateService(CreateServiceData data) {
        unscheduleGcIdler();
        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);// 1
        Service service = null;
        try {
            // 通過類加載器只锻,創(chuàng)建Service實例
            java.lang.ClassLoader cl = packageInfo.getClassLoader();// 2
            service = packageInfo.getAppFactory()
                    .instantiateService(cl, data.info.name, data.intent);// 3
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to instantiate service " + data.info.name
                    + ": " + e.toString(), e);
            }
        }
        try {
            if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            //向Service對象中初始化基本設(shè)置
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManager.getService());// 4
            //調(diào)用service的onCreate生命周期函數(shù)
            service.onCreate();// 5
            mServices.put(data.token, service);
            try {
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(service, e)) {
                throw new RuntimeException(
                    "Unable to create service " + data.info.name
                    + ": " + e.toString(), e);
            }
        }
    }
  • 注釋1:獲取LoadedApk對象玖像,負(fù)責(zé)解析manifest文件
  • 注釋2:獲取ClassLoader類加載器炬藤,負(fù)責(zé)創(chuàng)建Service實例御铃;
  • 注釋3:通過AppComponentFactory,創(chuàng)建Service對象沈矿;
  • 注釋4:設(shè)置Service基礎(chǔ)參數(shù)
  • 注釋5:調(diào)用Service的onCreate方法咬腋;

3.1.5 AppComponentFactory

public class AppComponentFactory {
    public @NonNull Service instantiateService(@NonNull ClassLoader cl,
            @NonNull String className, @Nullable Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return (Service) cl.loadClass(className).newInstance();// 1
    }
}
  • 注釋1:通過類加載器羹膳,創(chuàng)建Service實例

3.1.6 ActiveServices的sendServiceArgsLocked()函數(shù)

    private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
            boolean oomAdjusted) throws TransactionTooLargeException {
        //...        
       Exception caughtException = null;
        try {
            r.app.thread.scheduleServiceArgs(r, slice);// 1
        } catch (TransactionTooLargeException e) {
            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large for " + args.size()
                    + " args, first: " + args.get(0).args);
            Slog.w(TAG, "Failed delivering service starts", e);
            caughtException = e;
        }
        //....
    }
  • 注釋1:調(diào)用ActivityThread的scheduleServiceArgs函數(shù)(在Service創(chuàng)建之后根竿,會調(diào)用sendServiceArgsLocked()陵像,實現(xiàn)其他的生命周期方法的調(diào)用)

3.1.7 ActivityThread的handleServiceArgs函數(shù)

  • scheduleServiceArgs()最終通過Handler機(jī)制切換線程 & 調(diào)用handleServiceArgs()寇壳;
    private void handleServiceArgs(ServiceArgsData data) {
        Service s = mServices.get(data.token);
        if (s != null) {
            try {
                if (data.args != null) {
                    data.args.setExtrasClassLoader(s.getClassLoader());
                    data.args.prepareToEnterProcess();
                }
                int res;
                if (!data.taskRemoved) {
                    res = s.onStartCommand(data.args, data.flags, data.startId);// 1
                } else {
                    s.onTaskRemoved(data.args);
                    res = Service.START_TASK_REMOVED_COMPLETE;
                }
               //...
            } catch (Exception e) {
                // ...
            }
        }
    }
  • 注釋1:調(diào)用Service的onStartCommand()醒颖;

4.類關(guān)系

Service啟動類結(jié)構(gòu).png
  • 通過上圖發(fā)現(xiàn),Service啟動過程就是AMS與ActivityThread不同的進(jìn)程交互實現(xiàn)的壳炎。

5.總結(jié)

  • 到此泞歉,Service啟動過程介紹完畢。
  • 如果喜歡我的分享匿辩,可以點(diǎn)擊 關(guān)注 或者 腰耙,你們支持是我分享的最大動力 。
  • linhaojian的Github

歡迎關(guān)注linhaojian_CSDN博客或者linhaojian_簡書铲球!

不定期分享關(guān)于安卓開發(fā)的干貨挺庞。


寫技術(shù)文章初心

  • 技術(shù)知識積累
  • 技術(shù)知識鞏固
  • 技術(shù)知識分享
  • 技術(shù)知識交流
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市稼病,隨后出現(xiàn)的幾起案子选侨,更是在濱河造成了極大的恐慌掖鱼,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件援制,死亡現(xiàn)場離奇詭異锨用,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)隘谣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門增拥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人寻歧,你說我怎么就攤上這事掌栅。” “怎么了码泛?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵猾封,是天一觀的道長。 經(jīng)常有香客問我噪珊,道長晌缘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任痢站,我火速辦了婚禮磷箕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘阵难。我一直安慰自己岳枷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布呜叫。 她就那樣靜靜地躺著空繁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪朱庆。 梳的紋絲不亂的頭發(fā)上盛泡,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機(jī)與錄音娱颊,去河邊找鬼傲诵。 笑死,一個胖子當(dāng)著我的面吹牛维蒙,可吹牛的內(nèi)容都是我干的掰吕。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼颅痊,長吁一口氣:“原來是場噩夢啊……” “哼殖熟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起斑响,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤菱属,失蹤者是張志新(化名)和其女友劉穎钳榨,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纽门,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡薛耻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了赏陵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饼齿。...
    茶點(diǎn)故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蝙搔,靈堂內(nèi)的尸體忽然破棺而出缕溉,到底是詐尸還是另有隱情,我是刑警寧澤吃型,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布证鸥,位于F島的核電站,受9級特大地震影響勤晚,放射性物質(zhì)發(fā)生泄漏枉层。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一赐写、第九天 我趴在偏房一處隱蔽的房頂上張望鸟蜡。 院中可真熱鬧,春花似錦血淌、人聲如沸矩欠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至躺坟,卻和暖如春沦补,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咪橙。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工夕膀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人美侦。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓产舞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親菠剩。 傳聞我的和親對象是個殘疾皇子易猫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評論 2 361

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