Android消息機(jī)制原理锡溯,重要性,使用和優(yōu)化

本文為作者原創(chuàng)哑姚,轉(zhuǎn)載請(qǐng)注明地址鏈接Android消息機(jī)制原理祭饭,重要性,使用和優(yōu)化

想寫(xiě)這篇博客很久了叙量,但是一直感覺(jué)到自己的不足倡蝙,很怕自己會(huì)去誤導(dǎo)別人,所以一直拖到現(xiàn)在宛乃,但是我仍然相信會(huì)有很多東西是我沒(méi)想到悠咱,了解到的,所以有不足的地方歡迎指正征炼。

關(guān)于消息機(jī)制博客有太多了析既,具體有多少,反正我不知道谆奥,哈哈哈眼坏。但是大部分博客都只分析了原理,講解了如何使用酸些,對(duì)于很多人仍然是一頭霧水宰译,所以我從原理,重要性魄懂,使用和優(yōu)化4部分來(lái)講解消息機(jī)制

一沿侈、消息機(jī)制原理

這部分有太多的人去講解了,有很多人的博客都分析的很好市栗,我這里就不去講解了缀拭,如果大家感興趣的話,我可以推薦一篇我感覺(jué)講解的最好的:Android消息機(jī)制1-Handler(Java層) 博主的其他博客也都很好填帽,基本都是講解Android原理的蛛淋,很崇拜大神。在這里主要給大家看根據(jù)我自己的理解畫(huà)的流程圖:

Android 消息機(jī)制(不怎么會(huì)畫(huà)圖篡腌,見(jiàn)諒)

流程圖很簡(jiǎn)單褐荷,有些人甚至?xí)芷婀郑驗(yàn)檫@里面沒(méi)有大家很熟悉的Handler嘹悼,這是故意的叛甫,因?yàn)樗鋵?shí)不是消息機(jī)制的重要組成部分,他其實(shí)僅僅是Message的一個(gè)屬性而已⊙罨铮現(xiàn)在不去看Handler你就會(huì)發(fā)現(xiàn)消息機(jī)制很好理解:Looper循環(huán)調(diào)用Next去取消息消息隊(duì)列的第一條消息合溺,消息隊(duì)列是一個(gè)按照時(shí)間去排列的消息鏈表。Handler僅僅是用來(lái)將Message加入到MessageQueueLooper取到消息后執(zhí)行消息處理的邏輯而已缀台。除此之外有幾點(diǎn)仍然要提出來(lái):

  • 一個(gè)線程只有一個(gè)消息隊(duì)列和一個(gè)Looper
  • 線程是由有Looper才能有Handler
  • NextMessage是阻塞的

以上3點(diǎn)是比較重要的棠赛,很多博客都講到了,1和2就不說(shuō)了膛腐,稍微說(shuō)下第3點(diǎn):

  • 第一睛约、大家都知道Message是有執(zhí)行時(shí)間的,如果說(shuō)頭部的消息沒(méi)有到執(zhí)行時(shí)間線程是處于睡眠狀態(tài)哲身,當(dāng)?shù)竭_(dá)頭部消息的時(shí)間或者消息隊(duì)列發(fā)生改變后線程會(huì)被喚醒辩涝,這部分代碼的控制是Native層的。
  • 第二勘天、很多人會(huì)有疑問(wèn)如果阻塞主線程的話程序不就會(huì)直接報(bào)ANR了嗎怔揩?這個(gè)問(wèn)題首先要明白ANR是怎么發(fā)生的捉邢,是具體原因,不是主線程5s原則商膊,因?yàn)橐蓡?wèn)本身就是因5s原則來(lái)的伏伐。好在有先行者已經(jīng)解釋了這個(gè),直接借用傳送門(mén)晕拆,從博客中可以看出是否發(fā)生ANR是由Handler判斷得出的藐翎,所以MessageQueue阻塞不會(huì)發(fā)生ANR

二实幕、Handler的重要性

程序的運(yùn)行是靠消息機(jī)制來(lái)維持的

