日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

Handler的内功心法,值得拥有!

發(fā)布時(shí)間:2024/1/18 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Handler的内功心法,值得拥有! 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.


/? ?今日科技快訊? ?/

12月16日,華為今日宣布正式推出鴻蒙OS的手機(jī)開(kāi)發(fā)者Beta版。相關(guān)測(cè)試視頻顯示,鴻蒙OS在系統(tǒng)UI方面與現(xiàn)有的EMUI11基本保持一致。此前,華為曾宣布,鴻蒙系統(tǒng)在2021年4月將面向內(nèi)存128MB-4GB終端設(shè)備開(kāi)源,2021年10月以后將面向4GB以上所有設(shè)備開(kāi)源。

/? ?作者簡(jiǎn)介? ?/

本篇文章來(lái)自傷心的豬大腸的投稿,整理并分享了Android開(kāi)發(fā)中Handler的相關(guān)內(nèi)容,相信會(huì)對(duì)大家有所幫助!同時(shí)也感謝作者貢獻(xiàn)的精彩文章!

傷心的豬大腸的博客地址:

https://juejin.cn/user/4441682709326958

/? ?正文? ?/

Handler是Android中的消息處理機(jī)制,是一種線程間通信的解決方案,同時(shí)你也可以理解為它天然的為我們?cè)谥骶€程創(chuàng)建一個(gè)隊(duì)列,隊(duì)列中的消息順序就是我們?cè)O(shè)置的延遲的時(shí)間,如果你想在Android中實(shí)現(xiàn)一個(gè)隊(duì)列的功能,不妨第一時(shí)間考慮一下它。本文分為三部分:

Handler的源碼和常見(jiàn)問(wèn)題的解答

  • 一個(gè)線程中最多有多少個(gè)Handler,Looper,MessageQueue?

  • Looper死循環(huán)為什么不會(huì)導(dǎo)致應(yīng)用卡死,會(huì)耗費(fèi)大量資源嗎?

  • 子線程的如何更新UI,比如Dialog,Toast等?系統(tǒng)為什么不建議子線程中更新UI?

  • 主線程如何訪問(wèn)網(wǎng)絡(luò)?

  • 如何處理Handler使用不當(dāng)造成的內(nèi)存泄漏?

  • Handler的消息優(yōu)先級(jí),有什么應(yīng)用場(chǎng)景?

  • 主線程的Looper何時(shí)退出?能否手動(dòng)退出?

  • 如何判斷當(dāng)前線程是安卓主線程?

  • 正確創(chuàng)建Message實(shí)例的方式?

  • Handler深層次問(wèn)題解答

  • ThreadLocal

  • epoll機(jī)制

  • Handle同步屏障機(jī)制

  • Handler的鎖相關(guān)問(wèn)題

  • Handler中的同步方法

  • Handler在系統(tǒng)以及第三方框架的一些應(yīng)用

  • HandlerThread

  • IntentService

  • 如何打造一個(gè)不崩潰的APP

  • Glide中的運(yùn)用

  • Handler的源碼和常見(jiàn)問(wèn)題的解答

    下面來(lái)看一下官方對(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)消息,處理消息,沒(méi)有消息時(shí)進(jìn)行休眠。MessageQueue是一個(gè)由單鏈表構(gòu)成的優(yōu)先級(jí)隊(duì)列(取的都是頭部,所以說(shuō)是隊(duì)列)。

    前面說(shuō)過(guò),當(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),因此我們先來(lái)看看主線程中是如何處理的:

    //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(newLogPrinter(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()。先來(lái)看一下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ì)拋出異常,所以也就是說(shuō)主線程的Looper和MessageQueue只會(huì)有一個(gè)。同理子線程中調(diào)用Looper.prepare()時(shí),會(huì)調(diào)用prepare(true)方法,如果多次調(diào)用,也會(huì)拋出每個(gè)線程只能由一個(gè)Looper的異常,總結(jié)起來(lái)就是每個(gè)線程中只有一個(gè)Looper和MessageQueue。

    //Looper.java private?Looper(boolean?quitAllowed)?{mQueue?=?new?MessageQueue(quitAllowed);mThread?=?Thread.currentThread(); }

    再來(lái)看看主線程sMainThreadHandler = thread.getHandler(),getHandler獲取到的實(shí)際上就是mH這個(gè)Handler。

    //ActivityThread.java final?H?mH?=?new?H();? @UnsupportedAppUsagefinal?Handler?getHandler()?{return?mH; }

    mH這個(gè)Handler是ActivityThread的內(nèi)部類(lèi),通過(guò)查看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中的Looperfinal?Looper?me?=?myLooper();···final?MessageQueue?queue?=?me.mQueue;···for?(;;)?{?//死循環(huán)//獲取消息Message?msg?=?queue.next();?//?might?blockif?(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(),然后通過(guò)Handler(msg.target)進(jìn)行消息的分發(fā),其實(shí)并沒(méi)有什么具體的綁定,因?yàn)镠andler在每個(gè)線程中對(duì)應(yīng)只有一個(gè)Looper和消息隊(duì)列MessageQueue,自然要靠它來(lái)處理,也就是是調(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í)工作。

    再來(lái)看看子線程中是如何處理的,首先在子線程中創(chuàng)建一個(gè)Handler并發(fā)送Runnable。

    @Overrideprotected?void?onCreate(@Nullable?Bundle?savedInstanceState)?{super.onCreate(savedInstanceState);setContentView(R.layout.activity_three);new?Thread(new?Runnable()?{@Overridepublic?void?run()?{new?Handler().post(new?Runnable()?{@Overridepublic?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 2020-11-09?15:51:03.938?21122-21181/com.jackie.testdialog?E/AndroidRuntime:?FATAL?EXCEPTION:?Thread-2Process:?com.jackie.testdialog,?PID:?21122java.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()方法開(kāi)始處理消息。

    ?@Overrideprotected?void?onCreate(@Nullable?Bundle?savedInstanceState)?{super.onCreate(savedInstanceState);setContentView(R.layout.activity_three);new?Thread(new?Runnable()?{@Overridepublic?void?run()?{//創(chuàng)建Looper,MessageQueueLooper.prepare();new?Handler().post(new?Runnable()?{@Overridepublic?void?run()?{Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();}});//開(kāi)始處理消息Looper.loop();}}).start();}

    這里需要注意在所有事情處理完成后應(yīng)該調(diào)用quit方法來(lái)終止消息循環(huán),否則這個(gè)子線程就會(huì)一直處于循環(huán)等待的狀態(tài),因此不需要的時(shí)候終止Looper,調(diào)用Looper.myLooper().quit()。

    看完上面的代碼可能你會(huì)有一個(gè)疑問(wèn),在子線程中更新UI(進(jìn)行Toast)不會(huì)有問(wèn)題嗎,我們Android不是不允許在子線程更新UI嗎,實(shí)際上并不是這樣的,在ViewRootImpl中的checkThread方法會(huì)校驗(yàn)mThread != Thread.currentThread(),mThread的初始化是在ViewRootImpl的的構(gòu)造器中,也就是說(shuō)一個(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通過(guò)ViewRootImpl來(lái)建立聯(lián)系,因此,它是以View的形式存在的。我們來(lái)看一下Toast中的ViewRootImpl的創(chuàng)建過(guò)程,調(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),通過(guò)ViewRootImpl來(lái)更新界面并完成Window的添加過(guò)程。

    //WindowManagerGlobal.java root?=?new?ViewRootImpl(view.getContext(),?display);?//創(chuàng)建ViewRootImplview.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);//?do?this?last?because?it?fires?off?messages?to?start?doing?thingstry?{//ViewRootImplroot.setView(view,?wparams,?panelParentView);}?catch?(RuntimeException?e)?{//?BadTokenException?or?InvalidDisplayException,?clean?up.if?(index?>=?0)?{removeViewLocked(index,?true);}throw?e;} }

    setView內(nèi)部會(huì)通過(guò)requestLayout來(lái)完成異步刷新請(qǐng)求,同時(shí)也會(huì)調(diào)用checkThread方法來(lái)檢驗(yàn)線程的合法性。

    @Override public?void?requestLayout()?{if?(!mHandlingLayoutInLayoutRequest)?{checkThread();mLayoutRequested?=?true;scheduleTraversals();} }

    因此,我們的ViewRootImpl的創(chuàng)建是在子線程,所以mThread的值也是子線程,同時(shí)我們的更新也是在子線程,所以不會(huì)產(chǎn)生異常,同時(shí)也可以參考這篇文章分析,寫(xiě)的非常詳細(xì)。同理下面的代碼也可以驗(yàn)證這個(gè)情況。

    //子線程中調(diào)用???? public?void?showDialog(){new?Thread(new?Runnable()?{@Overridepublic?void?run()?{//創(chuàng)建Looper,MessageQueueLooper.prepare();new?Handler().post(new?Runnable()?{@Overridepublic?void?run()?{builder?=?new?AlertDialog.Builder(HandlerActivity.this);builder.setTitle("jackie");alertDialog?=?builder.create();alertDialog.show();alertDialog.hide();}});//開(kāi)始處理消息Looper.loop();}}).start();}

    在子線程中調(diào)用showDialog方法,先調(diào)用alertDialog.show()方法,再調(diào)用alertDialog.hide()方法,hide方法只是將Dialog隱藏,并沒(méi)有做其他任何操作(沒(méi)有移除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:?mainProcess:?com.jackie.testdialog,?PID:?24819android.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èn)網(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è)兩大問(wèn)題:ThreadPolicy(線程策略)和VmPolicy(VM策略),這里把嚴(yán)苛模式的網(wǎng)絡(luò)檢測(cè)關(guān)了,就可以在主線程中執(zhí)行網(wǎng)絡(luò)操作了,一般是不建議這么做的。

    系統(tǒng)為什么不建議在子線程中訪問(wèn)UI?

    這是因?yàn)?Android 的UI控件不是線程安全的,如果在多線程中并發(fā)訪問(wèn)可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài),那么為什么系統(tǒng)不對(duì)UI控件的訪問(wèn)加上鎖機(jī)制呢?缺點(diǎn)有兩個(gè):

  • 首先加上鎖機(jī)制會(huì)讓UI訪問(wèn)的邏輯變得復(fù)雜

  • 鎖機(jī)制會(huì)降低UI訪問(wèn)的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行。

  • 所以最簡(jiǎn)單且高效的方法就是采用單線程模型來(lái)處理UI操作。(安卓開(kāi)發(fā)藝術(shù)探索)

    子線程如何通知主線程更新UI(都是通過(guò)Handle發(fā)送消息到主線程操作UI的)

  • 主線程中定義 Handler,子線程通過(guò) 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ù)完成,用戶(hù)應(yīng)該手動(dòng)退出,而不是讓其一直休眠等待。(引用自Gityuan)線程其實(shí)就是一段可執(zhí)行的代碼,當(dāng)可執(zhí)行的代碼執(zhí)行完成后,線程的生命周期便該終止了,線程退出。而對(duì)于主線程,我們是絕不希望會(huì)被運(yùn)行一段時(shí)間,自己就退出,那么如何保證能一直存活呢?簡(jiǎn)單做法就是可執(zhí)行代碼是能一直執(zhí)行下去的,死循環(huán)便能保證不會(huì)被退出,例如,binder 線程也是采用死循環(huán)的方法,通過(guò)循環(huán)方式不同與 Binder 驅(qū)動(dòng)進(jìn)行讀寫(xiě)操作,當(dāng)然并非簡(jiǎn)單地死循環(huán),無(wú)消息時(shí)會(huì)休眠。Android是基于消息處理機(jī)制的,用戶(hù)的行為都在這個(gè)Looper循環(huán)中,我們?cè)谛菝邥r(shí)點(diǎn)擊屏幕,便喚醒主線程繼續(xù)進(jìn)行工作。

    主線程的死循環(huán)一直運(yùn)行是不是特別消耗 CPU 資源呢?其實(shí)不然,這里就涉及到 Linux ?pipe/epoll機(jī)制,簡(jiǎn)單說(shuō)就是在主線程的 MessageQueue 沒(méi)有消息時(shí),便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此時(shí)主線程會(huì)釋放 CPU 資源進(jìn)入休眠狀態(tài),直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生,通過(guò)往 pipe 管道寫(xiě)端寫(xiě)入數(shù)據(jù)來(lái)喚醒主線程工作。這里采用的 epoll 機(jī)制,是一種IO多路復(fù)用機(jī)制,可以同時(shí)監(jiān)控多個(gè)描述符,當(dāng)某個(gè)描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮?#xff0c;本質(zhì)同步I/O,即讀寫(xiě)是阻塞的。所以說(shuō),主線程大多數(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);

    我們來(lái)看看dispatchMessage的源碼:

    ??public?void?dispatchMessage(@NonNull?Message?msg)?{if?(msg.callback?!=?null)?{handleCallback(msg);}?else?{//如果?callback?處理了該?msg?并且返回?true,?就不會(huì)再回調(diào)?handleMessageif?(mCallback?!=?null)?{if?(mCallback.handleMessage(msg))?{return;}}handleMessage(msg);}}

    1.如果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()?{@Overridepublic?void?run()?{} });

    而handleCallback方法中調(diào)用的是Runnable的run方法。

    private?static?void?handleCallback(Message?message)?{message.callback.run(); }

    2.如果Message對(duì)象沒(méi)有CallBack回調(diào),進(jìn)入else分支判斷Handler的CallBack是否為空,不為空?qǐng)?zhí)行CallBack的handleMessage方法,然后return,構(gòu)建Handler的CallBack代碼如下:

    Handler.Callback?callback?=?new?Handler.Callback()?{@Overridepublic?boolean?handleMessage(@NonNull?Message?msg)?{//retrun?true,就不執(zhí)行下面的邏輯了,可以用于做優(yōu)先級(jí)的處理return?false;} };

    3.最后才調(diào)用到Handler的handleMessage()函數(shù),也就是我們經(jīng)常去重寫(xiě)的函數(shù),在該方法中做消息的處理。

    使用場(chǎng)景

    可以看到Handler.Callback 有優(yōu)先處理消息的權(quán)利 ,當(dāng)一條消息被 Callback 處理并攔截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不會(huì)被調(diào)用了;如果 Callback 處理了消息,但是并沒(méi)有攔截,那么就意味著一個(gè)消息可以同時(shí)被 Callback 以及 Handler 處理。我們可以利用CallBack這個(gè)攔截來(lái)攔截Handler的消息。

    場(chǎng)景:Hook ActivityThread.mH , 在 ActivityThread 中有個(gè)成員變量 mH ,它是個(gè) Handler,又是個(gè)極其重要的類(lèi),幾乎所有的插件化框架都使用了這個(gè)方法。

    Handler.post(Runnable r)方法的執(zhí)行邏輯

    我們需要分析平時(shí)常用的Handler.post(Runnable r)方法是如何執(zhí)行的,是否新創(chuàng)建了一個(gè)線程了呢,實(shí)際上并沒(méi)有,這個(gè)Runnable對(duì)象只是被調(diào)用了它的run方法,根本并沒(méi)有啟動(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)行線程切換的

    原理很簡(jiǎn)單,線程間是共享資源的,子線程通過(guò)handler.sendXXX,handler.postXXX等方法發(fā)送消息,然后通過(guò)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)部類(lèi)導(dǎo)致的內(nèi)存泄漏改為靜態(tài)內(nèi)部類(lèi),并對(duì)上下文或者Activity/Fragment使用弱引用。

  • 同時(shí)還有一個(gè)很關(guān)鍵的點(diǎn),如果有個(gè)延時(shí)消息,當(dāng)界面關(guān)閉時(shí),該Handler中的消息還沒(méi)有處理完畢,那么最終這個(gè)消息是怎么處理的?經(jīng)過(guò)測(cè)試,比如我打開(kāi)界面后延遲10s發(fā)送消息,關(guān)閉界面,最終在Handler(匿名內(nèi)部類(lèi)創(chuàng)建的)的handMessage方法中還是會(huì)收到消息(打印日志)。因?yàn)闀?huì)有一條MessageQueue -> Message -> Handler -> Activity的引用鏈,所以Handler不會(huì)被銷(xiāo)毀,Activity也不會(huì)被銷(xiāo)毀。

    正確創(chuàng)建Message實(shí)例

  • 通過(guò) Message 的靜態(tài)方法 Message.obtain() 獲取;

  • 通過(guò) Handler 的公有方法 handler.obtainMessage()

  • 所有的消息會(huì)被回收,放入sPool中,使用享元設(shè)計(jì)模式。

    Handler深層次問(wèn)題解答

    ThreadLocal

    ThreadLocal為每個(gè)線程都提供了變量的副本,使得每個(gè)線程在某一時(shí)間訪問(wèn)到的并非同一個(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,不過(guò)持有 ThreadLocalMap 的不是 ThreadLocal,而是 Thread。Thread 這個(gè)類(lèi)內(nèi)部有一個(gè)私有屬性 threadLocals,其類(lèi)型就是 ThreadLocalMap,ThreadLocalMap 的 Key 是 ThreadLocal。

    精簡(jiǎn)之后的代碼如下:

    class?Thread?{//內(nèi)部持有ThreadLocalMapThreadLocal.ThreadLocalMap?threadLocals; } class?ThreadLocal<T>{public?T?get()?{//首先獲取線程持有的//ThreadLocalMapThreadLocalMap?map?=Thread.currentThread().threadLocals;//在ThreadLocalMap中//查找變量Entry?e?=?map.getEntry(this);return?e.value;??}static?class?ThreadLocalMap{//內(nèi)部是數(shù)組而不是MapEntry[]?table;//根據(jù)ThreadLocal查找EntryEntry?getEntry(ThreadLocal?key){//省略查找邏輯}//Entry定義static?class?Entry?extendsWeakReference<ThreadLocal>{Object?value;}} }

    在Java的實(shí)現(xiàn)方案中,ThreadLocal僅僅只是一個(gè)代理工具類(lèi),內(nèi)部并不持有任何線程相關(guān)的數(shù)據(jù),所有和線程相關(guān)的數(shù)據(jù)都存儲(chǔ)在Thread里面,這樣的設(shè)計(jì)從數(shù)據(jù)的親緣性上來(lái)講,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的生命周期往往都比線程要長(zhǎng),所以這種設(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í)間太長(zhǎng),往往和程序都是同生共死的,這就意味著Thread持有的ThreadLocalMap一直都不會(huì)被回收,再加上ThreadLocalMap中的Entry對(duì)ThreadLocal是弱引用,所以只要ThreadLocal結(jié)束了自己的生命周期是可以被回收掉的。但是Entry中的Value卻是被Entry強(qiáng)引用的,所以即便Value的生命周期結(jié)束了,Value也是無(wú)法被回收的,從而導(dǎo)致內(nèi)存泄漏。

    所以我們可以通過(guò)try{}finally{}方案來(lái)手動(dòng)釋放資源

    ExecutorService?es; ThreadLocal?tl; es.execute(()->{//ThreadLocal增加變量tl.set(obj);try?{//?省略業(yè)務(wù)邏輯代碼}finally?{//手動(dòng)清理ThreadLocal?tl.remove();} });

    epoll機(jī)制

    epoll機(jī)制在Handler中的應(yīng)用,在主線程的 MessageQueue 沒(méi)有消息時(shí),便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,最終調(diào)用到epoll_wait()進(jìn)行阻塞等待。此時(shí)主線程會(huì)釋放 CPU 資源進(jìn)入休眠狀態(tài),直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生,通過(guò)往 pipe 管道寫(xiě)端寫(xiě)入數(shù)據(jù)來(lái)喚醒主線程工作。這里采用的 epoll 機(jī)制,是一種IO多路復(fù)用機(jī)制,可以同時(shí)監(jiān)控多個(gè)描述符,當(dāng)某個(gè)描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮?#xff0c;本質(zhì)同步I/O,即讀寫(xiě)是阻塞的。所以說(shuō),主線程大多數(shù)時(shí)候都是處于休眠狀態(tài),并不會(huì)消耗大量CPU資源。

  • 表面上看epoll的性能最好,但是在連接數(shù)少并且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機(jī)制需要很多函數(shù)回調(diào)。

  • select低效是因?yàn)槊看嗡夹枰喸?xún)。但低效也是相對(duì)的,視情況而定,也可通過(guò)良好的設(shè)計(jì)改善。

  • 之所以選擇Handler底層選擇epoll機(jī)制,我感覺(jué)是epoll在效率上更高。在select/poll中,進(jìn)程只有在調(diào)用一定的方法后,內(nèi)核才對(duì)所有監(jiān)視的文件描述符進(jìn)行掃描,而epoll事先通過(guò)epoll_ctl()來(lái)注冊(cè)一個(gè)文件描述符,一旦基于某個(gè)文件描述符就緒時(shí),內(nèi)核會(huì)采用類(lèi)似callback的回調(diào)機(jī)制,迅速激活這個(gè)文件描述符,當(dāng)進(jìn)程調(diào)用epoll_wait()時(shí)便得到通知。(此處去掉了遍歷文件描述符,而是通過(guò)監(jiān)聽(tīng)回調(diào)的的機(jī)制。這正是epoll的魅力所在。)

    Handler的同步屏障機(jī)制

    如果有一個(gè)緊急的Message需要優(yōu)先處理,該怎么做?這其實(shí)涉及到架構(gòu)方面的設(shè)計(jì)了,通用場(chǎng)景和特殊場(chǎng)景的設(shè)計(jì)。你可能會(huì)想到sendMessageAtFrontOfQueue()這個(gè)方法,實(shí)際也遠(yuǎn)遠(yuǎn)不只是如此,Handler中加入了同步屏障這種機(jī)制,來(lái)實(shí)現(xiàn)[異步消息優(yōu)先]執(zhí)行的功能。

    postSyncBarrier()發(fā)送同步屏障,removeSyncBarrier()移除同步屏障。

    同步屏障的作用可以理解成攔截同步消息的執(zhí)行,主線程的 Looper 會(huì)一直循環(huán)調(diào)用 MessageQueue 的 next() 來(lái)取出隊(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è)異步消息來(lái)執(zhí)行,否則就讓 next() 方法陷入阻塞狀態(tài)。如果 next() 方法陷入阻塞狀態(tài),那么主線程此時(shí)就是處于空閑狀態(tài)的,也就是沒(méi)在干任何事。所以,如果隊(duì)頭是一個(gè)同步屏障的消息的話,那么在它后面的所有同步消息就都被攔截住了,直到這個(gè)同步屏障消息被移除出隊(duì)列,否則主線程就一直不會(huì)去處理同步屏幕后面的同步消息。

    而所有消息默認(rèn)都是同步消息,只有手動(dòng)設(shè)置了異步標(biāo)志,這個(gè)消息才會(huì)是異步消息。另外,同步屏障消息只能由內(nèi)部來(lái)發(fā)送,這個(gè)接口并沒(méi)有公開(kāi)給我們使用。

    Choreographer 里所有跟 message 有關(guān)的代碼,你會(huì)發(fā)現(xiàn),都手動(dòng)設(shè)置了異步消息的標(biāo)志,所以這些操作是不受到同步屏障影響的。這樣做的原因可能就是為了盡可能保證上層 app 在接收到屏幕刷新信號(hào)時(shí),可以在第一時(shí)間執(zhí)行遍歷繪制 View 樹(shù)的工作。

    Choreographer 過(guò)程中的動(dòng)作也都是異步消息,這樣可以確保 Choreographer 的順利運(yùn)轉(zhuǎn),也確保了第一時(shí)間執(zhí)行 doTraversal(doTraversal → performTraversals 就是執(zhí)行 view 的 layout、measure、draw),這個(gè)過(guò)程中如果有其他同步消息,也無(wú)法得到處理,都要等到 doTraversal 之后。

    因?yàn)橹骶€程中如果有太多消息要執(zhí)行,而這些消息又是根據(jù)時(shí)間戳進(jìn)行排序,如果不加一個(gè)同步屏障的話,那么遍歷繪制 View 樹(shù)的工作就可能被迫延遲執(zhí)行,因?yàn)樗残枰抨?duì),那么就有可能出現(xiàn)當(dāng)一幀都快結(jié)束的時(shí)候才開(kāi)始計(jì)算屏幕數(shù)據(jù),那即使這次的計(jì)算少于 16.6ms,也同樣會(huì)造成丟幀現(xiàn)象。

    那么,有了同步屏障消息的控制就能保證每次一接收到屏幕刷新信號(hào)就第一時(shí)間處理遍歷繪制 View 樹(shù)的工作么?

    只能說(shuō),同步屏障是盡可能去做到,但并不能保證一定可以第一時(shí)間處理。因?yàn)?#xff0c;同步屏障是在 scheduleTraversals() 被調(diào)用時(shí)才發(fā)送到消息隊(duì)列里的,也就是說(shuō),只有當(dāng)某個(gè) View 發(fā)起了刷新請(qǐng)求時(shí),在這個(gè)時(shí)刻后面的同步消息才會(huì)被攔截掉。如果在 scheduleTraversals() 之前就發(fā)送到消息隊(duì)列里的工作仍然會(huì)按順序依次被取出來(lái)執(zhí)行。

    下面是部分詳細(xì)的分析:

    WindowManager維護(hù)著所有的Activity的DecorView和ViewRootImpl。在前面我們講過(guò),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綁定起來(lái)了。每個(gè)Activity的根布局都是DecorView,而DecorView的parent又是ViewRootImpl,所以在子View里執(zhí)行invalidate()之類(lèi)的工作,循環(huán)找parent,最后都會(huì)找到ViewRootImpl里來(lái)。所以實(shí)際上View的刷新都是由ViewRootImpl來(lái)控制的。

    即使是界面上一個(gè)小小的 View 發(fā)起了重繪請(qǐng)求時(shí),都要層層走到 ViewRootImpl,由它來(lái)發(fā)起重繪請(qǐng)求,然后再由它來(lái)開(kāi)始遍歷 View 樹(shù),一直遍歷到這個(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í)打開(kāi)一個(gè) Activity,當(dāng)它的 onCreate---onResume 生命周期都走完后,才將它的 DecoView 與新建的一個(gè) ViewRootImpl 對(duì)象綁定起來(lái),同時(shí)開(kāi)始安排一次遍歷 View 任務(wù)也就是繪制 View 樹(shù)的操作等待執(zhí)行,然后將 DecoView 的 parent 設(shè)置成 ViewRootImpl 對(duì)象。所以我們?cè)趏nCreate~onResume中獲取不到View寬高,界面的繪制也是在onResume之后才開(kāi)始執(zhí)行的。

    ViewRootImpl.scheduleTraversals()的一系列分析以及屏幕刷新機(jī)制可以參考這篇文章,這里的內(nèi)容也是大部分參考它的,同步屏障相關(guān)的分析內(nèi)容也在里面。Choreographer主要作用是協(xié)調(diào)動(dòng)畫(huà),輸入和繪制的時(shí)間,它從顯示子系統(tǒng)接收定時(shí)脈沖(例如垂直同步),然后安排渲染下一個(gè)frame的一部分工作。可通過(guò)Choreographer.getInstance().postFrameCallback()來(lái)監(jiān)聽(tīng)?zhēng)是闆r。

    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);}@Overridepublic?void?doFrame(long?frameTimeNanos)?{?//Vsync信號(hào)到來(lái)的時(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()))

    丟幀的原因:造成丟幀大體上有兩類(lèi)原因,一是遍歷繪制 View 樹(shù)計(jì)算屏幕數(shù)據(jù)的時(shí)間超過(guò)了 16.6ms;二是,主線程一直在處理其他耗時(shí)的消息,導(dǎo)致遍歷繪制 View 樹(shù)的工作遲遲不能開(kāi)始,從而超過(guò)了 16.6 ms 底層切換下一幀畫(huà)面的時(shí)機(jī)。

    Handler鎖相關(guān)問(wè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)部通過(guò)synchronized關(guān)鍵字保證線程安全。同時(shí)messagequeue.next()內(nèi)部也會(huì)通過(guò)synchronized加鎖,確保取的時(shí)候線程安全,同時(shí)插入也會(huì)加鎖。這個(gè)問(wèn)題其實(shí)不難,只是看你有沒(méi)有了解源碼。

    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)部也通過(guò)synchronized來(lái)保證線程安全,比如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修飾的,說(shuō)明該方法是提供外部調(diào)用的,Looper創(chuàng)建成功提供給外部使用。

    IntentService

    簡(jiǎn)單看一下源碼就能看到Handler的應(yīng)用,Handler的handMessage最終會(huì)回調(diào)到onHandleIntent方法。

    public?abstract?class?IntentService?extends?Service?{private?volatile?Looper?mServiceLooper;@UnsupportedAppUsageprivate?volatile?ServiceHandler?mServiceHandler;

    如何打造一個(gè)不崩潰的程序

    打造一個(gè)不崩潰的程序,可以參考我的這篇文章:

    https://juejin.cn/post/6856654877142188046#heading-2

    Glide中的應(yīng)用

    Glide 相信大應(yīng)該非常熟悉了,我們都知道Glide生命周期的控制(如果不了解,可以看下Glide相關(guān)文章的分析,跟LiveData 是同一個(gè)原理)是通過(guò)添加一個(gè)空的Fragment到Activity 或者Fragment中,然后通過(guò)FragmentMannager管理Fragment的生命周期,從而達(dá)到生命周期的控制。下面是節(jié)選了Glide一段添加Fragment的代碼:

    private?RequestManagerFragment?getRequestManagerFragment(@NonNull?final?android.app.FragmentManager?fm,@Nullable?android.app.Fragment?parentHint,boolean?isParentVisible)?{//1.通過(guò)FragmentManager獲取?RequestManagerFragment,如果已添加到FragmentManager則返回實(shí)例,否則為空RequestManagerFragment?current?=?(RequestManagerFragment)?fm.findFragmentByTag(FRAGMENT_TAG);if?(current?==?null)?{//2.如果fm里面沒(méi)有則從map緩存里取current?=?pendingRequestManagerFragments.get(fm);if?(current?==?null)?{//3.第1和2步都沒(méi)有,說(shuō)明確實(shí)沒(méi)創(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步),FragmentManager 去find 不就可以做到了嗎?

    其實(shí)答案很簡(jiǎn)單,學(xué)了Handler原理之后我們知道:就是在第5步執(zhí)行完之后并沒(méi)有將Fragment添加到FragmentManager(事件排隊(duì)中),而是發(fā)送添加Fragment的事件。接下來(lái)我們看代碼:

    //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方法,我們可以看到他是通過(guò)Handler 發(fā)送事件。

    這也就解釋了為什么第5步執(zhí)行完之后Fragment為什么沒(méi)有立即添加到FragmentManager,所以需要Map緩存Fragment來(lái)標(biāo)記是否有Fragment添加。再接著有了第6步發(fā)送移除Map緩存的消息,因?yàn)镠andler處理消息是有序的。

    /? ?總結(jié)? ?/

    本文其實(shí)對(duì)源碼的分析并沒(méi)有非常仔細(xì),卻從整個(gè)系統(tǒng)各個(gè)方面的運(yùn)用進(jìn)行的不同的擴(kuò)展,以及一些第三方框架中的使用,希望本文對(duì)你有幫助,喜歡的點(diǎn)個(gè)贊吧~

    推薦閱讀:

    我又開(kāi)發(fā)了一個(gè)非常好用的開(kāi)源庫(kù)

    我的新書(shū),《第一行代碼?第3版》已出版!

    Android上的網(wǎng)絡(luò)抓包原來(lái)是這樣工作的

    歡迎關(guān)注我的公眾號(hào)

    學(xué)習(xí)技術(shù)或投稿

    長(zhǎng)按上圖,識(shí)別圖中二維碼即可關(guān)注

    總結(jié)

    以上是生活随笔為你收集整理的Handler的内功心法,值得拥有!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。