本文為作者原創(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à)的流程圖:
流程圖很簡(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
加入到MessageQueue
和Looper
取到消息后執(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)記注解,onPreExecute
和onPostExecute
也被添加了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)單清楚度陆,初始化了WorkerRunnable
和FutureTask
,在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)看一下:
通過(guò)上面的Debug可以看到腺晾,在回調(diào)的時(shí)候也使用了基于MainLooper
的Handler
,所以切換原理也應(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ì)有漏掉的地方,歡迎大家交流指正领斥。