開(kāi)始的時(shí)候我比較糾結(jié)是先說(shuō)重要性還是先說(shuō)原理吝镣,因?yàn)槠鋵?shí)他們差別并不是太大,而且相互關(guān)聯(lián)昆庇。

首先說(shuō)一個(gè)面試中會(huì)被問(wèn)到的問(wèn)題:App程序的入口是什么末贾?JAVA基礎(chǔ)好的人或者對(duì)源碼了解的人都知道,是main方法整吆,不清楚的人才會(huì)想到是Application未舟。先來(lái)看一下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());

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

代碼不多,主要有2部分:一部分是環(huán)境配置和日志信息設(shè)置掂为,另外一部分就和Looper相關(guān)裕膀,包括主線程的Looper的初始化勇哗,Looper的運(yùn)行。代碼的最后一行是拋出RuntimeException異常欲诺,正常情況下是不會(huì)走到這一步的,走到這里程序就會(huì)退出扰法,APP就無(wú)法運(yùn)行了蛹含,所以Looper的主題代碼是死循環(huán)。說(shuō)到這里塞颁,會(huì)有人感覺(jué)Looper很重要浦箱,因?yàn)樗S持這項(xiàng)目的運(yùn)行,不讓項(xiàng)目異常退出祠锣。但是你忽略了一點(diǎn)酷窥,代碼如何運(yùn)行的,是MessageQueue伴网,它穿插于整個(gè)主線程蓬推,維持著項(xiàng)目的不斷運(yùn)行。舉個(gè)栗子:ActivityThread里面的H,繼承于Handler澡腾,里面幾乎涵蓋了四大組件和Application的調(diào)用沸伏,各個(gè)生命周期的代碼都是通過(guò)他來(lái)調(diào)用運(yùn)行的糕珊,MessageQueue不斷的解析著每一個(gè)Message,維持著程序的不斷運(yùn)行。有人會(huì)提出View的各種事件毅糟,恩红选,你翻下源碼就會(huì)發(fā)現(xiàn),那也有Handler留特。

在一定程度上我們可以說(shuō)纠脾,我們主線程的代碼是運(yùn)行在主線程的消息隊(duì)列中玛瘸。

三蜕青、消息機(jī)制的使用

這部分比較簡(jiǎn)單,大家對(duì)Handler的使用也比較熟悉糊渊,就不去講使用方法了右核,來(lái)看下在源碼和框架中的使用:

1、系統(tǒng)源碼

在前面提到的Android ANR原理分析中就有關(guān)于Handler的延時(shí)消息使用方法渺绒,其實(shí)系統(tǒng)也是通過(guò)延時(shí)消息來(lái)判斷你的動(dòng)作有沒(méi)有超過(guò)規(guī)定時(shí)間贺喝,超過(guò)的話系統(tǒng)就認(rèn)定是ANR。在這里主要看一下AsyncTask中關(guān)于Handler的使用 宗兼。對(duì)于AsyncTask大家應(yīng)該很熟悉躏鱼,因?yàn)槊嬖囍袝?huì)被大量的提及,其中的方法也比較常用殷绍,在源碼中也有很明確的標(biāo)記染苛,doInBackground被添加了WorkThread的標(biāo)記注解,onPreExecuteonPostExecute也被添加了MainThread的標(biāo)記注解茶行,表明了他們各自的工作線程畔师。這里就主要看下他們之間的切換是如何進(jìn)行的牧牢,先來(lái)看下AsyncTask的初始化:

public AsyncTask() {

    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //noinspection unchecked
            Result result = doInBackground(mParams);
            Binder.flushPendingCommands();
            return postResult(result);
        }
    };

    mFuture = new FutureTask<Result>(mWorker) {
        @Override
        protected void done() {
            try {
                postResultIfNotInvoked(get());
            } catch (InterruptedException e) {
                android.util.Log.w(LOG_TAG, e);
            } catch (ExecutionException e) {
                throw new RuntimeException("An error occurred while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                postResultIfNotInvoked(null);
            }
        }
    };
}

