Android中為什么主線程不會因?yàn)長ooper.loop()里的死循環(huán)阻塞?

標(biāo)題是偽命題

參考資料 Android中為什么主線程不會因?yàn)長ooper.loop()里的死循環(huán)卡死眉踱? 知乎
之前對這個概念一直處于比較模糊的狀態(tài),也是一直被自己忽略了,認(rèn)為可能涉及的東西過于復(fù)雜,所以不敢對自己問,為什么?
這兩天狀態(tài)不錯,生活還是code比較有趣,簡單而真實(shí),所以曾經(jīng)被忽略的問題不經(jīng)意間又開始出現(xiàn)在腦海.
這個問題在我理解看來可以分為兩個問題

為什么主線程需要阻塞

何謂主線程,和其他線程有什么不同之處

  • 何謂主線程
    主線程,通常稱之為UI線程,也就是APP進(jìn)程被創(chuàng)建的時候所處的線程,和其他的線程一樣都是一個普通的線程.
  • 不同之處
    從app角度來說,不同之處主要集中在UI界面更新上面,普通線程不能更新UI界面,如此設(shè)計也是為了程序的健壯性,畢竟不同線程同時對對象操作時為了保證其準(zhǔn)確性都要進(jìn)行加鎖,而界面更新不能像對象那樣僅僅保證準(zhǔn)確性,還要保證其連貫性,一個按鈕在同一段時間同步(假同步)發(fā)生了向左又向右的滑動自然是不可取的.

線程的生命周期

一個進(jìn)程或線程在CPU看來無非就是一段的可執(zhí)行代碼,代碼執(zhí)行完畢,線程的生命也就到頭了.

APP的生命周期

從使用手機(jī)的角度來看,從點(diǎn)開APP圖標(biāo)開始,到完全退出APP結(jié)束.

主線程在哪里進(jìn)行了阻塞

我們知道APP的入口是在ActivityThread,一個Java類,有著main方法,而且main方法中的代碼也不是很多.

    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        AndroidKeyStoreProvider.install();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

這就是main方法的全部代碼了,23的源碼.在其中Looper進(jìn)行了初始化,但是并不是常規(guī)的prepare(),而是prepareMainLooper(),其實(shí)差別不大,只不過是給靜態(tài)成員對象成員sMainLooper進(jìn)行了初始化賦值,并不準(zhǔn)予同進(jìn)程中sMainLooper初始化第二次而已.
然后在代碼末尾Looper.loop進(jìn)行阻塞.

標(biāo)題為什么是偽命題

我們都知道主線程是隨著APP的啟動而啟動,隨著APP的結(jié)束而結(jié)束的(多進(jìn)程對應(yīng)多主線程的情況我就將其看做一個統(tǒng)一的主線程).
APP要一直運(yùn)行直到用戶退出,那么主線程就必然不能代碼運(yùn)行完畢而終止,所以需要進(jìn)行阻塞,直到用戶退出了APP,才能停止阻塞,讓CPU執(zhí)行完剩下的代碼,爾后代碼執(zhí)行完畢,主線程從而壽終正寢.

為什么主線程阻塞還能更新UI

既然線程是在Looper中阻塞了,那么與Looper配合著出現(xiàn)的Handler肯定是少不了的.
至于Handler是如何進(jìn)行線程切換不了解的同學(xué)請戳這

ActivityThread.H

很容易就在Activity中找到了繼承自Handler的內(nèi)部類H,并且重寫了handleMessage方法,代碼就不列出了.

ActivityThread.H怎么和Looper交互的

光有Handler是不行的,關(guān)鍵要有調(diào)用Handler的地方,然后Handler才能去處理,才會在主線程調(diào)用一個又一個方法.
答案在這!


        ActivityThread thread = new ActivityThread();
        thread.attach(false);

進(jìn)一步來看attach()方法

    private void attach(boolean system) {
        ...
        if (!system) {
            ...
            final IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }
            ...
        } else {
            ...
        }

       ...
    }

恩,想必你們都知道我想看什么了,這里傳入了對象mAppThread,我只關(guān)心mAppThread對象,而他作為參數(shù)最終通過IPC傳遞到哪里去,能力有限就不再繼續(xù)跟進(jìn)了.

ApplicationThread

