1. 什么是Handler宪哩,為什么要有Handler?
Android中主線程也叫UI線程,主線程主要是用來創(chuàng)建第晰、更新UI的锁孟,而其他耗時(shí)操作,比如網(wǎng)絡(luò)訪問茁瘦,文件處理品抽、多媒體處理等都需要在子線程中操作,之所以在子線程中操作是為了保證UI的流暢程度甜熔,手機(jī)顯示的刷新頻率是60Hz圆恤,也就是一秒鐘刷新60次,每16.67毫秒刷新一次腔稀,為了不丟失幀盆昙,那么主線程處理代碼最好不要超過16毫秒羽历。當(dāng)子線程處理完數(shù)據(jù)后,為了防止UI處理邏輯的混亂(1.鎖機(jī)制會(huì)讓UI處理邏輯變得混亂淡喜;2.鎖機(jī)制會(huì)降低UI訪問的效率秕磷,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行),Android只允許主線程修改UI炼团,那么這時(shí)候就需要Handler來充當(dāng)子線程和主線程之間的橋梁了澎嚣。
2. Handler的使用方法:
- post(runnable)
- sendMessage(message)
其實(shí)post(runnable)和sendMessage(message)最終底層都是調(diào)用了sendMessageAtTime方法。
3. Handler消息機(jī)制的原理:
在主線程中Android默認(rèn)已經(jīng)調(diào)用了Looper.preperMainLooper方法们镜,調(diào)用該方法的目的是在Looper中創(chuàng)建MessageQueue成員變量并把Looper對(duì)象綁定到當(dāng)前線程中(ThreadLocal)币叹。當(dāng)調(diào)用Handler的sendMessage方法的時(shí)候,就將Message對(duì)象添加到了Looper創(chuàng)建的MessageQueue隊(duì)列中模狭,同時(shí)給Message指定了target對(duì)象颈抚,其實(shí)這個(gè)target對(duì)象就是Handler對(duì)象。主線程默認(rèn)執(zhí)行了Looper.looper()方法嚼鹉,該方法從Looper的成員變量MessageQueue隊(duì)列中調(diào)用Message的target對(duì)象的dispatchMessage方法(也就是msg.target.dispatchMessage方法)取出Message贩汉,然后在dispatchMessage方法中調(diào)用Message的target對(duì)象的handleMessage()方法(也就是msg.target.handleMessag),這樣就完成了整個(gè)消息機(jī)制锚赤。
我們從源碼的角度來分析上述原理匹舞,首先我們來看Handler的構(gòu)造方法:
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;
}
我們發(fā)現(xiàn)Looper.myLooper()內(nèi)部是通過sThreadLocal.get()獲得Looper的,(關(guān)于ThreadLocal:不同的線程訪問同一個(gè)ThreadLocal线脚,不管是get方法還是set方法對(duì)其所做的操作赐稽,僅限于各自線程內(nèi)部。這就是為什么用ThreadLocal來保存Looper浑侥,因?yàn)檫@樣才能使每一個(gè)線程有單獨(dú)唯一的Looper姊舵。)我們可能會(huì)想,這是通過get方法獲得Looper寓落,那么何時(shí)set的呢括丁?
當(dāng)我們觀察Looper這個(gè)類,發(fā)現(xiàn)有一個(gè)方法prepareMainLooper:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
在該方法的prepare中:
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
哦伶选,原來set方法是在這里調(diào)用的哈史飞,接下來我們還剩下一個(gè)疑問,那就是prepareMainLooper是在哪里調(diào)用的呢仰税?其實(shí)Android主線程對(duì)應(yīng)一個(gè)類ActivityThread构资,而每個(gè)Android應(yīng)用程序都是從該類的main函數(shù)開始的:
public static void main(String[] args) {
...
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.prepareMainLooper就是在這里調(diào)用的陨簇,首先程序是從這個(gè)main開始的吐绵,依次調(diào)用了prepareMainLooper ——> prepare ——> sThreadLocal.set,是不是有種茅塞頓開的感覺呢?我們繼續(xù)看這個(gè)main函數(shù)拦赠,內(nèi)部調(diào)用了Looper.loop,這是Handler機(jī)制很重要的一個(gè)方法葵姥,這也是為什么我們經(jīng)常說Android主線程默認(rèn)給我們調(diào)用了Looper.prepare和Looper.looper的原因荷鼠。
接下來我們?cè)賮砜次覀兪謩?dòng)調(diào)用了handler.sendMessage或者h(yuǎn)andler.postRunnable方法,默認(rèn)底層都是調(diào)用handler.sendMessageAtTime榔幸,該方法內(nèi)部調(diào)用了enqueueMessage:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
我們可以看到這里給msg.target指定了一個(gè)this對(duì)象允乐,其實(shí)這個(gè)this就是Handler對(duì)象(因?yàn)檫@是在Handler類中啊,又不是內(nèi)部類其它的)削咆,我們繼續(xù)看到queue.enqueueMessage方法:
boolean enqueueMessage(Message msg, long when) {
...
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//插入消息到鏈表的頭部:如果當(dāng)前MessageQueue消息隊(duì)列為空牍疏,或者插入的消息觸發(fā)時(shí)間為0,
//亦或是插入消息的觸發(fā)時(shí)間小于現(xiàn)有頭結(jié)點(diǎn)的觸發(fā)時(shí)間
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//根據(jù)觸發(fā)時(shí)間拨齐,將插入的消息放入到合適的位置
// 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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
通過調(diào)用此方法將消息發(fā)送到MessageQueue消息隊(duì)列中鳞陨,(這里我一直存在一個(gè)問題,這里明明做了觸發(fā)時(shí)間相關(guān)的排序瞻惋,并不符合隊(duì)列的先進(jìn)先出的特點(diǎn)厦滤,可為什么一直叫做消息隊(duì)列,就連用的類名翻譯過來也是如此歼狼,而不是鏈表呢掏导?還是說這是廣義上的隊(duì)列?如果有知道的大牛羽峰,可以跟我說說L伺亍!)
剛剛說過Android主線程梅屉,也就是ActivityThread的main函數(shù)會(huì)調(diào)用Looper.loop方法:
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
...
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();
}
}
loop方法中調(diào)用了queue.next()方法:next()方法是一個(gè)無線循環(huán)的方法值纱,如果消息隊(duì)列中沒有消息,那么next()方法會(huì)一直阻塞履植,當(dāng)有新消息到來時(shí)计雌,next()方法會(huì)將這條消息返回同時(shí)也將這條消息從鏈表中刪除。我們主要再來看msg.target.dispatchMessage方法玫霎,從上面的分析可以知道m(xù)sg.target其實(shí)就是Handler對(duì)象凿滤,我們找到dispatchMessage方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
其中調(diào)用了handleMessage,這也就是我們創(chuàng)建Handler類或其子類庶近,所需要重寫的handleMessage方法翁脆。至此,整個(gè)Handler消息機(jī)制就走通了鼻种,面試的時(shí)候反番,我們只需要說上面的原理,看源碼主要是為了更深入的了解,而不是簡單的記憶罢缸、背誦篙贸,要在理解的基礎(chǔ)上去記。
4. Handler引起的內(nèi)存泄漏以及解決辦法
原因:
非靜態(tài)內(nèi)部類持有外部類的強(qiáng)引用枫疆,導(dǎo)致外部Activity無法釋放爵川。
解決辦法:
1.handler內(nèi)部持有外部activity的弱引用
2.把handler改為靜態(tài)內(nèi)部類
3.mHandler.removeCallbacksAndMessage(尤其針對(duì)延時(shí)消息)
5. Handler相關(guān)的問題:
(1) Looper死循環(huán)為什么不會(huì)導(dǎo)致應(yīng)用卡死?
對(duì)于線程即是一段可執(zhí)行的代碼息楔,當(dāng)可執(zhí)行代碼執(zhí)行完成后寝贡,線程生命周期便該終止了,線程退出值依。而對(duì)于主線程圃泡,我們是絕不希望會(huì)被運(yùn)行一段時(shí)間,自己就退出愿险,那么如何保證能一直存活呢颇蜡?簡單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的,死循環(huán)便能保證不會(huì)被退出拯啦,例如澡匪,binder 線程也是采用死循環(huán)的方法,通過循環(huán)方式不同與 Binder 驅(qū)動(dòng)進(jìn)行讀寫操作褒链,當(dāng)然并非簡單地死循環(huán)唁情,無消息時(shí)會(huì)休眠。但這里可能又引發(fā)了另一個(gè)問題甫匹,既然是死循環(huán)又如何去處理其他事務(wù)呢甸鸟?通過創(chuàng)建新線程的方式。真正會(huì)卡死主線程的操作是在回調(diào)方法 onCreate/onStart/onResume 等操作時(shí)間過長兵迅,會(huì)導(dǎo)致掉幀抢韭,甚至發(fā)生 ANR,looper.loop 本身不會(huì)導(dǎo)致應(yīng)用卡死恍箭。
(2) 主線程的死循環(huán)一直運(yùn)行是不是特別消耗 CPU 資源呢刻恭?
其實(shí)不然,這里就涉及到 Linux pipe/epoll機(jī)制扯夭,簡單說就是在主線程的 MessageQueue 沒有消息時(shí)鳍贾,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此時(shí)主線程會(huì)釋放 CPU 資源進(jìn)入休眠狀態(tài)交洗,直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生骑科,通過往 pipe 管道寫端寫入數(shù)據(jù)來喚醒主線程工作。這里采用的 epoll 機(jī)制构拳,是一種IO多路復(fù)用機(jī)制咆爽,可以同時(shí)監(jiān)控多個(gè)描述符梁棠,當(dāng)某個(gè)描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮鞫饭。举|(zhì)同步I/O符糊,即讀寫是阻塞的。 所以說呛凶,主線程大多數(shù)時(shí)候都是處于休眠狀態(tài)濒蒋,并不會(huì)消耗大量CPU資源。
(3) 子線程中Toast把兔、showDialog問題
Toast 本質(zhì)是通過 window 顯示和繪制的(操作的是 window),而子線程不能更新UI 是因?yàn)?ViewRootImpl 的 checkThread方法 在 Activity 維護(hù) View樹 的行為瓮顽。
Dialog 亦是如此县好。
參考鏈接:Android 消息機(jī)制——你真的了解Handler?
喜歡本篇博客的簡友們暖混,就請(qǐng)來一波點(diǎn)贊缕贡,您的每一次關(guān)注,將成為我前進(jìn)的動(dòng)力拣播,謝謝晾咪!