日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

Android Handler原理

發布時間:2025/3/20 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android Handler原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

Handler消息處理機制在Android開發中起著舉足輕重的作用,我們有必要好好理解下其原理,先前我寫的一篇文章,感覺疏漏了好多東西,因此打算寫這篇文章,下面我們先從一個簡單的例子出發

一、日常使用

假設我們有這么一個需要,請求網絡然后將圖片展示出來,我們知道網絡請求是不允許在主線程執行的,而UI是不能在子線程(具體是不允許在非創建UI的原始線程)更新的,因此我們需要在子線程請求網絡獲得了數據以后再切換回主線程更新UI,這個例子中Handler就是起著切換線程的作用,下面的代碼演示了這個例子

class MainActivity : AppCompatActivity() {private lateinit var mImageView: ImageViewoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)mImageView = findViewById(R.id.iv)loadImage()}private fun loadImage() {Thread {val url = URL("https://img-my.csdn.net/uploads/201309/01/1378037235_7476.jpg")val conn = url.openConnection()val bitmap = BitmapFactory.decodeStream(conn.inputStream)runOnUiThread {mImageView.setImageBitmap(bitmap)}}.start()} } 復制代碼

咦!,說好的Handler去哪了?這里的runOnUIThread方法內部實現其實就是利用了Handler,我們來看看它的源碼

public final void runOnUiThread(Runnable action) {if (Thread.currentThread() != mUiThread) {mHandler.post(action);} else {action.run();} } 復制代碼

該方法首先判斷了當前線程是否是主線程,如果不是主線程就調用mHandler.post(),如果當前線程就是主線程就直接運行,下面我們來分析看看Handler的原理

二、Handler原理

要想分析Handler的原理,我們先從Handler的創建過程開始分析

Handler的創建

Activity的這個mHandler是怎么來的呢?原來mHandler是Activity的成員變量,在Activity實例創建的時候就創建了

final Handler mHandler = new Handler(); 復制代碼

接著看看Handler的構造方法

