前言
文章是一篇學(xué)習(xí)筆記溯捆,主要記錄了閱讀Handler
丑搔、Message
、MessageQueue
、Looper
源碼(Android 8.1
)的一些心得體會(huì)啤月,但并沒有涉及到更深層次源碼的分析煮仇,比如MessageQueue
的native
層的源碼。而AsyncTask
只是簡(jiǎn)單介紹其API
谎仲。
概述
Android
的消息機(jī)制主要是指Handler
的運(yùn)行機(jī)制欺抗,Handler
的運(yùn)行需要底層的MessageQueue
和Looper
的支撐,常用于更新UI
强重。
MessageQueue
用于存儲(chǔ)消息(Message
)绞呈,內(nèi)部的存儲(chǔ)結(jié)構(gòu)采用了鏈表,而不是隊(duì)列间景,它提供插入和刪除消息的功能佃声,但不會(huì)處理消息。Looper
,MessageQueue
的管家倘要,它的loop
方法會(huì)一直查看MessageQueue
是否存在消息(Message
)需要處理圾亏,如果有,就交給Handler
來處理封拧。Handler
是消息(Message
)的發(fā)送者和處理者志鹃。
ThreadLocal
并不是線程,它的作用是可以在每個(gè)線程中存儲(chǔ)數(shù)據(jù)泽西,這些數(shù)據(jù)對(duì)于其他線程是不可見的曹铃。每個(gè)線程中只會(huì)有一個(gè)Looper
,可以通過ThreadLocal
獲取到捧杉。
另外陕见,線程默認(rèn)是沒有Loope
r的,如果需要使用Handler
就必須為線程創(chuàng)建味抖,比如在子線程评甜。而主線程已經(jīng)為我們創(chuàng)建好了,可以查閱ActivityThread
的main
方法仔涩,其中包括了MessageQueue
的初始化忍坷。
它們的數(shù)量級(jí)關(guān)系是:Handler(N):Looper(1):MessageQueue(1):Thread(1)
。
Handler
的主要作用是將一個(gè)任務(wù)切換到某個(gè)指定的線程中執(zhí)行熔脂,這樣設(shè)計(jì)主要是為了解決Android
只能再主線程中更新UI
佩研。
系統(tǒng)為什么不允許再子線程中訪問UI呢?這是因?yàn)?code>Android的UI
控件不是線程安全的锤悄,如果再多線程中并發(fā)訪問可能會(huì)導(dǎo)致UI
控件處于不可預(yù)期的狀態(tài)韧骗。
加鎖的缺點(diǎn)有兩個(gè):首先加上鎖機(jī)制會(huì)讓UI
訪問的邏輯變得復(fù)雜嘉抒;其次零聚,鎖機(jī)制會(huì)降低UI
訪問的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行。
創(chuàng)建方式
在此只列出一種創(chuàng)建Handler
的方式隶症,其他的創(chuàng)建方式可以自行百度政模,但是,一定要注意:如果在沒有Looper的線程里創(chuàng)建Handler會(huì)報(bào)錯(cuò)的:
// 在主線程創(chuàng)建
public class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
// 處理的代碼
}
}
MyHandler handler = new MyHandler();
// 子線程中執(zhí)行
Message message = handler.obtainMessage();
message.what = 1;
message.obj = 123;
handler.sendMessage(message);
原理圖
源碼分析
先從ActivityThread
的main
方法看起蚂会,里面有兩句需要注意的:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// 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");
}
大致就是說淋样,主線程的Looper
已經(jīng)準(zhǔn)備好了。通常胁住,我們創(chuàng)建Handler
會(huì)選擇繼承Handler
并重寫handleMessage
趁猴,因?yàn)楦割惖?code>handleMessage什么也不做。這里彪见,我們關(guān)注其構(gòu)造方法(無參的):
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
// 注意以下幾句
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// 初始化
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
假如我們?cè)僮泳€程中創(chuàng)建Handler
儡司,但該線程并沒有Looper
,它會(huì)拋出異常:
Can't create handler inside thread that has not called Looper.prepare()
可見余指,Handler
的創(chuàng)建需要Looper
捕犬。那這個(gè)異常如何解決呢?舉例如下:
new Thread("new Thread") {
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();
現(xiàn)在酵镜,我們回想一下碉碉,為什么在主線程可以直接創(chuàng)建Handler
?正如前面所講述的淮韭,當(dāng)我們的App
啟動(dòng)后垢粮,會(huì)調(diào)用ActivityThread
的main
的方法,而Looper
的prepareMainLooper
方法會(huì)被調(diào)用靠粪,也就是創(chuàng)建了Looper
足丢,也就是滿足了Handler
的創(chuàng)建條件。其源碼如下:
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
// 一個(gè)線程只能創(chuàng)建一個(gè)Looper
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
不管是prepareMainLooper()
方法還是prepare()
方法庇配,最后都是通過prepare(boolean quitAllowed)
來創(chuàng)建Looper
斩跌。我們先來看sThreadLocal
:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
詳細(xì)的介紹可以參考這篇博客:Android的消息機(jī)制之ThreadLocal的工作原理。它的作用是保存當(dāng)前線程的Looper
捞慌,且線程間互不干擾耀鸦。
再看Looper
的構(gòu)造方法,它完成了MessageQueue
的創(chuàng)建:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
總的來說啸澡,Android
的主線程就是ActivityThread
袖订,主線程的入口方法為main
,再main
方法中系統(tǒng)會(huì)通過Looper.prepareMainLooper()
來創(chuàng)建主線程的Looper
以及MessageQueue
嗅虏,并通過Looper.loop()
來開啟主線程的消息循環(huán)洛姑。
Message:消息載體
-
public int what
:標(biāo)識(shí) -
public int arg1
:保存int
數(shù)據(jù) -
public int arg2
:保存int
數(shù)據(jù) -
public Object obj
:保存任意數(shù)據(jù) -
long when
:記錄應(yīng)該被處理的時(shí)間值,換句話說就是延時(shí)時(shí)間 -
Handler target
:保存在主線程創(chuàng)建的Handler
對(duì)象引用 -
Runnable callback
:用來處理消息的回調(diào)器(一般不用皮服,見原理圖二) -
Meaage next
:指向下一個(gè)Message
用來形成一個(gè)鏈表 -
private static Message sPool
:用來緩存處理過的Message
楞艾,以便復(fù)用 -
Message obtain()
:它利用了Message
中消息池(sPool
)
先從Message
的創(chuàng)建入手参咙,官方更推薦使用obtain
來創(chuàng)建,而不是其構(gòu)造方法硫眯。它的構(gòu)造方法什么也不做蕴侧,只是完成了Message
的創(chuàng)建,而obtain
可以復(fù)用Message
两入,且有很多多種重載净宵。從內(nèi)存、效率來看裹纳,obtain
更優(yōu)择葡。
/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
*/
public Message() {
}
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() { // 最常見
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
可見,消息池采用了鏈表結(jié)構(gòu)剃氧,確實(shí)是復(fù)用了Message
刁岸。
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
下面是Message
的回收處理:
/**
* Return a Message instance to the global pool.
* <p>
* You MUST NOT touch the Message after calling this function because it has
* effectively been freed. It is an error to recycle a message that is currently
* enqueued or that is in the process of being delivered to a Handler.
* </p>
*/
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
前面也提到,Message
可以自行處理她我,處理的邏輯交給一個(gè)Runnable
虹曙,也就是callback
,讀者可以自行查閱obtain
的其他重載番舆,看看callback
賦值的情況酝碳,這里就不帖代碼了椅贱。
Handler:發(fā)送透揣、處理、移除消息
前面已經(jīng)分析過Message
的相關(guān)代碼庐镐,該小節(jié)將從發(fā)送Message
開始分析禾怠。其實(shí)返奉,也可以通過Handler
來創(chuàng)建Message
,其內(nèi)部也是調(diào)用Message.obtain
來創(chuàng)建Message
對(duì)象吗氏。
public final Message obtainMessage() {
return Message.obtain(this);
}
Handler
發(fā)送消息的方式有多種方式芽偏,這里選了一種最常見的,它的調(diào)用流程:
源碼如下:
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0); // 延時(shí)發(fā)送時(shí)間為0
}
// 延時(shí)發(fā)送
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
// 容錯(cuò)
if (delayMillis < 0) {
delayMillis = 0;
}
// 當(dāng)前時(shí)間加上延時(shí)時(shí)間就是真正發(fā)送Message的時(shí)間
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// Looper創(chuàng)建時(shí)弦讽,MessageQueue也創(chuàng)建了
// mQueue在Handler創(chuàng)建是就初始化了污尉,代碼在前面已經(jīng)貼過了
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// Message的成員變量,用來存儲(chǔ)Handler的對(duì)象引用
// 也就是記錄了由哪個(gè)Handler來處理這個(gè)Message
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 將Message插入到MessageQueue中往产,完成發(fā)送
return queue.enqueueMessage(msg, uptimeMillis);
}
另外被碗,Handler
的post
方法也可以發(fā)送Message
,且該Message
將交給它自身來處理仿村,當(dāng)讀者看過dispatchMessage
方法后就明白了锐朴。
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r; // 可以回憶下講述Message的那一節(jié)
return m;
}
下面是dispatchMessage
的源碼,它在Looper.loop
方法中被調(diào)用:
public void dispatchMessage(Message msg) {
// 如果Message.callback不為null蔼囊,就交給它自身處理
if (msg.callback != null) {
handleCallback(msg);
} else {
// 如果mCallback不為null焚志,交給該Handler處理
// mCallback是Handler內(nèi)部的一個(gè)接口
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 最后是我們最常見的衣迷,重寫handleMessage
handleMessage(msg);
}
}
接著來看看Callback
,這個(gè)接口長(zhǎng)啥樣:
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
它也是在Handler
創(chuàng)建的時(shí)候初始化的娩嚼,但調(diào)用的構(gòu)造方法不一樣,導(dǎo)致最后的初始化不一樣滴肿。無參的構(gòu)造方法會(huì)導(dǎo)致mCallback
為null
岳悟。
public Handler(Callback callback) {
this(callback, false);
}
最后來看看Handler
移除Message
的實(shí)現(xiàn),但真正的實(shí)現(xiàn)交給了MessageQueue
泼差。
public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null);
}
public final void removeMessages(int what, Object object) {
mQueue.removeMessages(this, what, object);
}
MessageQueue:存儲(chǔ)消息的贵少,以message的when排序優(yōu)先級(jí)
Message
經(jīng)Handler
發(fā)送后到達(dá)MessageQueue
,它采用鏈表的數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)Message
堆缘。下面是其構(gòu)造方法:
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
// 如果需要理解為什么loop方法不會(huì)卡住主線程滔灶,可以從這個(gè)本地方法開始入手
mPtr = nativeInit();
}
MessageQueue
中最主要的兩個(gè)方法是next
和enqueueMessage
。
next
方法也會(huì)阻塞(當(dāng)消息隊(duì)列為空時(shí)或當(dāng)前時(shí)間還沒到達(dá)Message
要處理的時(shí)間點(diǎn)時(shí))但不會(huì)卡住主線程吼肥,它的工作就是根據(jù)需求從消息隊(duì)列中取一個(gè)Message
录平。注意,next
方法是一個(gè)無限循環(huán)的方法缀皱。
enqueueMessage
方法的工作就是將一個(gè)Message
插入到消息隊(duì)列且位置是合適的斗这,因?yàn)樗鼤?huì)根據(jù)Message
要處理的時(shí)間點(diǎn)進(jìn)行排序,從它的插入操作也可以了解到MessageQueue
采用了鏈表啤斗。
由于next
方法和enqueueMessage
方法的源碼過長(zhǎng)表箭,下面只貼出enqueueMessage排序Message
的源碼:
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 查找合適的插入位置
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
關(guān)于阻塞但不會(huì)卡住主線程的問題在下一小節(jié)會(huì)提到。
Looper:從MessageQueue中獲取當(dāng)前需要處理的消息钮莲,并提交給Handler處理
關(guān)于Looper
在前面也講述了一部分免钻,現(xiàn)在只剩下loop
方法了,很重要崔拥。在它的內(nèi)部也開啟了一個(gè)無限循環(huán)极舔。
在for
無限循環(huán)中,有一句表明了會(huì)阻塞:
Message msg = queue.next(); // might block
導(dǎo)致它阻塞的原因在MessageQueue.next
方法链瓦。當(dāng)消息隊(duì)列退出或正在退出時(shí)姆怪,loop
方法會(huì)結(jié)束,也就是跳出無限循環(huán)澡绩。
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
在這個(gè)循環(huán)里還有一句需要注意的:
msg.target.dispatchMessage(msg);
意思時(shí)交給Handler
處理這個(gè)Message
稽揭。至此,整個(gè)流程就分析完了肥卡。
面試的時(shí)候可能會(huì)遇到:為什么loop
方法會(huì)阻塞主線程但不會(huì)卡住呢?以下的文章也許可以解答你心中的疑問:
異步任務(wù)
- 邏輯上:以多線程的方式完成的功能需求
- API上:指
AsyncTask
類
AsyncTask
- 在沒有
AsyncTask
之前璃哟,我們用Handler+Thread
就可以實(shí)現(xiàn)異步任務(wù)的功能需求 -
AsyncTask
是針對(duì)Handler
和Thread
的封裝,使用它編碼更加簡(jiǎn)潔喊递,更加高效 -
AsyncTask
封裝了ThreadPool
随闪,比直接使用Thread
效率要高
相關(guān)API
-
AsyncTack<Params,Progress,Ressult>
-
Params
啟動(dòng)任務(wù)執(zhí)行的輸入?yún)?shù),比如HTTP請(qǐng)求的URL -
Progress
后臺(tái)任務(wù)執(zhí)行的百分比 -
Result
后臺(tái)執(zhí)行任務(wù)最終返回的結(jié)果骚勘,比如String
-
-
execute(Params... params)
:?jiǎn)?dòng)任務(wù)铐伴,開始任務(wù)的執(zhí)行流程 -
onPreExcute()
:在分線程工作開始之前在UIThread中執(zhí)行,一般用來顯示提示視圖5 -
doInBackground(Params... params)
:在workerThread
中執(zhí)行俏讹,完成任務(wù)的主要工作当宴,通常需要較長(zhǎng)的時(shí)間 -
onPostExecute(Result result)
:在doInBackground
執(zhí)行完后再UIThread
中執(zhí)行,一般用來更新界面 -
publishProgress(Progress... values)
:在分線程中發(fā)布當(dāng)前進(jìn)度 -
onProgressUpdate(Progress... values)
:在主線程中更新進(jìn)度
AsyncTask
在具體的使用過程中也是有一些條件限制的泽疆,主要有以下幾種(摘自《Android開發(fā)藝術(shù)探索》):
- 1户矢、
AsyncTask
的類必須在主線程中加載,這就意味著第一次訪問AsyncTask
必須發(fā)生在主線程殉疼,當(dāng)然這個(gè)過程在Android 4.1
及以上版本中已經(jīng)被系統(tǒng)自動(dòng)完成梯浪。在Android 5.0
的源碼中,可以查看ActivityThread
的main
方法瓢娜,它會(huì)調(diào)用AsyncTask
的init
方法驱证,這就滿足了AsyncTask
的類必須在主線程中進(jìn)行加載這個(gè)條件了。 - 2恋腕、
AsyncTask
的對(duì)象必須在主線程中創(chuàng)建抹锄。 - 3、
execute
方法必須在UI線程調(diào)用荠藤。 - 4伙单、不要再程序中直接調(diào)用
onPreExecute
、onPostExecute
哈肖、doInBackground
和onProgressUpdate
方法 - 5吻育、一個(gè)
AsyncTask
對(duì)象只能執(zhí)行一次,即只能調(diào)用一次execute
方法淤井,否則會(huì)報(bào)運(yùn)行時(shí)異常 - 6布疼、在
Android 1.6
之前,AsyncTask
是串行執(zhí)行任務(wù)的币狠,Android 1.6
的時(shí)候AsyncTask
開始采用線程池里處理并行任務(wù)游两,但是從Android 3.0
開始,為了避免AsyncTask
所帶來的并發(fā)錯(cuò)誤漩绵,AsyncTask又采用一個(gè)線程來串行執(zhí)行任務(wù)贱案。盡管如此,在Android 3.0
以及后續(xù)的版本中止吐,我們?nèi)匀豢梢酝ㄟ^AsyncTask
的executeOnExecutor
方法來并行地執(zhí)行任務(wù)宝踪。
總結(jié)
文章以Handler
的創(chuàng)建過程為參照侨糟,簡(jiǎn)單介紹了Handler的原理和源碼。文章末尾對(duì)AsyncTask
進(jìn)行了簡(jiǎn)單的介紹瘩燥。讀者有空可以讀一讀《Android開發(fā)藝術(shù)探索》的第10章和第11章秕重。