代碼比較簡(jiǎn)單清楚度陆,初始化了WorkerRunnableFutureTask,在WorkerRunnable中我們看到了doInBackground懂傀,運(yùn)行在Runnable(也就是工作線程)中蹬蚁,得到了結(jié)果Result ,然后發(fā)送出去贝乎,我們看下發(fā)送代碼postResult

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

很熟悉的代碼览效,構(gòu)建Message然后發(fā)送出去锤灿,再來(lái)看下獲取Handler的代碼:

private static Handler getHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler();
        }
        return sHandler;
    }
}

構(gòu)建一個(gè)靜態(tài)的Handler


private static class InternalHandler extends Handler {

    public InternalHandler() {
        super(Looper.getMainLooper());
    }

    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_RESULT:
                // There is only one result
                // 這個(gè)mTask就是AsyncTask本身
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

super(Looper.getMainLooper()); 這個(gè)地方表明了這個(gè)Handler處理的是主線程的消息但校,如此就將子線程的結(jié)果發(fā)送到了主線程状囱。

結(jié)果處理亭枷,onPostExecute的調(diào)用:

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

線程執(zhí)行叨粘,onPreExecute的調(diào)用:

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}

@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
        Params... params) {
    if (mStatus != Status.PENDING) {
        switch (mStatus) {
            case RUNNING:
                throw new IllegalStateException("Cannot execute task:"
                        + " the task is already running.");
            case FINISHED:
                throw new IllegalStateException("Cannot execute task:"
                        + " the task has already been executed "
                        + "(a task can be executed only once)");
        }
    }
    mStatus = Status.RUNNING;

    onPreExecute();
    
    //線程池執(zhí)行線程
    mWorker.mParams = params;
    exec.execute(mFuture);

    return this;
}

通過(guò)上面就可以很清晰的看到由主線程(onPreExecute)到子線程(doInBackground)再到主線程(onPostExecute)的切換流程宣鄙。

2冻晤、框架源碼

上面我們分析了AsyncTask鼻弧,這個(gè)地方我們也分析一個(gè)相對(duì)的網(wǎng)絡(luò)框架攘轩,現(xiàn)在說(shuō)到網(wǎng)絡(luò)框架最火的應(yīng)該是Retrofit了度帮,就看下他是如何進(jìn)行線程之間的切換的。這里換個(gè)方法瞳秽,不先分析源碼练俐,用Debug的方式來(lái)看一下:

Retorfit CallBack

通過(guò)上面的Debug可以看到腺晾,在回調(diào)的時(shí)候也使用了基于MainLooperHandler,所以切換原理也應(yīng)該是相似的悯蝉,再來(lái)看下源碼念颈,根據(jù)Debug圖片我們能夠很清晰的找到源碼的路徑:

static class Android extends Platform {
  @Override public Executor defaultCallbackExecutor() {
    return new MainThreadExecutor();
  }

  @Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
    return new ExecutorCallAdapterFactory(callbackExecutor);
  }

  static class MainThreadExecutor implements Executor {
    private final Handler handler = new Handler(Looper.getMainLooper());
    @Override public void execute(Runnable r) {
      handler.post(r);
    }
  }
}

通過(guò)Looper.getMainLooper()就很明白了连霉,這個(gè)地方也是在處理主線程的消息隊(duì)列

另外稍微提一下RxAndroid,這個(gè)現(xiàn)在最有名的線程切換框架,直接看源碼:

private AndroidSchedulers() {
    RxAndroidSchedulersHook hook = RxAndroidPlugins.getInstance().getSchedulersHook();
    Scheduler main = hook.getMainThreadScheduler();
    if (main != null) {
        mainThreadScheduler = main;
    } else {
        mainThreadScheduler = new LooperScheduler(Looper.getMainLooper());
    }
}

class LooperScheduler extends Scheduler {
    ...
    LooperScheduler(Looper looper) {
        handler = new Handler(looper);
    }
    LooperScheduler(Handler handler) {
        this.handler = handler;
    }
    ....
}

這里只貼了關(guān)鍵代碼窟感,就不解釋了,感興趣的可以自己去看一看柿祈。
通過(guò)上面一系列的分析應(yīng)該能了解線程之間如何切換,那么剩下的代碼也僅僅是歸類封裝而已躏嚎,大家如果有興趣可以寫(xiě)一些自己的框架,那樣就能有更加深刻的理解了卢佣。

