天天看點

android 子線程toast handle,Android之Handler淺析

Android之Handler淺析

Handler相信每個從事Android開發的小夥伴都非常熟悉了, 最常用的場景就是在子線程中進行資料操作然後通過handler消息機制通知到UI線程來更新UI,地球人都知道在子線程中更新UI,一般情況下都會報錯。每每出去面試被問到“handler原理”,“消息是怎麼從子線程發送到主線程的”等等handler底層的實作,就懵逼了。

雖然網上關于分析handler的部落格問丈夫非常多,已經有很多大佬分析的非常清晰了。這裡分析主要是為了讓自己加深了解,另一方面就是想分享所學知識。

Android消息循環流程圖如下所示:

android 子線程toast handle,Android之Handler淺析

主要涉及的角色如下所示:

Message:消息。

MessageQueue: 消息隊列,負責消息的存儲于管理,負責管理由handler發過來的Message,讀取會自動删除消息,單連結清單維護,插入和删除上有優勢。 在其next()方法中會無限循環,不短判斷是否有消息,有就傳回這條消息并移除。

Looper: 消息循環器,負責關聯線程以及消息的分發,在該線程下從MessageQueue擷取Message,分發給handler,Looper建立的時候會建立一個MessageQueue,調用loop()方法的時候消息循環開始,其中會不斷調用MessageQueue的next()方法,有消息就處理,否則就阻塞在MessageQueue的next()方法中,當Looper的quit()被調用的時候會調用MessageQueue的quit(),此時的next()會傳回null,然後loop()方法也就跟着退出。

Handler:消息處理器,負責發送并處理消息,面向開發則,提供API,并隐藏背後實作的細節。

整個消息循環流程還是比較清晰的,具體來說:

1.Handler通過sendMessage()發送消息Message到隊列MessageQueue.

2.Looper通過loop()不斷提取觸發條件的Message,并将Message交給對應的target handler來處理。

3.target handler調用自身的handleMessage()方法來處理Message。

事實上,在整個消息循環的流程中,并不止隻有Java層參與,很多重要的工作都是在C++層來完成,我們來看看下圖的這些類的調用它關系。

android 子線程toast handle,Android之Handler淺析

注:虛線表示關聯關系,實作表示調用關系。

在這些類中MessageQueue 是Java層與C++層維系的橋梁,MessageQueue與Looper相關功能都通過MessageQueue的Native方法來完成,而其他虛線連接配接的類隻有關聯關系,并沒有直接調用的關系,它們發生關系的橋梁是MessageQueue.

總結:

Handler發送的消息由MessageQueue存儲管理,并由Looper負責回調消息到handlerMessage()

線程的切換由Looper完成,handlerMessage()所線上程由Looper.loop()調用者所線上程決定。

下面來列舉我們幾個工作中或許都會遇到的問題。

Handler引起的記憶體洩漏原因以及最佳解決方案

Handler允許我們發送延遲消息,如果在延時期間内使用者關閉了activity,那麼該activity會洩漏。這個洩漏是因為Message會出油Handler,而又因為Java的特性,内部類會持有外部類,使得activity會被Handler持有, 這樣最終就會導緻activity洩漏了。

解決的辦法就是:将Handler定義為靜态的内部類,在内部持有activity的弱引用,并在activity的ondestroy()中調用handler.removeCallbacksAndMessage(null)及時移除所有消息。

private static class SafeHandler extends Handler {

private WeakReference 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();

}

為什麼我們能在主線程直接使用Handler,而不需要建立Looper?

通常我們認為ActivityThread就是主線程,事實上它并不是一個線程,而是主線程操作的管理者。在ActivityThread.main()方法中調用了Looper.prepareMainLooper()方法建立了主線程的looper,并且調用了loop()方法,所有我們就可以直接使用Handler了。

是以我們可以利用Callback這個攔截機制來攔截Handler的消息,如大部分插件化架構中的Hook ActivityThread.mH的處理。

主線程的Looper不允許退出

主線程不允許退出,退出就意味APP要挂了。

Handler裡藏着Callback能幹什麼?

Handler.Callback 有優先處理消息的權利 ,當一條消息被 Callback 處理并攔截(傳回 true),那麼 Handler 的 handleMessage(msg) 方法就不會被調用了;如果 Callback 處理了消息,但是并沒有攔截,那麼就意味着一個消息可以同時被 Callback 以及 Handler 處理。

建立Message執行個體的最佳方式

為了節省開銷,Android給Message設計了回收機制,是以我們在使用的時候盡量複用Message,減少記憶體的消耗:

通過Message的靜态方法Message.obtain()

通過Handler的共有方法handler.obtainMessage()

子線程裡彈Toast的正确姿勢

本質上是因為Toast的實作依賴于Handler,按子線程使用Handler的要求修改即可,同理的還有就是dialog。

new Thread(new Runnable() {

@Override

public void run() {

Looper.prepare();

Toast.makeText(MainActivity.this, "不會崩潰啦!", Toast.LENGTH_SHORT).show();

Looper.loop();

}

}).start();

妙用Looper機制

将Runnable post 到主線程執行

利用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();

}

}

主線程的死循環一直運作是不是特别消耗CPU資源呢?

并不是,這裡就涉及到Linux pipe/epoll機制,簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法裡,此時主線程會釋放CPU資源進入休眠狀态,直到下個消息到達或者有事務發生,通過往pipe管道寫端寫入資料來喚醒主線程工作。這裡采用的epoll機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程式進行讀或寫操作,本質是同步I/O,即讀寫是阻塞的。是以說,主線程大多數時候都是處于休眠狀态,并不會消耗大量CPU資源。