Android入門教程 | Handler,Looper與MessageQueue使用與分析

Android_anzi發表於2021-11-25

從原始碼角度分析Handler。有利於使用 Handler 和分析 Handler 的相關問題。認識Looper與Handler的關係。

Handler 簡介

一個 Handler 允許傳送和處理 Message,透過關聯執行緒的 MessageQueue 執行 Runnable 物件。 每個 Handler 例項都和一個單獨的執行緒及其訊息佇列繫結。 可以將一個任務切換到 Handler 所在的執行緒中去執行。一個用法就是子執行緒透過 Handler 更新 UI。

Handler主要有2種用法:

  • 做出計劃,在未來某個時間點執行訊息和Runnable
  • 執行緒排程,在其他執行緒規劃並執行任務

要使用好 Handler,需要了解與其相關的  MessageQueue,  MessageLooper;不能孤立的看Handler。 Handler就像一個操作者(或者像一個對開發者開放的視窗),利用 MessageQueueLooper來實現任務排程和處理。

Handler持有 Looper 的例項,直接持有looper的訊息佇列。

屬性與構造器

Handler 類中持有的例項,持有 looper,messageQueue 等等。

final Looper mLooper; // Handler持有Looper例項
final MessageQueue mQueue; // Handler持有訊息佇列
final Callback mCallback;

在 Handler 的構造器中,我們可以看到 Handler 獲取了 Looper 的訊息佇列。