四、消息機(jī)制的優(yōu)化

在消息機(jī)制的重要性中就提到了它在整個(gè)程序中扮演的角色虚茶,因此合理的使用消息隊(duì)列也能夠優(yōu)化自己的程序仇参。在說(shuō)優(yōu)化之前,先推薦一篇博客Android消息機(jī)制2-Handler(Native層),本來(lái)這個(gè)應(yīng)該放在第一部分原理中诈乒,但是這個(gè)比較適合想對(duì)消息機(jī)制進(jìn)一步了解的人來(lái)看,之所以放在這個(gè)地方是因?yàn)椴┛椭械囊欢卧掃m合這一部分:

消息處理流程是先處理Native Message怕磨,再處理Native Request寞缝,最后處理Java Message仰泻。理解了該流程,也就明白有時(shí)上層消息很少被啼,但響應(yīng)時(shí)間卻較長(zhǎng)的真正原因。

看到這句話浓体,在以后對(duì)程序的優(yōu)化上面應(yīng)該能有一定的幫助。

除此之外命浴,關(guān)于相對(duì)于消息隊(duì)列的優(yōu)化部分,提一個(gè)MessageQueue的一個(gè)接口IdleHandler,關(guān)于這個(gè)我曾經(jīng)寫(xiě)過(guò):MessageQueue中的IdleHandler生闲,這個(gè)接口會(huì)在MessageQueue空閑的時(shí)候調(diào)用執(zhí)行月幌,對(duì)于一些不是特別緊急的邏輯可以放在這里面執(zhí)行。Google后來(lái)也專門(mén)出了相關(guān)的類:JobScheduler ,當(dāng)然這個(gè)類更加全面扯躺,不僅僅有空閑時(shí)候,還監(jiān)控了其他很多狀態(tài)录语,感興趣的朋友可以去了解一下,但是這個(gè)類是在5.0中加入的澎埠,如果想低版本支持,我建議大家直接使用IdleHandler自己去封裝彼宠,具體代碼可以參考Glide

結(jié)語(yǔ):轉(zhuǎn)身看看了博客凭峡,發(fā)現(xiàn)沒(méi)有什么太多原理性的東西决记,很多都是歸納總結(jié)別人的東西,真的是站在巨人的肩膀上面索昂。說(shuō)了這么多可能會(huì)有不正確的地方椒惨,肯定會(huì)有漏掉的地方,歡迎大家交流指正领斥。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末月洛,一起剝皮案震驚了整個(gè)濱河市嚼黔,隨后出現(xiàn)的幾起案子唬涧,更是在濱河造成了極大的恐慌韵丑,老刑警劉巖撵彻,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陌僵,死亡現(xiàn)場(chǎng)離奇詭異创坞,居然都是意外死亡题涨,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)巡雨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)铐望,“玉大人正蛙,你說(shuō)我怎么就攤上這事乒验。” “怎么了奸攻?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵睹耐,是天一觀的道長(zhǎng)硝训。 經(jīng)常有香客問(wèn)我窖梁,道長(zhǎng)夹囚,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任假哎,我火速辦了婚禮,結(jié)果婚禮上舵抹,老公的妹妹穿的比我還像新娘。我一直安慰自己惧蛹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布香嗓。 她就那樣靜靜地躺著装畅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪洁灵。 梳的紋絲不亂的頭發(fā)上掺出,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天苫费,我揣著相機(jī)與錄音,去河邊找鬼百框。 笑死,一個(gè)胖子當(dāng)著我的面吹牛铐维,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嫁蛇,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼第煮!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起包警,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤底靠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后暑中,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痒芝,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笆呆。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖赠幕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榕堰,我是刑警寧澤嫌套,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布圾旨,位于F島的核電站踱讨,受9級(jí)特大地震影響痹筛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜廓鞠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望滋早。 院中可真熱鬧,春花似錦馆衔、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)篮撑。三九已至,卻和暖如春赢笨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茧妒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留桐筏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓梅忌,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親牧氮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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