public Handler() {this(null, false); } public Handler(Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;// 代表發送的消息是否是異步的mAsynchronous = async; } 復制代碼

首先判斷了該Handler派生類是否是非靜態內部類,如果是的話就打印出日志提示可能導致內存泄露,然后調用了Looper.myLooper獲取到當前線程的Looper對象,如果當前線程沒有Looper就會拋出異常,最后將Looper中的MessageQueue對象賦值給mQueue,callback賦值給mCallback,aync賦值給mAsynchronous,我們來看看Looper.myLooper做了些什么

public static @Nullable Looper myLooper() {return sThreadLocal.get(); } 復制代碼

從ThreadLocal里面去Looper,那么是在哪里把Looper設置到ThreadLocal里面去的呢?其實Looper提供了prepare方法來創建當前線程的Looper,我們來看看代碼

public static void prepare() {// 表示允許退出循環prepare(true); } 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的時候才會去創建Looper對象并將其設置到ThreadLocal中去,不然就拋出異常說一個線程只能擁有一個Looper,繼續看看Looper的構造方法

// 這里的quitAllowed是true private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread(); } 復制代碼

又創建了一個MessageQueue對象,繼續看看它的構造方法

// 這里的quitAllowed是true MessageQueue(boolean quitAllowed) {mQuitAllowed = quitAllowed;mPtr = nativeInit(); } 復制代碼

調用了一個Native方法就結束了,其實mPtr是NativeMessageQueue與MessageQueue之間的橋梁內部會調用epoll.create()、epoll.ctl(),暫時不看native層代碼

源碼看到這里就會產生一個疑問,既然創建Handler的時候判斷了當前線程的Looper是否為null,為null就會拋出異常,那么Activity的Handler是怎么創建成功的呢?其實在Activity實例創建前主線程就已經有Looper對象了,這個得從ActivityThread開始說起。ActivityThread是一個應用程序的入口里面有一個main方法,我們來看看

// 忽略其它代碼 public static void main(String[] args) {...Looper.prepareMainLooper();Looper.loop();... } 復制代碼

Looper.loop()后面會講到先忽略,main方法內部調用了Looper.prepareMainLooper()這個方法跟上面講到的Looper.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方法前面已經分析過了但是主線程是不允許退出的,所以傳入了false,后面判斷了如果sMainLooper不為空那么就拋出異常,至此主線程的Looper創建成功這也就解釋了為什么Activity中可以直接創建Handler,接著我們分析那個post方法干了些什么事情

Handler的消息發送

Handler提供了很多方法用于發送消息,比如以下幾種

  • sendEmptyMessage(int what) 發送一個空消息,what用于判斷消息類別
  • sendEmptyMessageDelayed(int what, long delayMillis) 發送一個空消息,延遲delayMillis毫秒執行,what用于判斷消息類別
  • sendEmptyMessageAtTime(int what, long uptimeMillis) 發送一個空消息,在uptimeMillis的時候執行,what用于判斷消息類別
  • sendMessageDelayed(Message msg, long delayMillis) 發送一個消息,延遲delayMillis毫秒執行
  • sendMessageAtTime(Message msg, long uptimeMillis) 發送一個消息,在uptimeMillis的時候執行
  • sendMessageAtFrontOfQueue(Message msg) 發送一個消息,該消息會排在消息隊列的隊首
  • executeOrSendMessage(Message msg) 如果Handler中的Looper與當前線程的Looper一致就直接分開消息,不然就發送一個消息

我們繼續著看post方法的實現

public final boolean post(Runnable r) {return sendMessageDelayed(getPostMessage(r), 0); } 復制代碼

其實post方法內部也就是發送了一個消息

private static Message getPostMessage(Runnable r) {// message內部維護了一個Message鏈表,以達到復用的目的,記得不要直接newMessage m = Message.obtain();m.callback = r;return m; } public final boolean sendMessageDelayed(Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } public boolean sendMessageAtTime(Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis); } 復制代碼

最終調用到了sendMessageAtTime,其實幾乎所有發送消息的方法最終都會調用到該方法,繼續看enqueueMessage的實現

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis); } 復制代碼

這里將本Handler的實例賦值給了msg.target,這個很重要以后會用到,然后判斷下當然Handler是否是異步的,是的話就將消息設置成異步,我們這里不是異步的,接著繼續看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) {if (mQuitting) {IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");Log.w(TAG, e.getMessage(), e);msg.recycle();return false;}msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}if (needWake) {nativeWake(mPtr);}}return true; } 復制代碼

該方法首先判斷了msg.target是否為空,這個我們剛才看到已經設置了,然后判斷msg是否正在被使用,然后再判斷消息隊列是否已經退出了,如果已經退出了就將msg回收并拋出個異常,下面那個同步代碼塊其實處理的邏輯就是將msg放入到消息隊列中去,插入過程分為以下兩步,至于needWake是用于判斷是否要喚醒處于nativePollOnce而阻塞的Message.next方法

  • 如果滿足p == null || when == 0 || when < p.when其實也就是如果消息隊列的頭指針為空,或者當前消息的執行時間為0,或者當前消息的執行時間先與消息隊列隊首的執行時間,那么將當前msg當做頭指針
  • 如果不滿足第一種情況就根據當前msg.when決定插入的位置

現在已經將消息放到的消息隊列中,但是什么時候這個消息才能得到執行呢?這就要看看前面跳過的ActivityThread的main方法中的Looper.loop()

public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;boolean slowDeliveryDetected = false;for (;;) {Message msg = queue.next(); // might blockif (msg == null) {return;}final Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}final long traceTag = me.mTraceTag;long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;if (thresholdOverride > 0) {slowDispatchThresholdMs = thresholdOverride;slowDeliveryThresholdMs = thresholdOverride;}final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);final boolean needStartTime = logSlowDelivery || logSlowDispatch;final boolean needEndTime = logSlowDispatch;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;final long dispatchEnd;try {msg.target.dispatchMessage(msg);dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (logSlowDelivery) {if (slowDeliveryDetected) {if ((dispatchStart - msg.when) <= 10) {Slog.w(TAG, "Drained");slowDeliveryDetected = false;}} else {if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",msg)) {// Once we write a slow delivery log, suppress until the queue drains.slowDeliveryDetected = true;}}}if (logSlowDispatch) {showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);}if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}final long newIdent = Binder.clearCallingIdentity();if (ident != newIdent) {Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+ Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);}msg.recycleUnchecked();} } 復制代碼

這個代碼有點長,主要流程如下

  • 首先判斷一下當前線程是否包含Looper不包含就拋出異常
  • 調用MessageQueue的next方法獲取Message,如果返回了null,標志了MessageQueue已經退出了,所以Looper也要退出
  • 獲取Looper中的mLogging用于打印日志,我們可以通過setMessageLogging進行設置,設置后每次收到消息和消息處理完畢都會有日志我們可以根據這些日志分析ANR是由于處理哪個消息超時造成的
  • 設置慢分發時間和慢交付時間,可以通過adb進行設置,慢分發時間表示如果這個消息的實際執行時間比其設置的slowDeliveryThresholdMs要長就會打印警告日志,慢交付時間表示這個消息從消息隊列取出時間比其設置的when超過slowDispatchThresholdMs就會打印警告日志
  • 記錄開始分發的時間
  • 調用msg.target.dispatchMessage進行分發消息,其中msg.target就是一個Handler實例,上文說到過的
  • 記錄結束分發的時間
  • 根據實際情況打印日志
  • 回收msg

loop方法調用queue.next取出消息,我們來看看該方法的實現

Message next() {final long ptr = mPtr;if (ptr == 0) {return null;}int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: " + msg);msg.markInUse();return msg;}} else {// No more messages.nextPollTimeoutMillis = -1;}if (mQuitting) {dispose();return null;}if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) {mBlocked = true;continue;}if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}pendingIdleHandlerCount = 0;nextPollTimeoutMillis = 0;} } 復制代碼

該方法主要流程如下

  • 調用nativePollOnce,阻塞等待下一個可執行消息,該方法離開阻塞
  • 判斷第一個消息的target是否為空,如果不為空表示是一個普通的消息,如果為空則表示是一個同步屏障消息(在屏幕刷新的時候會發送),遍歷消息隊列找到第一個異步消息賦值給msg
  • 判斷msg是否為空,如果為空那么進行無超時的等待,直到被喚醒
  • 判斷msg是否到了執行時間,如果不到就執行阻塞等待msg.when - now,如果已經到了就將該消息返回
  • 如果消息隊列已經退出就返回null

拿到了消息以后就調用了handler.dispatchMessage我們來看看其實現

public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);} } 復制代碼

首先判斷是否該Message是否設置了callBack,設置了就直接運行,然后判斷Handler是否設置了callBack,設置了就調用callback.handleMessage如果返回false,繼續調用handleMessage

三、總結

  • Handler的作用就是把想要執行的操作從當前線程切換到Handler中Looper所在的線程進行執行
  • 每次通過Handler發送消息其實就是把消息插入到了消息隊列中,然后根據情況判斷是否要喚醒處于調用nativePollOnce阻塞狀態的線程

轉載于:https://juejin.im/post/5c9d846ff265da60d95fd58e

總結

以上是生活随笔為你收集整理的Android Handler原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。