上面我們說到了mAppThread對象,那么這個對象是哪個對象呢?就是ApplicationThread的實(shí)例化對象,代碼不多,也就500來行,我就截取一點(diǎn)點(diǎn)來示例一下

        public final void schedulePauseActivity(IBinder token, boolean finished,
                boolean userLeaving, int configChanges, boolean dontReport) {
            sendMessage(
                    finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
                    token,
                    (userLeaving ? 1 : 0) | (dontReport ? 2 : 0),
                    configChanges);
        }

        public final void scheduleStopActivity(IBinder token, boolean showWindow,
                int configChanges) {
           sendMessage(
                showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
                token, 0, configChanges);
        }

        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);
        }

        public final void scheduleBindService(IBinder token, Intent intent,
                                              boolean rebind, int processState) {
            updateProcessState(processState, false);
            BindServiceData s = new BindServiceData();
            s.token = token;
            s.intent = intent;
            s.rebind = rebind;

            if (DEBUG_SERVICE)
                Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="
                        + Binder.getCallingUid() + " pid=" + Binder.getCallingPid());
            sendMessage(H.BIND_SERVICE, s);
        }

聰明的同學(xué)已經(jīng)明了,主線程阻塞之后生命周期等方法是如何啟用的.
這一個個形似各種聲明周期的方法,最終還調(diào)用了sendMessage()方法,讓我們再來看看sendMessage方法的是怎么操作的

    private void sendMessage(int what, Object obj) {
        sendMessage(what, obj, 0, 0, false);
    }

    private void sendMessage(int what, Object obj, int arg1) {
        sendMessage(what, obj, arg1, 0, false);
    }

    private void sendMessage(int what, Object obj, int arg1, int arg2) {
        sendMessage(what, obj, arg1, arg2, false);
    }

    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        if (DEBUG_MESSAGES) Slog.v(
            TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
            + ": " + arg1 + " / " + obj);
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }

可以看到最終調(diào)用了mH.sendMessage()方法,而mH是誰呢?ActivityThread的成員變量是ActivityThread.H的實(shí)例化對象.

總結(jié)

到此想必各位也就明了,主線程確實(shí)是阻塞的,不阻塞那APP怎么能一直運(yùn)行,所以說主線程阻塞是一個偽命題,只不過是沒有弄明白既然阻塞了,為什么還能調(diào)用各種聲明周期而已.
調(diào)用生命周期是因?yàn)橛蠰ooper,有MessageQueue,還有溝通的橋梁Handler,通過IPC機(jī)制調(diào)用Handler發(fā)送各種消息,保存到MessageQueue中,然后在主線程中的Looper提取了消息,并在主線程中調(diào)用Handler的方法去處理消息.最終完成各種聲明周期.

文章到此結(jié)束,順便給自己打個小廣告,深圳求職,目前在職招人頂缸中(ps:找個人頂缸真不好招...全是假簡歷)
簡歷戳我

擴(kuò)展的知識點(diǎn)

IPC機(jī)制下的startService流程分析
為什么選擇Binder為什么 Android 要采用 Binder 作為 IPC 機(jī)制挤忙?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谈喳,隨后出現(xiàn)的幾起案子册烈,更是在濱河造成了極大的恐慌,老刑警劉巖婿禽,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赏僧,死亡現(xiàn)場離奇詭異,居然都是意外死亡扭倾,警方通過查閱死者的電腦和手機(jī)淀零,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膛壹,“玉大人驾中,你說我怎么就攤上這事∧A” “怎么了肩民?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長链方。 經(jīng)常有香客問我持痰,道長,這世上最難降的妖魔是什么侄柔? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任共啃,我火速辦了婚禮,結(jié)果婚禮上暂题,老公的妹妹穿的比我還像新娘移剪。我一直安慰自己,他們只是感情好薪者,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布纵苛。 她就那樣靜靜地躺著桩匪,像睡著了一般砾嫉。 火紅的嫁衣襯著肌膚如雪买窟。 梳的紋絲不亂的頭發(fā)上媚赖,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音踊谋,去河邊找鬼护赊。 笑死给赞,一個胖子當(dāng)著我的面吹牛初婆,可吹牛的內(nèi)容都是我干的蓬坡。 我是一名探鬼主播猿棉,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼屑咳!你這毒婦竟也來了萨赁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤兆龙,失蹤者是張志新(化名)和其女友劉穎杖爽,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體紫皇,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡慰安,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了坝橡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泻帮。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖计寇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脂倦,我是刑警寧澤番宁,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站赖阻,受9級特大地震影響蝶押,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜火欧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一棋电、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧苇侵,春花似錦赶盔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至陡鹃,卻和暖如春烘浦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萍鲸。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工闷叉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脊阴。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓握侧,卻偏偏與公主長得像蚯瞧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子藕咏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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