public Handler(Callback callback, boolean async) {
    // rustfisher 處理異常
    mLooper = Looper.myLooper();
    // rustfisher 處理特殊情況...
    mQueue = mLooper.mQueue; // 獲取的是Looper的訊息佇列
}
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue; // 獲取的是Looper的訊息佇列
    mCallback = callback;
    mAsynchronous = async;
}` </pre>

Handler 使用方法

Handler 傳送和處理訊息的幾個方法

  • void handleMessage( Message msg): 處理訊息的方法,該方法通常被重寫。
  • final boolean hasMessage(int what): 檢查訊息佇列中是否包含有what屬性為指定值的訊息
  • final boolean hasMessage(int what ,Object object) : 檢查訊息佇列中是否包含有what好object屬性指定值的訊息
  • sendEmptyMessage(int what): 傳送空訊息
  • final Boolean send EmptyMessageDelayed(int what ,long delayMillis): 指定多少毫秒傳送空訊息
  • final boolean sendMessage(Message msg): 立即傳送訊息
  • final boolean sendMessageDelayed(Message msg,long delayMillis): 多少秒之後傳送訊息

Handler.sendEmptyMessage(int what) 流程解析

獲取一個Message 例項,並立即將 Message 例項新增到訊息佇列中去。 簡要流程如下:

// 立刻傳送一個empty訊息
sendEmptyMessage(int what) 
// 傳送延遲為0的empty訊息  這個方法裡透過Message.obtain()獲取一個Message例項
sendEmptyMessageDelayed(what, 0) 
// 計算訊息的計劃執行時間,進入下一階段
sendMessageDelayed(Message msg, long delayMillis)
// 在這裡判斷佇列是否為null  若為null則直接返回false
sendMessageAtTime(Message msg, long uptimeMillis)
// 將訊息新增到佇列中
enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
// 接下來是MessageQueue新增訊息
// MessageQueue.java
boolean enqueueMessage(Message msg, long when)

可以看到,最後是把message新增到了messageQueue中。

Handler 取消任務

要取消任務時,呼叫下面這個方法 removeCallbacksAndMessages(Object token)

public final void removeCallbacksAndMessages(Object token) {
    mQueue.removeCallbacksAndMessages(this, token);
}

透過呼叫  Message.recycleUnchecked() 方法,取消掉與此 Handler 相關聯的Message。

相關的訊息佇列會執行取消指令

void removeCallbacksAndMessages(Handler h, Object object)

訊息驅動與 Handler

Android是訊息驅動的,實現訊息驅動有幾個要素

  • 訊息的表示:Message
  • 訊息佇列:MessageQueue
  • 訊息迴圈,用於迴圈取出訊息進行處理:Looper
  • 訊息處理,訊息迴圈從訊息佇列中取出訊息後要對訊息進行處理:Handler

初始化訊息佇列

在Looper構造器中即建立了一個MessageQueue,Looper持有訊息佇列的例項。

傳送訊息

透過 Looper.prepare 初始化好訊息佇列後就可以呼叫 Looper.loop 進入訊息迴圈了,然後我們就可以向訊息佇列傳送訊息, 訊息迴圈就會取出訊息進行處理,在看訊息處理之前,先看一下訊息是怎麼被新增到訊息佇列的。

訊息迴圈

Java 層的訊息都儲存在了 Java 層 MessageQueue 的成員 mMessages 中,Native 層的訊息都儲存在了 Native Looper 的 mMessageEnvelopes 中,這就可以說有兩個訊息佇列,而且都是按時間排列的。

Message 和 MessageQueue

與 Handler 工作的幾個元件 Looper、MessageQueue 各自的作用:

  • Handler:它把訊息傳送給 Looper 管理的 MessageQueue ,並負責處理Looper分給它的訊息
  • MessageQueue:管理 Message,由 Looper 管理
  • Looper:每個執行緒只有一個Looper,比如UI執行緒中,系統會預設的初始化一個Looper物件,它負責管理 MessageQueue,不斷的從MessageQueue中取訊息,並將相對應的訊息分給Handler處理。

Message

Message 屬於被傳遞,被使用的角色。Message 是包含描述和任意資料物件的“訊息”,能被髮送給  Handler。Message 包含 2 個 int 屬性和一個額外的物件。 雖然構造器是公開的,但獲取例項最好的辦法是呼叫 Message.obtain()Handler.obtainMessage()。這樣可以從他們的可回收物件池中獲取到訊息例項。一般來說,每個Message例項持有一個Handler。

Message部分屬性值

/*package*/ Handler target; // 指定的Handler
/*package*/ Runnable callback;
// 可以組成連結串列
// sometimes we store linked lists of these things
/*package*/ Message next;

從這裡也不難看出,每個 Message 都持有 Handler 例項。如果 Handler 持有Activity的引用,Activity onDestroy 後 Message 卻仍然在佇列中,因為 Handler 與Activity的強關聯,會造成 Activity 無法被 GC 回收,導致記憶體洩露。 因此在Activity onDestroy 時,與Activity關聯的Handler應清除它的佇列由Activity產生的任務,避免記憶體洩露。

重置自身的方法,將屬性全部重置

public void recycle()
void recycleUnchecked()

獲取 Message 例項的常用方法,得到的例項與傳入的 Handler 繫結

/**
 * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
 * @param h  Handler to assign to the returned Message object's <em>target</em> member.
 * @return A Message object from the global pool.
 */
public static Message obtain(Handler h) {
    Message m = obtain();
    m.target = h;
    return m;
}

將訊息傳送給 Handler

/**
 * Sends this Message to the Handler specified by {@link #getTarget}.
 * Throws a null pointer exception if this field has not been set.
 */
public void sendToTarget() {
    target.sendMessage(this); // target 就是與訊息繫結的Handler
}

呼叫這個方法後,Handler 會將訊息新增進它的訊息佇列 MessageQueue中。

MessageQueue

持有一列可以被 Looper 分發的 Message。一般來說由 Handler 將 Message 新增到 MessageQueue 中。 獲取當前執行緒的 MessageQueue 方法是 Looper.myQueue()。透過  Looper.getMainLooper() 獲取到主執行緒的 looper。

Looper 簡介

Looper 與 MessageQueue 緊密關聯。在一個執行緒中執行的訊息迴圈。執行緒預設情況下是沒有與之管理的訊息迴圈的。 要建立一個訊息迴圈,線上程中呼叫 prepare,然後呼叫 loop。即開始處理訊息,直到迴圈停止。大多數情況下透過Handler來與訊息迴圈互動。

Handler 與 Looper 線上程中互動的典型例子

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare(); // 為當前執行緒準備一個Looper
        // 建立Handler例項,Handler會獲取當前執行緒的Looper
        // 如果例項化Handler時當前執行緒沒有Looper,會報異常 RuntimeException
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop(); // Looper開始執行
    }
}

呼叫了  Looper.loop() 之後,looper 開始執行。當 looper 的 messageQueue 中沒有訊息時,相關的執行緒處於什麼狀態呢? 檢視looper的原始碼,看到loop方法裡面有一個死迴圈。 queue.next()方法是可能會阻塞執行緒的。如果從queue中獲取到null,則表明此訊息佇列正在退出。此時looper的死迴圈也會被返回。

for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }

呼叫 looper 的 quit 方法,實際上呼叫了  mQueue.quit(false)。訊息佇列退出後,looper 的 loop 死迴圈也被退出了。

進入 MessageQueue 的 next方法去看,發現裡面也有一個死迴圈。沒有訊息時,這個死迴圈會阻塞在 nativePollOnce這個方法。

Message next() {
    // ...
    for (;;) {
        // ...
        nativePollOnce(ptr, nextPollTimeoutMillis);
        // 處理message物件

我們知道 Thread 有 New(新建,未執行),RUNNABLE(可執行),BLOCKED,WAITING(執行緒擁有了某個鎖之後, 呼叫了他的wait方法, 等待其他執行緒/鎖擁有者呼叫 notify / notifyAll),TIMED_WAITING,TERMINATED(已經執行完畢)這幾種狀態。

訊息佇列中沒有訊息,在 nativePollOnce方法中“等待”。 nativePollOnce效果上大致等同於 Object.wait(),但它們的實現完全不同。 nativePollOnce使用  epoll, 而  Object.wait 使用  futex

“等待”時,相關執行緒則處於 WAITING狀態。

Looper中的屬性

Looper 持有 MessageQueue;唯一的主執行緒 Looper  sMainLooper;Looper 當前執行緒  mThread; 儲存 Looper 的  sThreadLocal

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;  // guarded by Looper.class
final MessageQueue mQueue; // Handler會獲取這個訊息佇列例項(參考Handler構造器)
final Thread mThread; // Looper當前執行緒

ThreadLocal並不是執行緒,它的作用是可以在每個執行緒中儲存資料。

Looper方法

準備方法,將當前執行緒初始化為 Looper。退出時要呼叫 quit

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例項存入了sThreadLocal
}

prepare方法新建 Looper 並存入 sThreadLocal  sThreadLocal.set(new Looper(quitAllowed))

ThreadLocal<T>

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

當要獲取 Looper 物件時,從  sThreadLocal 獲取

// 獲取與當前執行緒關聯的Looper,返回可以為null
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

在當前執行緒執行一個訊息佇列。結束後要呼叫退出方法 quit()

public static void loop()

準備主執行緒 Looper。Android 環境會建立主執行緒 Looper,開發者不應該自己呼叫這個方法。 UI執行緒,它就是 ActivityThread,ActivityThread 被建立時就會初始化 Looper,這也是在主執行緒中預設可以使用 Handler 的原因。

public static void prepareMainLooper() {
    prepare(false); // 這裡表示了主執行緒Looper不能由開發者來退出
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

獲取主執行緒的 Looper。我們開發者想操作主執行緒時,可呼叫此方法

public static Looper getMainLooper()

同一個 Thread 的不同 Handler

與 UI 執行緒對應的 MainLooper,可以關聯多個 Handler。 多個 Handler 之間的計劃任務不會互相影響。比如有 2 個關聯了 UI 執行緒的 handler。

Handler mMainHandler1;
Handler mMainHandler2;
private void initUtils() {
    mMainHandler1 = new Handler(Looper.getMainLooper());
    mMainHandler2 = new Handler(Looper.getMainLooper());
    Log.d(TAG, "mMainHandler1 post 任務");
    mMainHandler1.postDelayed(new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "mMainHandler1的演示任務已執行 rustfisher");
        }
    }, 1500);
    mMainHandler2.removeCallbacksAndMessages(null);
}

mMainHandler2 取消它的任務並不會影響 mMainHandler1。

Handler 相關面試題

1. ⼦執行緒⼀定不能更新 UI 嗎?

答:不⼀定。

  • Activity存在⼀種審計機制,這個機制會在Activity完全顯示之後⼯作,如果⼦執行緒在Activity完全顯示

之前更新UI是可⾏的;

  • SurfaceView:多媒體影片播放,也可以在⼦執行緒中更新UI
  • Progress:進度相關控制元件,也可以在⼦執行緒中更新UI

2. 給我說說 Handler 的原理

3. Handler 導致的記憶體洩露你是如何解決的?

4. 如何使⽤Handler讓⼦執行緒和⼦執行緒通訊?

  • 傳送訊息的⼦執行緒
    package com.cdc.handler;
    import android.os.Handler;
    import android.os.Message;
    import android.os.SystemClock;
    //傳送訊息的⼦執行緒
    public class Thread1 extends Thread {
         private Handler handler;
         public Thread1(Handler handler){
             super.setName("Thread1");
             this.handler=handler;
         }
         @Override
         public void run() {
             Message msg = Message.obtain();
             msg.what = 1;
             msg.obj = System.currentTimeMillis()+"";
             handler.sendMessage(msg);
             System.out.println((Thread.currentThread().getName() + "----傳送了消
息!" + msg.obj));
            SystemClock.sleep(1000);
    }
}
  • 接收訊息的⼦執行緒
    package com.cdc.handler;
    import android.os.Handler;
    import android.os.Looper;
    //接收訊息的⼦執行緒
    public class Thread2 extends Thread{
        private Handler handler2;
        public Handler getHandler(){//注意哦,在run執⾏之前,返回的是null
            return handler2;
        }
        public Thread2(){
            super.setName("Thread2");
        }
        @Override
        public void run() {
            //在⼦執行緒⾥⾯新建Handler的例項,需要先調⽤Looper.prepare();否則會報
錯:Can't create handler inside thread that has not called Looper.prepare()
            Looper.prepare();
            handler2 = new Handler(){
                public void handleMessage(android.os.Message msg) {
                //這⾥處理訊息
                System.out.println(("收到訊息了:" +
Thread.currentThread().getName() + "----" + msg.obj));
            };
        };
        Looper.loop();
    }
}
  • 調⽤
private Handler myHandler=null;
private Thread2 thread1;
private Thread1 thread2;
@OnClick(R.id.handler3)
public void handler3(){
    thread1=new Thread2();
    thread1.start();
    myHandler=thread1.getHandler();
    while(myHandler==null){
        SystemClock.sleep(100);
        myHandler=thread1.getHandler();
    }
    thread2=new Thread1(myHandler);
    thread2.start();
}

5. HandlerThread是什麼 & 原理 & 使⽤場景?

6. IdleHandler是什麼?

7. ⼀個執行緒能否建立多個Handler,Handler和Looper之間的對應關係?

8 為什麼Android系統不建議⼦執行緒訪問UI? ⾸先,UI控制元件不是執行緒安全的,如果多執行緒併發訪問UI控制元件可能會出現不可預期的狀態 那為什麼系統不對UI控制元件的訪問加上鎖機制呢? 缺點有兩個:

  • 加上鎖機制會讓UI訪問的邏輯變得複雜;
  • 鎖機制會降低UI訪問的效率,因為鎖機制會阻塞某些執行緒的執⾏

鑑於這兩個缺點,最簡單且⾼效的⽅法就是採⽤單執行緒模型來處理UI操作,所以原始碼ViewRootImpl中會有對執行緒的⼀個判斷,程式碼如下: frameworks/base/core/java/android/view/ViewRootImpl.java

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can
touch its views.");
     }
 }

對於開發者來說也不是很麻煩,只是透過 handler 切換⼀下 UI 訪問的執⾏執行緒即可

9. Looper 死迴圈為什麼不會導致應⽤卡死?

10. 使⽤ Handler 的 postDealy 後訊息佇列有什麼變化?


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70008155/viewspace-2844177/,如需轉載,請註明出處,否則將追究法律責任。

相關文章