溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何掌握Handler的初級(jí)、中級(jí)、高級(jí)問法

發(fā)布時(shí)間:2021-10-23 17:03:51 來源:億速云 閱讀:144 作者:iii 欄目:移動(dòng)開發(fā)

這篇文章主要講解了“如何掌握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工作流程圖

如何掌握Handler的初級(jí)、中級(jí)、高級(jí)問法

可以看到在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 {      &middot;&middot;&middot;      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;              &middot;&middot;&middot;              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();      &middot;&middot;&middot;      final MessageQueue queue = me.mQueue;      &middot;&middot;&middot;      for (;;) { //死循環(huán)          //獲取消息          Message msg = queue.next(); // might block          if (msg == null) {              // No message indicates that the message queue is quitting.              return;          }          &middot;&middot;&middot;          msg.target.dispatchMessage(msg);          &middot;&middot;&middot;          //回收復(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) {      &middot;&middot;&middot;      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è)

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

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

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

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

  1.  主線程中定義 Handler,子線程通過 mHandler 發(fā)送消息,主線程 Handler 的 handleMessage 更新UI。

  2.  用 Activity 對(duì)象的 runOnUiThread 方法。

  3.  創(chuàng)建 Handler,傳入 getMainLooper。

  4.  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)存泄漏?

  1.  有延時(shí)消息,在界面關(guān)閉后及時(shí)移除Message/Runnable,調(diào)用handler.removeCallbacksAndMessages(null)

  2.  內(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í)例

  1.  通過 Message 的靜態(tài)方法 Message.obtain() 獲??;

  2.  通過 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ì)成這樣

如何掌握Handler的初級(jí)、中級(jí)、高級(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。

如何掌握Handler的初級(jí)、中級(jí)、高級(jí)問法

精簡之后的代碼如下

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詳解,這邊拿文章中的最后兩段話:

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

  2.  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 &rarr; 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;              &middot;&middot;&middot;              // 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)求              &middot;&middot;&middot;              view.assignParent(this); //將當(dāng)前ViewRootImpl對(duì)象this作為參數(shù)調(diào)用了DecorView的            assignParent              &middot;&middot;&middot;          }      }  }

在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方法

如何掌握Handler的初級(jí)、中級(jí)、高級(jí)問法

源碼如下:

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) {          &middot;&middot;&middot;      }      }

其內(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)注!

向AI問一下細(xì)節(jié)

免責(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)容。

AI