這是我參與更文挑戰的第4天,活動詳情查看: 更文挑戰html
本文基於 Android 9.0.0的源代碼,來分析Handler的用法java
framework/base/core/java/andorid/os/
- Handler.java
- Looper.java
- Message.java
- MessageQueue.java
複製代碼
post()
和 send()
等方法來指定某個任務在某個時間執行常見的子線程中更新UI,復現代碼,更具體見 Android子線程和更新UI問題android
textView = (TextView) findViewById(R.id.txt);
new Thread(new Runnable() {
public void run() {
SystemClock.sleep(3000);//這句不加不會報錯,具體分析見上面連接
textView.setText("from來自子線程");
}
}).start();
複製代碼
運行異常信息git
ErrorInfo: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6903)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1050)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.view.View.requestLayout(View.java:19785)
at android.widget.TextView.checkForRelayout(TextView.java:7368)
at android.widget.TextView.setText(TextView.java:4480)
at android.widget.TextView.setText(TextView.java:4337)
at android.widget.TextView.setText(TextView.java:4312)
複製代碼
能夠看到錯誤發生在android.view.ViewRootImpl#checkThreadgithub
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
複製代碼
可見此處會判斷mThread
是否是等於當前線程 看下mThread
究竟是啥,在何處賦值的面試
public ViewRootImpl(Context context, Display display) {
...
mThread = Thread.currentThread();
...
}
複製代碼
在構造方法中被賦值的,也就是說是建立ViewRootImpl
時所在的線程 ViewRootImpl
又是在哪裏被建立的呢?這裏不深刻講了,是在main線程 更具體的異常分析能夠參考這個編程
android.os.Handler handler = new Handler(){//在主線程中獲取handler
@Override
public void handleMessage(final Message msg) {
//這裏接受並處理消息
}
};
new Thread(() -> {
try {
Thread.sleep(2000);//子線程中執行耗時操做
//發送消息
Message message = Message.obtain();
message.what=1;
message.obj=new Object();
handler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Handler().post(new Runnable() {
@Override
public void run() {
//doSomething
}
});
複製代碼
實例化一個 Handler
重寫handleMessage
方法 ,而後在須要的時候調用它的 send
以及 post
系列方法就能夠了,很是簡單易用,而且支持延時消息。(更多方法可查詢 API 文檔)數組
可是咱們並無看到Handler
是如何與MessageQueue
以及Looper
關聯起來的,下面咱們進入源碼分析下緩存
從構造函數開始,咱們一般從主線程中建立,先看下Handler的構造函數有哪些安全
Handler()
Handler(Callback callback)
Handler(Looper looper)
Handler(Looper looper, Callback callback)
Handler(boolean async)
Handler(Callback callback, boolean async)
Handler(Looper looper, Callback callback, boolean async)
看最後兩個構造方法就行,由於前面的幾個也是依次調用到後的方法
先看Handler(Callback callback, boolean async)
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
複製代碼
Handler(Looper looper, Callback callback, boolean async)
與上面的區別就是Looper
是賦值進去的。
由上面能夠看到調用Looper#myLooper
方法獲取到Looper對象, 若是mLooper == null的話,會拋出異常
Can't create handler inside thread that has not called Looper.prepare()
這個錯誤咱們應該也見過。實際上咱們在實例化 Handler
的時候 會去檢查當前線程的 Looper
是否存在,若是不存在則會報異常,也就是說在建立 Handler 以前必定須要先建立 Looper
。 咱們平時通常不會遇到這個錯,由於咱們大多數都是在主線程建立Handler
的,而爲何在主線程就不要本身建立Looper
,咱們待會再看,目前只須要知道若是Looper.myLooper()
沒有獲取到Looper
對象的話就會報這個錯。
咱們跟蹤Looper#myLooper
方法進去,解決爲何會拋出這個異常。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
複製代碼
只有一行代碼,從線程中取出Looper
對象,那麼咱們有理由相信,這個ThreadLocal
是經過set方法把Looper
對象設置進去的。關於ThreadLocal
,參考ThreadLocal 源碼分析。
想想ThreadLocal在哪裏把Looper對象設置進去了呢。回到剛纔想要解決的問題:Can’t create handler inside thread that has not called Looper.prepare() 。那會不會是Looper的prepare方法呢?
public static void prepare() {
prepare(true);
}
//調用私有構造方法
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));
}
複製代碼
ThreadLocal
確實是在Looper#prepare
方法裏把Looper
對象設置進去的,並且從第一行的判斷能夠知道,一個線程只有一個Looper
對象。
因此,要建立Handler
,那麼Looper.myLooper()
就必須非空,上面分析得出要非空,要先調用Looper.prepare()
。
到了這裏,Looper
與ThreadLocal
創建起了關聯。
接着上面繼續看下Looper
的構造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
複製代碼
每當咱們實例化一個 Looper
的時候會調用它的構造方法,並在其中實例化一個 MessageQueue
,相比於 Looper
和 Handler
,MessageQueue
就顯得相對複雜一些。由於內部用到了 JNI 編程。初始化、銷燬和入隊等事件都用到了 native
的方法。能夠在 android_os_MessageQueue 查看其源碼的定義。更多參考MessageQueue 的實例化
咱們接着看Handle
構造函數裏的
mQueue = mLooper.mQueue
咱們知道消息是存放在MessageQueue
消息隊列中的,而MessageQueue
就是在上面Looper
構造函數中new出來的,至此Handler
經過Looper
與MessageQueue
也創建起了關聯。
總結一下,建立Handler
,他的構造函數中會先調用Looper.myLooper()
獲取Looper,也便是從ThreadLocal
中獲取,而ThreadLocal
中要想獲取到,要先調用Looper.prepare()
來set值,那麼問題又來了,咱們寫程序時好像沒有手動調用Looper.prepare()
吧,也不會拋出異常。其實這是一個特殊狀況,咱們一般都是在主線程,也就是UI線程中建立handler的。而在主線程中,系統已經爲咱們建立了一個Looper
對象,因此不會拋出異常了,而那些會拋出異常報錯的狀況,是在子線程中建立的Handler
,可是又沒有調用Looper.prepare()
去建立Looper
對象。 繼續看,主線程在何時建立了Looper
對象吧。
在ActivityThread
的main方法,這個方法是應用程序的入口。
public static void main(String[] args) {
...
Looper.prepareMainLooper();
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();
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
複製代碼
能夠看到第一行仍是調用了prepar(false)
方法的(false表明不可退出)。因此主線程是已經建立了一個Looper
對象的。
Handler
的建立過程分析完畢,如今總算搞明白了。
最後再總結一下,Handler
的建立是依賴於Looper
的。而主線程是默認建立了一個Looper
對象的。每個Looper
會關聯一個線程(ThreadLocal
中封裝了Looper
)。每個Looper
中又會封裝一個消息隊列。 這樣一來,Handler
,Looper
,MessageQueue
,Thread
四個角色就關聯了起來。 Handler
在主線程中建立,是由於要和主線程的消息隊列關聯起來,那樣Handler#handleMessage
方法纔會在主線程中執行,那麼這樣在更新UI就是線程安全的了。
回想開頭咱們基礎用法裏提到 Handler
通常是經過一下2個方法發送的
handler.sendMessage(message); handler.post(runnable);
咱們先從第一個開始分析 handler.sendMessage(message)
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
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);
}
複製代碼
sendMessage
會調用sendMessageDelayed
方法並將message
對象傳進去,第二個參數是延時時間,使用sendMessage
方法時默認爲0的,最後都會調用sendMessageAtTime
。 上面分析了,在建立Looper
對象的時候,會建立一個MessageQueue
,因此只要Looper
是正常建立的話,消息隊列是不爲空的。 那麼到最後一行的enqueueMessage
方法,源碼以下
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼
將handler
自己賦值給msg.target
msg.setAsynchronous(true
設置message是不是異步的,這是message的一個屬性。同一個Thread只有一個Looper,一個MessageQueue,可是能夠有不少個Handler,若是Handler
初始化的時候async參數是true,那麼這個Handler
所post的全部的message都會帶上異步的屬性。能夠經過MessageQueue``的postSyncBarrier(long when)
來向隊列中插入一個同步分割欄,同步分割欄是一個特殊的message,這種message的target=null,就像一個卡子,當他被插入時,會卡住在這以後的全部的同步的message,只會摘取異步的message。固然也能夠經過MessageQueue的removeSyncBarrier(int token)來移除這個同步分割欄,token就是postSyncBarrier方法的返回值。可是目前這兩個方法都被hide了。因此你們通常用到的都只是普通的Message。(注:摘自從源碼去理解Handler)
而後最終調用queue.enqueueMessage
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
//消息是否正在使用
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//很明顯enqueueMessage須要同步,由於存在多個線程往一個Loop線程的MessageQueue中插入消息的場景。
//這裏實際上是將Message根據延時插入到特定的地方,先看下關鍵點1,mMessages其實表明消息隊列的頭部,若是mMessages爲空,說明尚未消息,若是當前插入的消息不須要延時,或者說延時比mMessages頭消息的延時要小,那麼當前要插入的消息就須要放在頭部
//至因而否須要喚醒隊列,則須要根據當前的Loop線程的狀態來判斷,後面講Loop線程的時候再回過頭說;
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//再來看下關鍵點2,這個時候須要將消息插入到隊列中間,其實就是找到第一個Delay事件小於當前Message的非空Message,並插入到它的前面,往隊列中插入消息時,若是Loop線程在睡眠,是不該該喚醒的,異步消息的處理會更加特殊一些,先不討論。
//最後看關鍵點3,若是須要喚醒Loop線程,經過nativeWake喚醒,以上,就是普通消息的插入。
// 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;
}
複製代碼
Messagequeue
中有一個對象mMessage
用於指向當前傳進的msg
,即最新的消息。而剛纔的sendMessageAtTime(Message msg, long uptimeMillis)
方法,第二個參數指定了時間,而後在這裏按照這個uptimeMillis
來進行消息的排序,這樣每個消息都是按照時間的排序關聯了起來,排在前面的消息指向了排在後面的消息。
以上是進入消息隊列的分析,Handler
調用sendMessage
方法的最終將message
對象傳進Messagequeue
。
那麼消息是怎麼從消息隊列出來的呢? 這時咱們要回看ActiviryThread
的main方法,去尋找點線索。源碼在上面已貼出。 發現了倒數第二行的Looper.loop()
,簡單理解就是消息執行循環操做。 android.os.Looper#loop
public static void loop() {
//確保MessageQueue準備好
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
...
for (;;) {
//for 無限循環,阻塞於消息隊列的 next() 方法;
//不斷從隊列中讀取消息並移除,若是隊列爲空,阻塞等待
Message msg = queue.next(); // might block
if (msg == null) {//跳出循環,looper退出就是利用了這點
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);
...
} finally {
...
}
...
//清理,回收到緩存池
msg.recycleUnchecked();
}
}
複製代碼
loop方法是個死循環,可是爲何不會卡死主線程呢,參考
Android中爲何主線程不會由於Looper.loop()裏的死循環卡死?
Handler後傳篇一: 爲何Looper中的Loop()方法不能致使主線程卡死?
loop內容有點複雜,借用一張圖來看下
當咱們調用 Looper#loop()
方法以後整個 Looper
循環就開始不斷地處理消息了。在上圖中就是咱們用綠色標記的一個循環。當咱們在循環中調用 MessageQueue#next()`` 方法來獲取下一個消息的時候,會調用 nativePollOnce()
方法,該方法可能會形成線程阻塞和非阻塞,當線程爲非阻塞的時候就會從 Native 層回到 Java 層,從 MessageQueuue
中取得一個消息以後給 Looper
進行處理。若是獲取的時候形成線程阻塞,那麼有兩種狀況會喚醒阻塞的線程,一個是當一個新的消息被加入到隊列中,而且將會早於以前隊列的全部消息被觸發,那麼此時將會從新設置超時時間。若是達到了超時時間一樣能夠從睡眠狀態中返回,也就回到了 Java 層繼續處理。因此,Native 層的 Looper
的做用就是經過阻塞消息隊列獲取消息的過程阻塞 Looper
。
再看下關鍵的Message msg = queue.next()
深刻分析參見MessageQueue中Message消息的執行以及 MessageQueue 的消息管理
Message next() {
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//是否須要阻塞等待,第一次必定不阻塞
// 調用 Native 層的 nativePollOnce() 方法進行精準時間的阻塞。
// 在 Native 層,將進入 pullInner() 方法,使用 epoll_wait 阻塞等待以讀取管道的通知。
// 若是沒有從 Native 層獲得消息,那麼這個方法就不會返回。此時主線程會釋放 CPU 資源進入休眠狀態。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {//互斥同步
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//是否存在barier
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
//存在同步分隔欄,找到後面異步屬性的msg
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//第一個消息是否須要阻塞等待,並計算出阻塞等待時間
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
//須要無限等待
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
//沒有能夠即刻執行的Message,查看是否存在須要處理的IdleHandler,若是不存在,則返回,阻塞等待,若是存在則執行IdleHandler
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
// 若是目前沒有消息,已經處在空閒狀態,則執行 idler.queueIdle
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
//處理完IdleHandler ,須要從新判斷Message隊列 nextPollTimeoutMillis賦值爲0
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
複製代碼
上面分析過msg.target
就是handler
,因此loop
循環的時候又把消息取出扔給handler#dispatchMessage
方法了,咱們來看下
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製代碼
因爲這種方法沒有傳callback
,因此最終調用handleMessage
,咱們來看下
/** * Subclasses must implement this to receive messages. */
public void handleMessage(Message msg) {
}
複製代碼
看到這裏,相信你們應該很熟悉了,這就是咱們重寫的方法。
咱們再看看另外一個發送消息的方法 handler.post(runnable)
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
複製代碼
接收一個實現了Runable
接口的對象,而後將其傳進getPostMessage()
方法。跟進getPostMessage()
方法看看
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
複製代碼
其實就是將Runable
包裝成message的callback
嘛。 因此,若是咱們使用post
方法發送消息,在執行dispatchMessage
的時候,callback
字段是不爲空的,那麼就會執行handleCallback()
方法,而不是執行handleMessage
方法了。
private static void handleCallback(Message message) {
message.callback.run();
}
複製代碼
經過上面的分析可知 MessageQueue
經過 next
方法經過死循環獲取下一個要處理的 Message, 若當前時刻不存在要處理的消息, 下次循環會進行睡眠操做
public static interface IdleHandler {
/** * Called when the message queue has run out of messages and will now * wait for more. Return true to keep your idle handler active, false * to have it removed. This may be called if there are still messages * pending in the queue, but they are all scheduled to be dispatched * after the current time. */
boolean queueIdle();
}
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
複製代碼
經過上述代碼能夠獲得如下的信息
public final class MessageQueue {
// 空閒消息集合
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
// 空閒消息處理者的數組
private IdleHandler[] mPendingIdleHandlers;
Message next() {
......
for (;;) {
......
synchronized (this) {
// 省略獲取 msg 的代碼
......
// 1. 從空閒消息集合 mIdleHandlers 中獲取 空閒處理者 數量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 2 若無空閒處理者, 則進行下一次 for 循環
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
......
// 3. 將空閒消息處理者集合轉爲數組
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 4. 處理空閒消息
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];// 獲取第 i 給位置的空閒處理者
mPendingIdleHandlers[i] = null; // 置空
boolean keep = false;
try {
// 4.1 處理空閒消息
keep = idler.queueIdle();
} catch (Throwable t) {
......
}
if (!keep) {
synchronized (this) {
// 4.2 走到這裏表示它是一次性的處理者, 從 mIdleHandlers 移除
mIdleHandlers.remove(idler);
}
}
}
......
}
}
}
複製代碼
好的, 能夠看到 MessageQueue.next 在獲取不到 msg 時, 會進行一些空閒消息的處理
咱們發現不論是使用post
方法仍是sendMessage
方法來發送消息,最終都會調用sendMessageDelayed
方法。handler
將消息追加到消息隊列中的過程都是同樣的,而後Looper
不斷的從MessageQueue
中取出消息,並由handler
去分發消息,處理消息,這樣就構成了完善的Android消息機制體系。
Handler
雖然簡單易用,可是要用好它仍是須要注意一點。
因爲 Handler
的特性,它在 Android 裏的應用很是普遍,好比: AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等。
Handler
容許咱們發送延時消息,若是在延時期間用戶關閉了 Activity
,那麼該 Activity
會泄露。
這個泄露是由於Message
會持有Handler
,而又由於 Java 的特性,內部類會持有外部類,使得 Activity
會被 Handler
持有,這樣最終就致使 Activity 泄露。
解決該問題的最有效的方法是:將 Handler 定義成靜態的內部類,在內部持有 Activity 的弱引用,並及時移除全部消息。
示例代碼以下:
private static class SafeHandler extends Handler {
private WeakReference<HandlerActivity> ref;
public SafeHandler(HandlerActivity activity) {
this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
HandlerActivity activity = ref.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
複製代碼
而且再在 Activity.onDestroy()
前移除消息,加一層保障:
@Override
protected void onDestroy() {
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
複製代碼
這樣雙重保障,就能徹底避免內存泄露了。
注意:單純的在 onDestroy 移除消息並不保險,由於 onDestroy 並不必定執行。
在 Handler
的構造方法中有幾個 要求傳入 Callback
,那它是什麼,又能作什麼呢?
來看看 Handler.dispatchMessage(msg)
方法:
public void dispatchMessage(Message msg) {
//這裏的 callback 是 Runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
//若是 callback 處理了該 msg 而且返回 true, 就不會再回調 handleMessage
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製代碼
能夠看到 Handler.Callback
有優先處理消息的權利 ,當一條消息被 Callback
處理並攔截(返回 true),那麼 Handler
的 handleMessage(msg)
方法就不會被調用了;若是 Callback
處理了消息,可是並無攔截,那麼就意味着一個消息能夠同時被 Callback 以及 Handler 處理。
這個就頗有意思了,這有什麼做用呢?
咱們能夠利用 Callback 這個攔截機制來攔截 Handler 的消息!
場景:Hook ActivityThread.mH
, 在 ActivityThread
中有個成員變量 mH
,它是個 Handler
,又是個極其重要的類,幾乎全部的插件化框架都使用了這個方法。
因爲 Handler
極爲經常使用,因此爲了節省開銷,Android 給 Message
設計了回收機制,因此咱們在使用的時候儘可能複用 Message
,減小內存消耗。
方法有二:
Message
的靜態方法 Message.obtain();
獲取;Handler
的公有方法 handler.obtainMessage();
。咱們能夠利用 Looper
的機制來幫助咱們作一些事情:
將 Runnable
post 到主線程執行
Activity.runOnUiThread(Runnable)
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
複製代碼
View.post(Runnable)
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
//直接經過handler發送Post消息
return attachInfo.mHandler.post(action);
}
//先加入隊列,等attachInfo被賦值時,會經過handler發送消息.
getRunQueue().post(action);
return true;
}
複製代碼
利用 Looper
判斷當前線程是不是主線程
public final class MainThread {
private MainThread() {
}
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
public static void run(@NonNull Runnable runnable) {
if (isMainThread()) {
runnable.run();
}else{
HANDLER.post(runnable);
}
}
public static boolean isMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}
複製代碼
Looper
和 Handler
不須要再一個線程中,默認的狀況下會從ThreadLocal
中取當前線程對應的 Looper
,但咱們能夠經過顯式地指定一個 Looper
的方式來建立 Handler
. 好比,當咱們想要在子線程中發送消息到主線程中,那麼咱們能夠
Handler handler = new Handler(Looper.getMainLooper());
複製代碼
Handler的post()方法
View的post()方法
Activity的runOnUiThread()方法
參見Handler後傳篇二: 該如何理解Handler的"異步"?
調用 MessageQueue.next()
方法的時候會調用 Native 層的 nativePollOnce()
方法進行精準時間的阻塞。在 Native 層,將進入 pullInner()
方法,使用 epoll_wait
阻塞等待以讀取管道的通知。若是沒有從 Native 層獲得消息,那麼這個方法就不會返回。此時主線程會釋放 CPU 資源進入休眠狀態。
當咱們加入消息的時候,會調用 MessageQueue.enqueueMessage()
方法,添加完 Message 後,若是消息隊列被阻塞,則會調用 Native 層的 nativeWake()
方法去喚醒。它經過向管道中寫入一個消息,結束上述阻塞,觸發上面提到的 nativePollOnce()
方法返回,好讓加入的 Message 獲得分發處理。
MessageQueue.enqueueMessage()
使用 synchronized 代碼塊去進行同步。
資料:Android 中的 Handler 的 Native 層研究
quit() 和 quitSafely() 有什麼區別 子線程中建立了 Looper,在使用完畢後,終止消息循環的方法? quit() 和 quitSafely() 的本質是什麼?
quit()
和 quitSafely()
的本質就是讓消息隊列的 next()
返回 null
,以此來退出Looper.loop()
。 quit()
調用後直接終止 Looper
,不在處理任何 Message
,全部嘗試把 Message
放進消息隊列的操做都會失敗,好比 Handler.sendMessage()
會返回 false,可是存在不安全性,由於有可能有 Message
還在消息隊列中沒來的及處理就終止Looper
了。 quitSafely()
調用後會在全部消息都處理後再終止 Looper
,全部嘗試把 Message
放進消息隊列的操做也都會失敗。
由前文可得出一些知識點,彙總一下,方便記憶。
Handler
的背後有 Looper
、MessageQueue
支撐,Looper
負責消息分發,MessageQueue
負責消息管理Handler
以前必定須要先建立Looper
Looper
有退出的功能,可是主線程的 Looper
不容許退出 Looper
須要本身調用 Looper.myLooper().quit();
退出Runnable
被封裝進了 Message
,能夠說是一個特殊的 Message
Handler.handleMessage()
所在的線程是 Looper.loop()
方法被調用的線程,也能夠說成Looper
所在的線程,並非建立 Handler
的線程Handler
可能會致使內存泄露,即使在 Activity.onDestroy 裏移除延時消息,必需要寫成靜態內部類