掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網(wǎng)交流
handler是Android中的消息處理機制,是一種線程間通信的解決方案,同時你也可以理解為它天然的為我們在主線程創(chuàng)建一個隊列,隊列中的消息順序就是我們設置的延遲的時間,如果你想在Android中實現(xiàn)一個隊列的功能,不妨第一時間考慮一下它。本文分為三部分:

1. 一個線程中最多有多少個Handler,Looper,MessageQueue?
2. Looper死循環(huán)為什么不會導致應用卡死,會耗費大量資源嗎?
3. 子線程的如何更新UI,比如Dialog,Toast等?系統(tǒng)為什么不建議子線程中更新UI?
4. 主線程如何訪問網(wǎng)絡?
5. 如何處理Handler使用不當造成的內存泄漏?
6. Handler的消息優(yōu)先級,有什么應用場景?
7. 主線程的Looper何時退出?能否手動退出?
8. 如何判斷當前線程是安卓主線程?
9. 正確創(chuàng)建Message實例的方式?
1. ThreadLocal
2. epoll機制
3. Handle同步屏障機制
4. Handler的鎖相關問題
5. Handler中的同步方法
1. HandlerThread
2. IntentService
3. 如何打造一個不崩潰的APP
4. Glide中的運用
Handler的源碼和常見問題的解答
下面來看一下官方對其的定義:
- A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread.
大意就是Handler允許你發(fā)送Message/Runnable到線程的消息隊列(MessageQueue)中,每個Handler實例和一個線程以及那個線程的消息隊列相關聯(lián)。當你創(chuàng)建一個Handler時應該和一個Looper進行綁定(主線程默認已經(jīng)創(chuàng)建Looper了,子線程需要自己創(chuàng)建Looper),它向Looper的對應的消息隊列傳送Message/Runnable同時在那個Looper所在線程處理對應的Message/Runnable。下面這張圖就是Handler的工作流程
Handler工作流程圖
可以看到在Thread中,Looper的這個傳送帶其實就一個死循環(huán),它不斷的從消息隊列MessageQueue中不斷的取消息,最后交給Handler.dispatchMessage進行消息的分發(fā),而Handler.sendXXX,Handler.postXXX這些方法把消息發(fā)送到消息隊列中MessageQueue,整個模式其實就是一個生產(chǎn)者-消費者模式,源源不斷的生產(chǎn)消息,處理消息,沒有消息時進行休眠。MessageQueue是一個由單鏈表構成的優(yōu)先級隊列(取的都是頭部,所以說是隊列)。
前面說過,當你創(chuàng)建一個Handler時應該和一個Looper進行綁定(綁定也可以理解為創(chuàng)建,主線程默認已經(jīng)創(chuàng)建Looper了,子線程需要自己創(chuàng)建Looper),因此我們先來看看主線程中是如何處理的:
- //ActivityThread.java
- public static void main(String[] args) {
- ···
- Looper.prepareMainLooper();
- ···
- ActivityThread thread = new ActivityThread();
- thread.attach(false, startSeq);
- 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");
- }
可以看到在ActivityThread中的main方法中,我們先調用了Looper.prepareMainLooper()方法,然后獲取當前線程的Handler,最后調用Looper.loop()。先來看一下Looper.prepareMainLooper()方法
- //Looper.java
- /**
- * 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();
- }
- }
- //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));
- }
可以看到在Looper.prepareMainLooper()方法中創(chuàng)建了當前線程的Looper,同時將Looper實例存放到線程局部變量sThreadLocal(ThreadLocal)中,也就是每個線程有自己的Looper。在創(chuàng)建Looper的時候也創(chuàng)建了該線程的消息隊列,可以看到prepareMainLooper會判斷sMainLooper是否有值,如果調用多次,就會拋出異常,所以也就是說主線程的Looper和MessageQueue只會有一個。同理子線程中調用Looper.prepare()時,會調用prepare(true)方法,如果多次調用,也會拋出每個線程只能由一個Looper的異常,總結起來就是每個線程中只有一個Looper和MessageQueue。
- //Looper.java
- private Looper(boolean quitAllowed) {
- mQueue = new MessageQueue(quitAllowed);
- mThread = Thread.currentThread();
- }
再來看看主線程sMainThreadHandler = thread.getHandler(),getHandler獲取到的實際上就是mH這個Handler。
- //ActivityThread.java
- final H mH = new H();
- @UnsupportedAppUsage
- final Handler getHandler() {
- return mH;
- }
mH這個Handler是ActivityThread的內部類,通過查看handMessage方法,可以看到這個Handler處理四大組件,Application等的一些消息,比如創(chuàng)建Service,綁定Service的一些消息。
- //ActivityThread.java
- class H extends Handler {
- ···
- public void handleMessage(Message msg) {
- if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
- switch (msg.what) {
- case BIND_APPLICATION:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
- AppBindData data = (AppBindData)msg.obj;
- handleBindApplication(data);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case EXIT_APPLICATION:
- if (mInitialApplication != null) {
- mInitialApplication.onTerminate();
- }
- Looper.myLooper().quit();
- break;
- case RECEIVER:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
- handleReceiver((ReceiverData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case CREATE_SERVICE:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
- handleCreateService((CreateServiceData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case BIND_SERVICE:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
- handleBindService((BindServiceData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case UNBIND_SERVICE:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
- handleUnbindService((BindServiceData)msg.obj);
- schedulePurgeIdler();
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case SERVICE_ARGS:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
- handleServiceArgs((ServiceArgsData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case STOP_SERVICE:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");
- handleStopService((IBinder)msg.obj);
- schedulePurgeIdler();
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- ···
- case APPLICATION_INFO_CHANGED:
- mUpdatingSystemConfig = true;
- try {
- handleApplicationInfoChanged((ApplicationInfo) msg.obj);
- } finally {
- mUpdatingSystemConfig = false;
- }
- break;
- case RUN_ISOLATED_ENTRY_POINT:
- handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
- (String[]) ((SomeArgs) msg.obj).arg2);
- break;
- case EXECUTE_TRANSACTION:
- final ClientTransaction transaction = (ClientTransaction) msg.obj;
- mTransactionExecutor.execute(transaction);
- if (isSystem()) {
- // Client transactions inside system process are recycled on the client side
- // instead of ClientLifecycleManager to avoid being cleared before this
- // message is handled.
- transaction.recycle();
- }
- // TODO(lifecycler): Recycle locally scheduled transactions.
- break;
- case RELAUNCH_ACTIVITY:
- handleRelaunchActivityLocally((IBinder) msg.obj);
- break;
- case PURGE_RESOURCES:
- schedulePurgeIdler();
- break;
- }
- Object obj = msg.obj;
- if (obj instanceof SomeArgs) {
- ((SomeArgs) obj).recycle();
- }
- if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
- }
- }
最后我們查看Looper.loop()方法
- //Looper.java
- public static void loop() {
- //獲取ThreadLocal中的Looper
- final Looper me = myLooper();
- ···
- final MessageQueue queue = me.mQueue;
- ···
- for (;;) { //死循環(huán)
- //獲取消息
- Message msg = queue.next(); // might block
- if (msg == null) {
- // No message indicates that the message queue is quitting.
- return;
- }
- ···
- msg.target.dispatchMessage(msg);
- ···
- //回收復用
- msg.recycleUnchecked();
- }
- }
在loop方法中是一個死循環(huán),在這里從消息隊列中不斷的獲取消息queue.next(),然后通過Handler(msg.target)進行消息的分發(fā),其實并沒有什么具體的綁定,因為Handler在每個線程中對應只有一個Looper和消息隊列MessageQueue,自然要靠它來處理,也就是是調用Looper.loop()方法。在Looper.loop()的死循環(huán)中不斷的取消息,最后回收復用。
這里要強調一下Message中的參數(shù)target(Handler),正是這個變量,每個Message才能找到對應的Handler進行消息分發(fā),讓多個Handler同時工作。
再來看看子線程中是如何處理的,首先在子線程中創(chuàng)建一個Handler并發(fā)送Runnable
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_three);
- new Thread(new Runnable() {
- @Override
- public void run() {
- new Handler().post(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
- }
- });
- }
- }).start();
- }
運行后可以看到錯誤日志,可以看到提示我們需要在子線程中調用Looper.prepare()方法,實際上就是要創(chuàng)建一個Looper和你的Handler進行“關聯(lián)”。
- --------- beginning of crash
- 020-11-09 15:51:03.938 21122-21181/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: Thread-2
- Process: com.jackie.testdialog, PID: 21122
- java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
- at android.os.Handler.
(Handler.java:207) - at android.os.Handler.
(Handler.java:119) - at com.jackie.testdialog.HandlerActivity$1.run(HandlerActivity.java:31)
- at java.lang.Thread.run(Thread.java:919)
添加Looper.prepare()創(chuàng)建Looper,同時調用Looper.loop()方法開始處理消息。
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_three);
- new Thread(new Runnable() {
- @Override
- public void run() {
- //創(chuàng)建Looper,MessageQueue
- Looper.prepare();
- new Handler().post(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
- }
- });
- //開始處理消息
- Looper.loop();
- }
- }).start();
- }
這里需要注意在所有事情處理完成后應該調用quit方法來終止消息循環(huán),否則這個子線程就會一直處于循環(huán)等待的狀態(tài),因此不需要的時候終止Looper,調用Looper.myLooper().quit()。
看完上面的代碼可能你會有一個疑問,在子線程中更新UI(進行Toast)不會有問題嗎,我們Android不是不允許在子線程更新UI嗎,實際上并不是這樣的,在ViewRootImpl中的checkThread方法會校驗mThread != Thread.currentThread(),mThread的初始化是在ViewRootImpl的的構造器中,也就是說一個創(chuàng)建ViewRootImpl線程必須和調用checkThread所在的線程一致,UI的更新并非只能在主線程才能進行。
- void checkThread() {
- if (mThread != Thread.currentThread()) {
- throw new CalledFromWrongThreadException(
- "Only the original thread that created a view hierarchy can touch its views.");
- }
- }
這里需要引入一些概念,Window是Android中的窗口,每個Activity和Dialog,Toast分別對應一個具體的Window,Window是一個抽象的概念,每一個Window都對應著一個View和一個ViewRootImpl,Window和View通過ViewRootImpl來建立聯(lián)系,因此,它是以View的形式存在的。我們來看一下Toast中的ViewRootImpl的創(chuàng)建過程,調用toast的show方法最終會調用到其handleShow方法
- //Toast.java
- public void handleShow(IBinder windowToken) {
- ···
- if (mView != mNextView) {
- // Since the notification manager service cancels the token right
- // after it notifies us to cancel the toast there is an inherent
- // race and we may attempt to add a window after the token has been
- // invalidated. Let us hedge against that.
- try {
- mWM.addView(mView, mParams); //進行ViewRootImpl的創(chuàng)建
- trySendAccessibilityEvent();
- } catch (WindowManager.BadTokenException e) {
- /* ignore */
- }
- }
- }
這個mWM(WindowManager)的最終實現(xiàn)者是WindowManagerGlobal,其的addView方法中會創(chuàng)建ViewRootImpl,然后進行root.setView(view, wparams, panelParentView),通過ViewRootImpl來更新界面并完成Window的添加過程。
- //WindowManagerGlobal.java
- root = new ViewRootImpl(view.getContext(), display); //創(chuàng)建ViewRootImpl
- view.setLayoutParams(wparams);
- mViews.add(view);
- mRoots.add(root);
- mParams.add(wparams);
- // do this last because it fires off messages to start doing things
- try {
- //ViewRootImpl
- root.setView(view, wparams, panelParentView);
- } catch (RuntimeException e) {
- // BadTokenException or InvalidDisplayException, clean up.
- if (index >= 0) {
- removeViewLocked(index, true);
- }
- throw e;
- }
- }
setView內部會通過requestLayout來完成異步刷新請求,同時也會調用checkThread方法來檢驗線程的合法性。
- @Override
- public void requestLayout() {
- if (!mHandlingLayoutInLayoutRequest) {
- checkThread();
- mLayoutRequested = true;
- scheduleTraversals();
- }
- }
因此,我們的ViewRootImpl的創(chuàng)建是在子線程,所以mThread的值也是子線程,同時我們的更新也是在子線程,所以不會產(chǎn)生異常,同時也可以參考這篇文章分析,寫的非常詳細。同理下面的代碼也可以驗證這個情況
- //子線程中調用
- public void showDialog(){
- new Thread(new Runnable() {
- @Override
- public void run() {
- //創(chuàng)建Looper,MessageQueue
- Looper.prepare();
- new Handler().post(new Runnable() {
- @Override
- public void run() {
- builder = new AlertDialog.Builder(HandlerActivity.this);
- builder.setTitle("jackie");
- alertDialog = builder.create();
- alertDialog.show();
- alertDialog.hide();
- }
- });
- //開始處理消息
- Looper.loop();
- }
- }).start();
- }
在子線程中調用showDialog方法,先調用alertDialog.show()方法,再調用alertDialog.hide()方法,hide方法只是將Dialog隱藏,并沒有做其他任何操作(沒有移除Window),然后再在主線程調用alertDialog.show();便會拋出Only the original thread that created a view hierarchy can touch its views異常了。
- 2020-11-09 18:35:39.874 24819-24819/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: main
- Process: com.jackie.testdialog, PID: 24819
- android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
- at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8191)
- at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1420)
- at android.view.View.requestLayout(View.java:24454)
- at android.view.View.setFlags(View.java:15187)
- at android.view.View.setVisibility(View.java:10836)
- at android.app.Dialog.show(Dialog.java:307)
- at com.jackie.testdialog.HandlerActivity$2.onClick(HandlerActivity.java:41)
- at android.view.View.performClick(View.java:7125)
- at android.view.View.performClickInternal(View.java:7102)
所以在線程中更新UI的重點是創(chuàng)建它的ViewRootImpl和checkThread所在的線程是否一致。
如何在主線程中訪問網(wǎng)絡
在網(wǎng)絡請求之前添加如下代碼
- StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
- StrictMode.setThreadPolicy(policy);
StrictMode(嚴苛模式)Android2.3引入,用于檢測兩大問題:ThreadPolicy(線程策略)和VmPolicy(VM策略),這里把嚴苛模式的網(wǎng)絡檢測關了,就可以在主線程中執(zhí)行網(wǎng)絡操作了,一般是不建議這么做的。關于嚴苛模式可以查看這里。
系統(tǒng)為什么不建議在子線程中訪問UI?
這是因為 Android 的UI控件不是線程安全的,如果在多線程中并發(fā)訪問可能會導致UI控件處于不可預期的狀態(tài),那么為什么系統(tǒng)不對UI控件的訪問加上鎖機制呢?缺點有兩個
所以最簡單且高效的方法就是采用單線程模型來處理UI操作。(安卓開發(fā)藝術探索)
子線程如何通知主線程更新UI(都是通過Handle發(fā)送消息到主線程操作UI的)
Looper死循環(huán)為什么不會導致應用卡死,會耗費大量資源嗎?
從前面的主線程、子線程的分析可以看出,Looper會在線程中不斷的檢索消息,如果是子線程的Looper死循環(huán),一旦任務完成,用戶應該手動退出,而不是讓其一直休眠等待。(引用自Gityuan)線程其實就是一段可執(zhí)行的代碼,當可執(zhí)行的代碼執(zhí)行完成后,線程的生命周期便該終止了,線程退出。而對于主線程,我們是絕不希望會被運行一段時間,自己就退出,那么如何保證能一直存活呢?簡單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的,死循環(huán)便能保證不會被退出,例如,binder 線程也是采用死循環(huán)的方法,通過循環(huán)方式不同與 Binder 驅動進行讀寫操作,當然并非簡單地死循環(huán),無消息時會休眠。Android是基于消息處理機制的,用戶的行為都在這個Looper循環(huán)中,我們在休眠時點擊屏幕,便喚醒主線程繼續(xù)進行工作。
主線程的死循環(huán)一直運行是不是特別消耗 CPU 資源呢?其實不然,這里就涉及到 Linux pipe/epoll機制,簡單說就是在主線程的 MessageQueue 沒有消息時,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此時主線程會釋放 CPU 資源進入休眠狀態(tài),直到下個消息到達或者有事務發(fā)生,通過往 pipe 管道寫端寫入數(shù)據(jù)來喚醒主線程工作。這里采用的 epoll 機制,是一種IO多路復用機制,可以同時監(jiān)控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程序進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。所以說,主線程大多數(shù)時候都是處于休眠狀態(tài),并不會消耗大量CPU資源。
主線程的Looper何時退出
在App退出時,ActivityThread中的mH(Handler)收到消息后,執(zhí)行退出。
- //ActivityThread.java
- case EXIT_APPLICATION:
- if (mInitialApplication != null) {
- mInitialApplication.onTerminate();
- }
- Looper.myLooper().quit();
- break;
如果你嘗試手動退出主線程Looper,便會拋出如下異常
- Caused by: java.lang.IllegalStateException: Main thread not allowed to quit.
- at android.os.MessageQueue.quit(MessageQueue.java:428)
- at android.os.Looper.quit(Looper.java:354)
- at com.jackie.testdialog.Test2Activity.onCreate(Test2Activity.java:29)
- at android.app.Activity.performCreate(Activity.java:7802)
- at android.app.Activity.performCreate(Activity.java:7791)
- at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299)
- at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245)
- at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409)
- at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
- at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
- at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
- at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
- at android.os.Handler.dispatchMessage(Handler.java:107)
- at android.os.Looper.loop(Looper.java:214)
- at android.app.ActivityThread.main(ActivityThread.java:7356)
- at java.lang.reflect.Method.invoke(Native Method)
- at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
- at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
為什么不允許退出呢,因為主線程不允許退出,一旦退出就意味著程序掛了,退出也不應該用這種方式退出。
Handler的消息處理順序
在Looper執(zhí)行消息循環(huán)loop()時會執(zhí)行下面這行代碼,msg.targe就是這個Handler對象
- msg.target.dispatchMessage(msg);
我們來看看dispatchMessage的源碼
- public void dispatchMessage(@NonNull Message msg) {
- if (msg.callback != null) {
- handleCallback(msg);
- } else {
- //如果 callback 處理了該 msg 并且返回 true, 就不會再回調 handleMessage
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- handleMessage(msg);
- }
- }
如果Message這個對象有CallBack回調的話,這個CallBack實際上是個Runnable,就只執(zhí)行這個回調,然后就結束了,創(chuàng)建該Message的CallBack代碼如下:
- Message msgCallBack = Message.obtain(handler, new Runnable() {
- @Override
- public void run
網(wǎng)站標題:Handler的初級、中級、高級問法,你都掌握了嗎?
當前鏈接:http://uogjgqi.cn/article/djchijs.html

我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網(wǎng)交流