您好,登錄后才能下訂單哦!
這篇文章主要講解了“如何掌握Handler的初級(jí)、中級(jí)、高級(jí)問法”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“如何掌握Handler的初級(jí)、中級(jí)、高級(jí)問法”吧!
Handler的源碼和常見問題的解答
下面來看一下官方對(duì)其的定義:
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到線程的消息隊(duì)列(MessageQueue)中,每個(gè)Handler實(shí)例和一個(gè)線程以及那個(gè)線程的消息隊(duì)列相關(guān)聯(lián)。當(dāng)你創(chuàng)建一個(gè)Handler時(shí)應(yīng)該和一個(gè)Looper進(jìn)行綁定(主線程默認(rèn)已經(jīng)創(chuàng)建Looper了,子線程需要自己創(chuàng)建Looper),它向Looper的對(duì)應(yīng)的消息隊(duì)列傳送Message/Runnable同時(shí)在那個(gè)Looper所在線程處理對(duì)應(yīng)的Message/Runnable。下面這張圖就是Handler的工作流程
Handler工作流程圖
可以看到在Thread中,Looper的這個(gè)傳送帶其實(shí)就一個(gè)死循環(huán),它不斷的從消息隊(duì)列MessageQueue中不斷的取消息,最后交給Handler.dispatchMessage進(jìn)行消息的分發(fā),而Handler.sendXXX,Handler.postXXX這些方法把消息發(fā)送到消息隊(duì)列中MessageQueue,整個(gè)模式其實(shí)就是一個(gè)生產(chǎn)者-消費(fèi)者模式,源源不斷的生產(chǎn)消息,處理消息,沒有消息時(shí)進(jìn)行休眠。MessageQueue是一個(gè)由單鏈表構(gòu)成的優(yōu)先級(jí)隊(duì)列(取的都是頭部,所以說是隊(duì)列)。
前面說過,當(dāng)你創(chuàng)建一個(gè)Handler時(shí)應(yīng)該和一個(gè)Looper進(jìn)行綁定(綁定也可以理解為創(chuàng)建,主線程默認(rèn)已經(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方法中,我們先調(diào)用了Looper.prepareMainLooper()方法,然后獲取當(dāng)前線程的Handler,最后調(diào)用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)建了當(dāng)前線程的Looper,同時(shí)將Looper實(shí)例存放到線程局部變量sThreadLocal(ThreadLocal)中,也就是每個(gè)線程有自己的Looper。在創(chuàng)建Looper的時(shí)候也創(chuàng)建了該線程的消息隊(duì)列,可以看到prepareMainLooper會(huì)判斷sMainLooper是否有值,如果調(diào)用多次,就會(huì)拋出異常,所以也就是說主線程的Looper和MessageQueue只會(huì)有一個(gè)。同理子線程中調(diào)用Looper.prepare()時(shí),會(huì)調(diào)用prepare(true)方法,如果多次調(diào)用,也會(huì)拋出每個(gè)線程只能由一個(gè)Looper的異常,總結(jié)起來就是每個(gè)線程中只有一個(gè)Looper和MessageQueue。
//Looper.java private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
再來看看主線程sMainThreadHandler = thread.getHandler(),getHandler獲取到的實(shí)際上就是mH這個(gè)Handler。
//ActivityThread.java final H mH = new H(); @UnsupportedAppUsage final Handler getHandler() { return mH; }
mH這個(gè)Handler是ActivityThread的內(nèi)部類,通過查看handMessage方法,可以看到這個(gè)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); ··· //回收復(fù)用 msg.recycleUnchecked(); } }
在loop方法中是一個(gè)死循環(huán),在這里從消息隊(duì)列中不斷的獲取消息queue.next(),然后通過Handler(msg.target)進(jìn)行消息的分發(fā),其實(shí)并沒有什么具體的綁定,因?yàn)镠andler在每個(gè)線程中對(duì)應(yīng)只有一個(gè)Looper和消息隊(duì)列MessageQueue,自然要靠它來處理,也就是是調(diào)用Looper.loop()方法。在Looper.loop()的死循環(huán)中不斷的取消息,最后回收復(fù)用。
這里要強(qiáng)調(diào)一下Message中的參數(shù)target(Handler),正是這個(gè)變量,每個(gè)Message才能找到對(duì)應(yīng)的Handler進(jìn)行消息分發(fā),讓多個(gè)Handler同時(shí)工作。
再來看看子線程中是如何處理的,首先在子線程中創(chuàng)建一個(gè)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(); }
運(yùn)行后可以看到錯(cuò)誤日志,可以看到提示我們需要在子線程中調(diào)用Looper.prepare()方法,實(shí)際上就是要?jiǎng)?chuàng)建一個(gè)Looper和你的Handler進(jìn)行“關(guān)聯(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.<init>(Handler.java:207) at android.os.Handler.<init>(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,同時(shí)調(diào)用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(); }
這里需要注意在所有事情處理完成后應(yīng)該調(diào)用quit方法來終止消息循環(huán),否則這個(gè)子線程就會(huì)一直處于循環(huán)等待的狀態(tài),因此不需要的時(shí)候終止Looper,調(diào)用Looper.myLooper().quit()。
看完上面的代碼可能你會(huì)有一個(gè)疑問,在子線程中更新UI(進(jìn)行Toast)不會(huì)有問題嗎,我們Android不是不允許在子線程更新UI嗎,實(shí)際上并不是這樣的,在ViewRootImpl中的checkThread方法會(huì)校驗(yàn)mThread != Thread.currentThread(),mThread的初始化是在ViewRootImpl的的構(gòu)造器中,也就是說一個(gè)創(chuàng)建ViewRootImpl線程必須和調(diào)用checkThread所在的線程一致,UI的更新并非只能在主線程才能進(jìn)行。
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
這里需要引入一些概念,Window是Android中的窗口,每個(gè)Activity和Dialog,Toast分別對(duì)應(yīng)一個(gè)具體的Window,Window是一個(gè)抽象的概念,每一個(gè)Window都對(duì)應(yīng)著一個(gè)View和一個(gè)ViewRootImpl,Window和View通過ViewRootImpl來建立聯(lián)系,因此,它是以View的形式存在的。我們來看一下Toast中的ViewRootImpl的創(chuàng)建過程,調(diào)用toast的show方法最終會(huì)調(diào)用到其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); //進(jìn)行ViewRootImpl的創(chuàng)建 trySendAccessibilityEvent(); } catch (WindowManager.BadTokenException e) { /* ignore */ } } }
這個(gè)mWM(WindowManager)的最終實(shí)現(xiàn)者是WindowManagerGlobal,其的addView方法中會(huì)創(chuàng)建ViewRootImpl,然后進(jìn)行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內(nèi)部會(huì)通過requestLayout來完成異步刷新請(qǐng)求,同時(shí)也會(huì)調(diào)用checkThread方法來檢驗(yàn)線程的合法性。
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
因此,我們的ViewRootImpl的創(chuàng)建是在子線程,所以mThread的值也是子線程,同時(shí)我們的更新也是在子線程,所以不會(huì)產(chǎn)生異常,同時(shí)也可以參考這篇文章分析,寫的非常詳細(xì)。同理下面的代碼也可以驗(yàn)證這個(gè)情況
//子線程中調(diào)用 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(); }
在子線程中調(diào)用showDialog方法,先調(diào)用alertDialog.show()方法,再調(diào)用alertDialog.hide()方法,hide方法只是將Dialog隱藏,并沒有做其他任何操作(沒有移除Window),然后再在主線程調(diào)用alertDialog.show();便會(huì)拋出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的重點(diǎn)是創(chuàng)建它的ViewRootImpl和checkThread所在的線程是否一致。
如何在主線程中訪問網(wǎng)絡(luò)
在網(wǎng)絡(luò)請(qǐng)求之前添加如下代碼
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build(); StrictMode.setThreadPolicy(policy);
StrictMode(嚴(yán)苛模式)Android2.3引入,用于檢測(cè)兩大問題:ThreadPolicy(線程策略)和VmPolicy(VM策略),這里把嚴(yán)苛模式的網(wǎng)絡(luò)檢測(cè)關(guān)了,就可以在主線程中執(zhí)行網(wǎng)絡(luò)操作了,一般是不建議這么做的。關(guān)于嚴(yán)苛模式可以查看這里。
系統(tǒng)為什么不建議在子線程中訪問UI?
這是因?yàn)?Android 的UI控件不是線程安全的,如果在多線程中并發(fā)訪問可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài),那么為什么系統(tǒng)不對(duì)UI控件的訪問加上鎖機(jī)制呢?缺點(diǎn)有兩個(gè)
首先加上鎖機(jī)制會(huì)讓UI訪問的邏輯變得復(fù)雜
鎖機(jī)制會(huì)降低UI訪問的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行。
所以最簡單且高效的方法就是采用單線程模型來處理UI操作。(安卓開發(fā)藝術(shù)探索)
子線程如何通知主線程更新UI(都是通過Handle發(fā)送消息到主線程操作UI的)
主線程中定義 Handler,子線程通過 mHandler 發(fā)送消息,主線程 Handler 的 handleMessage 更新UI。
用 Activity 對(duì)象的 runOnUiThread 方法。
創(chuàng)建 Handler,傳入 getMainLooper。
View.post(Runnable r) 。
Looper死循環(huán)為什么不會(huì)導(dǎo)致應(yīng)用卡死,會(huì)耗費(fèi)大量資源嗎?
從前面的主線程、子線程的分析可以看出,Looper會(huì)在線程中不斷的檢索消息,如果是子線程的Looper死循環(huán),一旦任務(wù)完成,用戶應(yīng)該手動(dòng)退出,而不是讓其一直休眠等待。(引用自Gityuan)線程其實(shí)就是一段可執(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ì)休眠。Android是基于消息處理機(jī)制的,用戶的行為都在這個(gè)Looper循環(huán)中,我們?cè)谛菝邥r(shí)點(diǎn)擊屏幕,便喚醒主線程繼續(xù)進(jìn)行工作。
主線程的死循環(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資源。
主線程的Looper何時(shí)退出
在App退出時(shí),ActivityThread中的mH(Handler)收到消息后,執(zhí)行退出。
//ActivityThread.java case EXIT_APPLICATION: if (mInitialApplication != null) { mInitialApplication.onTerminate(); } Looper.myLooper().quit(); break;
如果你嘗試手動(dòng)退出主線程Looper,便會(huì)拋出如下異常
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)
為什么不允許退出呢,因?yàn)橹骶€程不允許退出,一旦退出就意味著程序掛了,退出也不應(yīng)該用這種方式退出。
Handler的消息處理順序
在Looper執(zhí)行消息循環(huán)loop()時(shí)會(huì)執(zhí)行下面這行代碼,msg.targe就是這個(gè)Handler對(duì)象
msg.target.dispatchMessage(msg);
我們來看看dispatchMessage的源碼
public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { //如果 callback 處理了該 msg 并且返回 true, 就不會(huì)再回調(diào) handleMessage if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
如果Message這個(gè)對(duì)象有CallBack回調(diào)的話,這個(gè)CallBack實(shí)際上是個(gè)Runnable,就只執(zhí)行這個(gè)回調(diào),然后就結(jié)束了,創(chuàng)建該Message的CallBack代碼如下:
Message msgCallBack = Message.obtain(handler, new Runnable() { @Override public void run() { } });
而handleCallback方法中調(diào)用的是Runnable的run方法。
private static void handleCallback(Message message) { message.callback.run(); }
如果Message對(duì)象沒有CallBack回調(diào),進(jìn)入else分支判斷Handler的CallBack是否為空,不為空?qǐng)?zhí)行CallBack的handleMessage方法,然后return,構(gòu)建Handler的CallBack代碼如下:
Handler.Callback callback = new Handler.Callback() { @Override public boolean handleMessage(@NonNull Message msg) { //retrun true,就不執(zhí)行下面的邏輯了,可以用于做優(yōu)先級(jí)的處理 return false; } };
1. 最后才調(diào)用到Handler的handleMessage()函數(shù),也就是我們經(jīng)常去重寫的函數(shù),在該方法中做消息的處理。
使用場景
可以看到Handler.Callback 有優(yōu)先處理消息的權(quán)利 ,當(dāng)一條消息被 Callback 處理并攔截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不會(huì)被調(diào)用了;如果 Callback 處理了消息,但是并沒有攔截,那么就意味著一個(gè)消息可以同時(shí)被 Callback 以及 Handler 處理。我們可以利用CallBack這個(gè)攔截來攔截Handler的消息。
場景:Hook ActivityThread.mH , 在 ActivityThread 中有個(gè)成員變量 mH ,它是個(gè) Handler,又是個(gè)極其重要的類,幾乎所有的插件化框架都使用了這個(gè)方法。
Handler.post(Runnable r)方法的執(zhí)行邏輯
我們需要分析平時(shí)常用的Handler.post(Runnable r)方法是如何執(zhí)行的,是否新創(chuàng)建了一個(gè)線程了呢,實(shí)際上并沒有,這個(gè)Runnable對(duì)象只是被調(diào)用了它的run方法,根本并沒有啟動(dòng)一個(gè)線程,源碼如下:
//Handler.java public final boolean post(@NonNull Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
最終該Runnable對(duì)象被包裝成一個(gè)Message對(duì)象,也就是這個(gè)Runnable對(duì)象就是該Message的CallBack對(duì)象了,有優(yōu)先執(zhí)行的權(quán)利了。
Handler是如何進(jìn)行線程切換的
原理很簡單,線程間是共享資源的,子線程通過handler.sendXXX,handler.postXXX等方法發(fā)送消息,然后通過Looper.loop()在消息隊(duì)列中不斷的循環(huán)檢索消息,最后交給handle.dispatchMessage方法進(jìn)行消息的分發(fā)處理。
如何處理Handler使用不當(dāng)造成的內(nèi)存泄漏?
有延時(shí)消息,在界面關(guān)閉后及時(shí)移除Message/Runnable,調(diào)用handler.removeCallbacksAndMessages(null)
內(nèi)部類導(dǎo)致的內(nèi)存泄漏改為靜態(tài)內(nèi)部類,并對(duì)上下文或者Activity/Fragment使用弱引用。
具體內(nèi)存泄漏的分析和解決可以參考這篇文章。同時(shí)還有一個(gè)很關(guān)鍵的點(diǎn),如果有個(gè)延時(shí)消息,當(dāng)界面關(guān)閉時(shí),該Handler中的消息還沒有處理完畢,那么最終這個(gè)消息是怎么處理的?經(jīng)過測(cè)試,比如我打開界面后延遲10s發(fā)送消息,關(guān)閉界面,最終在Handler(匿名內(nèi)部類創(chuàng)建的)的handMessage方法中還是會(huì)收到消息(打印日志)。因?yàn)闀?huì)有一條MessageQueue -> Message -> Handler -> Activity的引用鏈,所以Handler不會(huì)被銷毀,Activity也不會(huì)被銷毀。
正確創(chuàng)建Message實(shí)例
通過 Message 的靜態(tài)方法 Message.obtain() 獲??;
通過 Handler 的公有方法 handler.obtainMessage()
所有的消息會(huì)被回收,放入sPool中,使用享元設(shè)計(jì)模式。
Handler深層次問題解答
ThreadLocal
ThreadLocal為每個(gè)線程都提供了變量的副本,使得每個(gè)線程在某一時(shí)間訪問到的并非同一個(gè)對(duì)象,這樣就隔離了多個(gè)線程對(duì)數(shù)據(jù)的數(shù)據(jù)共享。
如果讓你設(shè)計(jì)一個(gè)ThreadLocal,ThreadLocal 的目標(biāo)是讓不同的線程有不同的變量 V,那最直接的方法就是創(chuàng)建一個(gè) Map,它的 Key 是線程,Value 是每個(gè)線程擁有的變量 V,ThreadLocal 內(nèi)部持有這樣的一個(gè) Map 就可以了。你可能會(huì)設(shè)計(jì)成這樣
實(shí)際上Java的實(shí)現(xiàn)是下面這樣,Java 的實(shí)現(xiàn)里面也有一個(gè) Map,叫做 ThreadLocalMap,不過持有 ThreadLocalMap 的不是 ThreadLocal,而是 Thread。Thread 這個(gè)類內(nèi)部有一個(gè)私有屬性 threadLocals,其類型就是 ThreadLocalMap,ThreadLocalMap 的 Key 是 ThreadLocal。
精簡之后的代碼如下
class Thread { //內(nèi)部持有ThreadLocalMap ThreadLocal.ThreadLocalMap threadLocals; } class ThreadLocal<T>{ public T get() { //首先獲取線程持有的 //ThreadLocalMap ThreadLocalMap map = Thread.currentThread() .threadLocals; //在ThreadLocalMap中 //查找變量 Entry e = map.getEntry(this); return e.value; } static class ThreadLocalMap{ //內(nèi)部是數(shù)組而不是Map Entry[] table; //根據(jù)ThreadLocal查找Entry Entry getEntry(ThreadLocal key){ //省略查找邏輯 } //Entry定義 static class Entry extends WeakReference<ThreadLocal>{ Object value; } } }
在Java的實(shí)現(xiàn)方案中,ThreadLocal僅僅只是一個(gè)代理工具類,內(nèi)部并不持有任何線程相關(guān)的數(shù)據(jù),所有和線程相關(guān)的數(shù)據(jù)都存儲(chǔ)在Thread里面,這樣的設(shè)計(jì)從數(shù)據(jù)的親緣性上來講,ThreadLocalMap屬于Thread也更加合理。所以ThreadLocal的get方法,其實(shí)就是拿到每個(gè)線程獨(dú)有的ThreadLocalMap。
還有一個(gè)原因,就是不容易產(chǎn)生內(nèi)存泄漏,如果用我們的設(shè)計(jì)方案,ThreadLocal持有的Map會(huì)持有Thread對(duì)象的引用,這就意味著只要ThreadLocal對(duì)象存在,那么Map中的Thread對(duì)象就永遠(yuǎn)不會(huì)被回收。ThreadLocal的生命周期往往都比線程要長,所以這種設(shè)計(jì)方案很容易導(dǎo)致內(nèi)存泄漏。
而Java的實(shí)現(xiàn)中Thread持有ThreadLocalMap,而且ThreadLocalMap里對(duì)ThreadLocal的引用還是弱引用,所以只要Thread對(duì)象可以被回收,那么ThreadLocalMap就能被回收。Java的實(shí)現(xiàn)方案雖然看上去復(fù)雜一些,但是更安全。
ThreadLocal與內(nèi)存泄漏
但是一切并不總是那么完美,如果在線程池中使用ThreadLocal可能會(huì)導(dǎo)致內(nèi)存泄漏,原因是線程池中線程的存活時(shí)間太長,往往和程序都是同生共死的,這就意味著Thread持有的ThreadLocalMap一直都不會(huì)被回收,再加上ThreadLocalMap中的Entry對(duì)ThreadLocal是弱引用,所以只要ThreadLocal結(jié)束了自己的生命周期是可以被回收掉的。但是Entry中的Value卻是被Entry強(qiáng)引用的,所以即便Value的生命周期結(jié)束了,Value也是無法被回收的,從而導(dǎo)致內(nèi)存泄漏。
所以我們可以通過try{}finally{}方案來手動(dòng)釋放資源
ExecutorService es; ThreadLocal tl; es.execute(()->{ //ThreadLocal增加變量 tl.set(obj); try { // 省略業(yè)務(wù)邏輯代碼 }finally { //手動(dòng)清理ThreadLocal tl.remove(); } });
以上ThreadLocal內(nèi)容主要參考自這里。
epoll機(jī)制
epoll機(jī)制在Handler中的應(yīng)用,在主線程的 MessageQueue 沒有消息時(shí),便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,最終調(diào)用到epoll_wait()進(jìn)行阻塞等待。此時(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資源。
這里有一篇深度好文聊聊IO多路復(fù)用之select,poll,epoll詳解,這邊拿文章中的最后兩段話:
表面上看epoll的性能最好,但是在連接數(shù)少并且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機(jī)制需要很多函數(shù)回調(diào)。
select低效是因?yàn)槊看嗡夹枰喸?。但低效也是相?duì)的,視情況而定,也可通過良好的設(shè)計(jì)改善。
之所以選擇Handler底層選擇epoll機(jī)制,我感覺是epoll在效率上更高。在select/poll中,進(jìn)程只有在調(diào)用一定的方法后,內(nèi)核才對(duì)所有監(jiān)視的文件描述符進(jìn)行掃描,而epoll事先通過epoll_ctl()來注冊(cè)一個(gè)文件描述符,一旦基于某個(gè)文件描述符就緒時(shí),內(nèi)核會(huì)采用類似callback的回調(diào)機(jī)制,迅速激活這個(gè)文件描述符,當(dāng)進(jìn)程調(diào)用epoll_wait()時(shí)便得到通知。(此處去掉了遍歷文件描述符,而是通過監(jiān)聽回調(diào)的的機(jī)制。這正是epoll的魅力所在。)
Handler的同步屏障機(jī)制
如果有一個(gè)緊急的Message需要優(yōu)先處理,該怎么做?這其實(shí)涉及到架構(gòu)方面的設(shè)計(jì)了,通用場景和特殊場景的設(shè)計(jì)。你可能會(huì)想到sendMessageAtFrontOfQueue()這個(gè)方法,實(shí)際也遠(yuǎn)遠(yuǎn)不只是如此,Handler中加入了同步屏障這種機(jī)制,來實(shí)現(xiàn)[異步消息優(yōu)先]執(zhí)行的功能。
postSyncBarrier()發(fā)送同步屏障,removeSyncBarrier()移除同步屏障
同步屏障的作用可以理解成攔截同步消息的執(zhí)行,主線程的 Looper 會(huì)一直循環(huán)調(diào)用 MessageQueue 的 next() 來取出隊(duì)頭的 Message 執(zhí)行,當(dāng) Message 執(zhí)行完后再去取下一個(gè)。當(dāng) next() 方法在取 Message 時(shí)發(fā)現(xiàn)隊(duì)頭是一個(gè)同步屏障的消息時(shí),就會(huì)去遍歷整個(gè)隊(duì)列,只尋找設(shè)置了異步標(biāo)志的消息,如果有找到異步消息,那么就取出這個(gè)異步消息來執(zhí)行,否則就讓 next() 方法陷入阻塞狀態(tài)。如果 next() 方法陷入阻塞狀態(tài),那么主線程此時(shí)就是處于空閑狀態(tài)的,也就是沒在干任何事。所以,如果隊(duì)頭是一個(gè)同步屏障的消息的話,那么在它后面的所有同步消息就都被攔截住了,直到這個(gè)同步屏障消息被移除出隊(duì)列,否則主線程就一直不會(huì)去處理同步屏幕后面的同步消息。
而所有消息默認(rèn)都是同步消息,只有手動(dòng)設(shè)置了異步標(biāo)志,這個(gè)消息才會(huì)是異步消息。另外,同步屏障消息只能由內(nèi)部來發(fā)送,這個(gè)接口并沒有公開給我們使用。
Choreographer 里所有跟 message 有關(guān)的代碼,你會(huì)發(fā)現(xiàn),都手動(dòng)設(shè)置了異步消息的標(biāo)志,所以這些操作是不受到同步屏障影響的。這樣做的原因可能就是為了盡可能保證上層 app 在接收到屏幕刷新信號(hào)時(shí),可以在第一時(shí)間執(zhí)行遍歷繪制 View 樹的工作。
Choreographer 過程中的動(dòng)作也都是異步消息,這樣可以確保 Choreographer 的順利運(yùn)轉(zhuǎn),也確保了第一時(shí)間執(zhí)行 doTraversal(doTraversal → performTraversals 就是執(zhí)行 view 的 layout、measure、draw),這個(gè)過程中如果有其他同步消息,也無法得到處理,都要等到 doTraversal 之后。
因?yàn)橹骶€程中如果有太多消息要執(zhí)行,而這些消息又是根據(jù)時(shí)間戳進(jìn)行排序,如果不加一個(gè)同步屏障的話,那么遍歷繪制 View 樹的工作就可能被迫延遲執(zhí)行,因?yàn)樗残枰抨?duì),那么就有可能出現(xiàn)當(dāng)一幀都快結(jié)束的時(shí)候才開始計(jì)算屏幕數(shù)據(jù),那即使這次的計(jì)算少于 16.6ms,也同樣會(huì)造成丟幀現(xiàn)象。
那么,有了同步屏障消息的控制就能保證每次一接收到屏幕刷新信號(hào)就第一時(shí)間處理遍歷繪制 View 樹的工作么?
只能說,同步屏障是盡可能去做到,但并不能保證一定可以第一時(shí)間處理。因?yàn)?,同步屏障是?scheduleTraversals() 被調(diào)用時(shí)才發(fā)送到消息隊(duì)列里的,也就是說,只有當(dāng)某個(gè) View 發(fā)起了刷新請(qǐng)求時(shí),在這個(gè)時(shí)刻后面的同步消息才會(huì)被攔截掉。如果在 scheduleTraversals() 之前就發(fā)送到消息隊(duì)列里的工作仍然會(huì)按順序依次被取出來執(zhí)行。
下面是部分詳細(xì)的分析:
WindowManager維護(hù)著所有的Activity的DecorView和ViewRootImpl。在前面我們講過,WindowManagerGlobal的addView方法中中初始化了ViewRootImpl,然后調(diào)用它的setView方法,將DecorView作為參數(shù)傳遞了進(jìn)去。所以我們看看ViewRootImpl做了什么
//ViewRootImpl.java //view是DecorView public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; ··· // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); //發(fā)起布局請(qǐng)求 ··· view.assignParent(this); //將當(dāng)前ViewRootImpl對(duì)象this作為參數(shù)調(diào)用了DecorView的 assignParent ··· } } }
在setView()方法里調(diào)用了DecorView的assignParent
//View.java /* * Caller is responsible for calling requestLayout if necessary. * (This allows addViewInLayout to not request a new layout.) */ @UnsupportedAppUsage void assignParent(ViewParent parent) { if (mParent == null) { mParent = parent; } else if (parent == null) { mParent = null; } else { throw new RuntimeException("view " + this + " being added, but" + " it already has a parent"); } }
參數(shù)是ViewParent,而ViewRootImpl是實(shí)現(xiàn)了ViewParent接口的,所以在這里就將DecorView和ViewRootImpl綁定起來了。每個(gè)Activity的根布局都是DecorView,而DecorView的parent又是ViewRootImpl,所以在子View里執(zhí)行invalidate()之類的工作,循環(huán)找parent,最后都會(huì)找到ViewRootImpl里來。所以實(shí)際上View的刷新都是由ViewRootImpl來控制的。
即使是界面上一個(gè)小小的 View 發(fā)起了重繪請(qǐng)求時(shí),都要層層走到 ViewRootImpl,由它來發(fā)起重繪請(qǐng)求,然后再由它來開始遍歷 View 樹,一直遍歷到這個(gè)需要重繪的 View 再調(diào)用它的 onDraw() 方法進(jìn)行繪制。
View.invalidate()請(qǐng)求重繪的操作最后調(diào)用到的是ViewRootImpl.scheduleTraversals(),而ViewRootImpl.setView()方法中調(diào)用了requestLayout方法
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
最終也調(diào)用到了scheduleTraversals()方法,其實(shí)這個(gè)方法是屏幕刷新的關(guān)鍵。
其實(shí)打開一個(gè) Activity,當(dāng)它的 onCreate---onResume 生命周期都走完后,才將它的 DecoView 與新建的一個(gè) ViewRootImpl 對(duì)象綁定起來,同時(shí)開始安排一次遍歷 View 任務(wù)也就是繪制 View 樹的操作等待執(zhí)行,然后將 DecoView 的 parent 設(shè)置成 ViewRootImpl 對(duì)象。所以我們?cè)趏nCreate~onResume中獲取不到View寬高,界面的繪制也是在onResume之后才開始執(zhí)行的。可以參考我前面的文章分析。
ViewRootImpl.scheduleTraversals()的一系列分析以及屏幕刷新機(jī)制可以參考這篇文章,這里的內(nèi)容也是大部分參考它的,同步屏障相關(guān)的分析內(nèi)容也在里面。
Choreographer主要作用是協(xié)調(diào)動(dòng)畫,輸入和繪制的時(shí)間,它從顯示子系統(tǒng)接收定時(shí)脈沖(例如垂直同步),然后安排渲染下一個(gè)frame的一部分工作。
可通過Choreographer.getInstance().postFrameCallback()來監(jiān)聽幀率情況;
public class FPSFrameCallback implements Choreographer.FrameCallback { private static final String TAG = "FPS_TEST"; private long mLastFrameTimeNanos; private long mFrameIntervalNanos; public FPSFrameCallback(long lastFrameTimeNanos) { mLastFrameTimeNanos = lastFrameTimeNanos; //每一幀渲染時(shí)間 多少納秒 mFrameIntervalNanos = (long) (1000000000 / 60.0); } @Override public void doFrame(long frameTimeNanos) { //Vsync信號(hào)到來的時(shí)間frameTimeNanos //初始化時(shí)間 if (mLastFrameTimeNanos == 0) { //上一幀的渲染時(shí)間 mLastFrameTimeNanos = frameTimeNanos; } final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos; if (jitterNanos >= mFrameIntervalNanos) { final long skippedFrames = jitterNanos / mFrameIntervalNanos; if (skippedFrames > 5) { Log.d(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); } } mLastFrameTimeNanos = frameTimeNanos; //注冊(cè)下一幀回調(diào) Choreographer.getInstance().postFrameCallback(this); } }
調(diào)用方式在Application中注冊(cè)
Choreographer.getInstance().postFrameCallback(FPSFrameCallback(System.nanoTime()))
丟幀的原因:造成丟幀大體上有兩類原因,一是遍歷繪制 View 樹計(jì)算屏幕數(shù)據(jù)的時(shí)間超過了 16.6ms;二是,主線程一直在處理其他耗時(shí)的消息,導(dǎo)致遍歷繪制 View 樹的工作遲遲不能開始,從而超過了 16.6 ms 底層切換下一幀畫面的時(shí)機(jī)。
Handler鎖相關(guān)問題
既然可以存在多個(gè)Handler往MessageQueue中添加數(shù)據(jù)(發(fā)送消息時(shí)各個(gè)Handler可能處于不同線程),那它內(nèi)部是如何確保線程安全的?
Handler.sendXXX,Handler.postXXX最終會(huì)會(huì)調(diào)到MessageQueue的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) { ··· } }
其內(nèi)部通過synchronized關(guān)鍵字保證線程安全。同時(shí)messagequeue.next()內(nèi)部也會(huì)通過synchronized加鎖,確保取的時(shí)候線程安全,同時(shí)插入也會(huì)加鎖。這個(gè)問題其實(shí)不難,只是看你有沒有了解源碼。
Handler中的同步方法
如何讓handler.post消息執(zhí)行之后然后再繼續(xù)往下執(zhí)行,同步方法runWithScissors
public final boolean runWithScissors(@NonNull Runnable r, long timeout) { if (r == null) { throw new IllegalArgumentException("runnable must not be null"); } if (timeout < 0) { throw new IllegalArgumentException("timeout must be non-negative"); } if (Looper.myLooper() == mLooper) { r.run(); return true; } BlockingRunnable br = new BlockingRunnable(r); return br.postAndWait(this, timeout); }
Handler在系統(tǒng)以及第三方框架的一些應(yīng)用
HandlerThread
HandlerThread繼承于Thread,顧名思義,實(shí)際上是Handler和Thread的一個(gè)封裝,已經(jīng)為我們封裝的很好很安全了,內(nèi)部也通過synchronized來保證線程安全,比如getLooper方法
public Looper getLooper() { if (!isAlive()) { return null; } // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; }
在線程的run方法里,所以當(dāng)線程啟動(dòng)之后才能創(chuàng)建Looper并賦值給mLooper,這里的阻塞就是為了等待Looper的創(chuàng)建成功。同時(shí)該方法是用Public修飾的,說明該方法是提供外部調(diào)用的,Looper創(chuàng)建成功提供給外部使用。
IntentService
簡單看一下源碼就能看到Handler的應(yīng)用,Handler的handMessage最終會(huì)回調(diào)到onHandleIntent方法。
public abstract class IntentService extends Service { private volatile Looper mServiceLooper; @UnsupportedAppUsage private volatile ServiceHandler mServiceHandler;
如何打造一個(gè)不崩潰的程序
打造一個(gè)不崩潰的程序,可以參考我的這篇文章
Glide中的應(yīng)用
Glide 相信大應(yīng)該非常熟悉了,我們都知道Glide生命周期的控制(如果不了解,可以看下Glide相關(guān)文章的分析,跟LiveData 是同一個(gè)原理)是通過添加一個(gè)空的Fragment到Activity 或者Fragment中,然后通過FragmentMannager管理Fragment的生命周期,從而達(dá)到生命周期的控制。下面是節(jié)選了Glide一段添加Fragment的代碼:
private RequestManagerFragment getRequestManagerFragment( @NonNull final android.app.FragmentManager fm, @Nullable android.app.Fragment parentHint, boolean isParentVisible) { //1.通過FragmentManager獲取 RequestManagerFragment,如果已添加到FragmentManager則返回實(shí)例,否則為空 RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); if (current == null) { //2.如果fm里面沒有則從map緩存里取 current = pendingRequestManagerFragments.get(fm); if (current == null) { //3.第1和2步都沒有,說明確實(shí)沒創(chuàng)建,則走創(chuàng)建的流程 current = new RequestManagerFragment(); current.setParentFragmentHint(parentHint); if (isParentVisible) { current.getGlideLifecycle().onStart(); } //4.將新創(chuàng)建的fragment 保存到map容器 pendingRequestManagerFragments.put(fm, current); //5.發(fā)送添加fragment事務(wù)事件 fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); //6.發(fā)送remove 本地緩存事件 handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); } } return current; } //跟上面那個(gè)方法唯一的區(qū)別是 這個(gè)是Fragment 的FragmentManager,上面的是Acitivity 的FragmentManager private SupportRequestManagerFragment getSupportRequestManagerFragment( @NonNull final FragmentManager fm, @Nullable Fragment parentHint, boolean isParentVisible) { SupportRequestManagerFragment current = (SupportRequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG); if (current == null) { current = pendingSupportRequestManagerFragments.get(fm); if (current == null) { current = new SupportRequestManagerFragment(); current.setParentFragmentHint(parentHint); if (isParentVisible) { current.getGlideLifecycle().onStart(); } pendingSupportRequestManagerFragments.put(fm, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); handler.obtainMessage(ID_REMOVE_SUPPORT_FRAGMENT_MANAGER, fm).sendToTarget(); } } return current; } @Override public boolean handleMessage(Message message) { boolean handled = true; Object removed = null; Object key = null; switch (message.what) { case ID_REMOVE_FRAGMENT_MANAGER: //7.移除緩存 android.app.FragmentManager fm = (android.app.FragmentManager) message.obj; key = fm; removed = pendingRequestManagerFragments.remove(fm); break; //省略代碼... } //省略代碼... return handled; }
看了上面的代碼,大家可能會(huì)有疑惑。
Fragment添加到FragmentManager為什么還要保存到map容器里(第4步)?
判斷Fragment是否已添加為啥還要從map容器判斷(第2步),F(xiàn)ragmentManager 去find 不就可以做到了嗎?
其實(shí)答案很簡單,學(xué)了Handler原理之后我們知道:就是在第5步執(zhí)行完之后并沒有將Fragment添加到FragmentManager(事件排隊(duì)中),而是發(fā)送添加Fragment的事件。接下來我們看代碼
//FragmentManagerImpl.java void scheduleCommit() { synchronized (this) { boolean postponeReady = mPostponedTransactions != null && !mPostponedTransactions.isEmpty(); boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1; if (postponeReady || pendingReady) { mHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); updateOnBackPressedCallbackEnabled(); } } }
添加Fragment 最終會(huì)走到FragmentManagerImpl的 scheduleCommit方法,我們可以看到他是通過Handler 發(fā)送事件。
這也就解釋了為什么第5步執(zhí)行完之后Fragment為什么沒有立即添加到FragmentManager,所以需要Map緩存Fragment來標(biāo)記是否有Fragment添加。再接著有了第6步發(fā)送移除Map緩存的消息,因?yàn)镠andler處理消息是有序的。
感謝各位的閱讀,以上就是“如何掌握Handler的初級(jí)、中級(jí)、高級(jí)問法”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)如何掌握Handler的初級(jí)、中級(jí)、高級(jí)問法這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。