(轉)Android Handler 使用詳解

原文鏈接:https://segmentfault.com/a/1190000005926314


Handler:是一個消息分發(fā)對象,進行發(fā)送和處理消息,并且其 Runnable 對象與一個線程的 MessageQueue 關聯(lián)。

作用:調度消息,將一個任務切換到某個指定的線程中去執(zhí)行。

為什么需要 Handler?

子線程不允許訪問 UI

假若子線程允許訪問 UI,則在多線程并發(fā)訪問情況下,會使得 UI 控件處于不可預期的狀態(tài)。

傳統(tǒng)解決辦法:加鎖,但會使得UI訪問邏輯變的復雜,其次降低 UI 訪問的效率。

引入 Handler

采用單線程模型處理 UI 操作,通過 Handler 切換到 UI 線程,解決子線程中無法訪問 UI 的問題。


Handler 使用

方式一: post(Runnable)

  • 創(chuàng)建一個工作線程,實現(xiàn) Runnable 接口,實現(xiàn) run 方法,處理耗時操作

  • 創(chuàng)建一個 handler,通過 handler.post/postDelay,投遞創(chuàng)建的 Runnable,在 run 方法中進行更新 UI 操作。

new Thread(new Runnable() {
   @Override
   public void run() {
       /**
          耗時操作
        */
      handler.post(new Runnable() {
          @Override
          public void run() {
              /**
                更新UI
               */
          }
      });
   }
 }).start();

方式二: sendMessage(Message)

  • 創(chuàng)建一個工作線程,繼承 Thread,重新 run 方法,處理耗時操作

  • 創(chuàng)建一個 Message 對象,設置 what 標志及數(shù)據(jù)

  • 通過 sendMessage 進行投遞消息

  • 創(chuàng)建一個handler,重寫 handleMessage 方法,根據(jù) msg.what 信息判斷,接收對應的信息,再在這里更新 UI。


private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {      //判斷標志位
            case 1:
                /**
                 獲取數(shù)據(jù),更新UI
                */
                break;
        }
    }
};
   

public class WorkThread extends Thread {

    @Override
    public void run() {
        super.run();
       /**
         耗時操作
        */
       
        //從全局池中返回一個message實例,避免多次創(chuàng)建message(如new Message)
        Message msg =Message.obtain();  
        msg.obj = data;
        msg.what=1;   //標志消息的標志
        handler.sendMessage(msg);
    }
    
}
   
new WorkThread().start();


Handler 存在的問題

內存方面

Handler 被作為 Activity 引用,如果為非靜態(tài)內部類,則會引用外部類對象。當 Activity finish 時,Handler可能并未執(zhí)行完,從而引起 Activity 的內存泄漏。故而在所有調用 Handler 的地方,都用靜態(tài)內部類。

異常方面

當 Activity finish 時,在 onDestroy 方法中釋放了一些資源。此時 Handler 執(zhí)行到 handlerMessage 方法,但相關資源已經(jīng)被釋放,從而引起空指針的異常。

避免

  • 如果是使用 handlerMessage,則在方法中加try catch。

  • 如果是用 post 方法,則在Runnable方法中加try catch。


Handler 的改進

  • 內存方面:使用靜態(tài)內部類創(chuàng)建 handler 對象,且對 Activity 持有弱引用

  • 異常方面:不加 try catch,而是在 onDestory 中把消息隊列 MessageQueue 中的消息給 remove 掉。
    則使用如下方式創(chuàng)建 handler 對象:


/**
 * 為避免handler造成的內存泄漏
 * 1、使用靜態(tài)的handler,對外部類不保持對象的引用
 * 2、但Handler需要與Activity通信,所以需要增加一個對Activity的弱引用
 */
private static class MyHandler extends Handler {
    private final WeakReference<Activity> mActivityReference;    

    MyHandler(Activity activity) {
        this.mActivityReference = new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        MainActivity activity = (MainActivity) mActivityReference.get();  //獲取弱引用隊列中的activity
        switch (msg.what) {    //獲取消息,更新UI
            case 1:
                byte[] data = (byte[]) msg.obj;
                activity.threadIv.setImageBitmap(activity.getBitmap(data));
                break;
        }
    }
}

并在 onDesotry 中銷毀:

@Override
protected void onDestroy() {
    super.onDestroy();
    //避免activity銷毀時,messageQueue中的消息未處理完;故此時應把對應的message給清除出隊列
    handler.removeCallbacks(postRunnable);   //清除runnable對應的message
    //handler.removeMessage(what)  清除what對應的message
}


Handler 通信機制

handler.png
  • 創(chuàng)建Handler,并采用當前線程的Looper創(chuàng)建消息循環(huán)系統(tǒng);
  • Handler通過sendMessage(Message)或Post(Runnable)發(fā)送消息,調用enqueueMessage把消息插入到消息鏈表中;
  • Looper循環(huán)檢測消息隊列中的消息,若有消息則取出該消息,并調用該消息持有的handler的dispatchMessage方法,回調到創(chuàng)建Handler線程中重寫的handleMessage里執(zhí)行。

Handler 如何關聯(lián) Looper、MessageQueue

1、Handler 發(fā)送消息

上一段很熟悉的代碼:

Message msg =Message.obtain(); 
msg.obj = data;
msg.what=1;   //標志消息的標志
handler.sendMessage(msg);

從sendMessageQueue開始追蹤,函數(shù)調用關系:sendMessage -> sendMessageDelayed ->sendMessageAtTime,在sendMessageAtTime中,攜帶者傳來的message與Handler的mQueue一起通過enqueueMessage進入隊列了。

對于postRunnable而言,通過post投遞該runnable,調用getPostMessage,通過該runnable構造一個message,再通過 sendMessageDelayed投遞,接下來和sendMessage的流程一樣了。

2、消息入隊列

在enqueueMessage中,通過MessageQueue入隊列,并為該message的target賦值為當前的handler對象,記住msg.target很重要,之后Looper取出該消息時,還需要由msg.target.dispatchMessage回調到該handler中處理消息。

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

在MessageQueue中,由Message的消息鏈表進行入隊列

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 {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            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.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

3、Looper 處理消息

再說處理消息之前,先看Looper是如何構建與獲取的:

  • 構造Looper時,構建消息循環(huán)隊列,并獲取當前線程
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
  • 但該函數(shù)是私有的,外界不能直接構造一個Looper,而是通過Looper.prepare來構造的:
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));
}
  • 這里創(chuàng)建Looper,并把Looper對象保存在sThreadLocal中,那sThreadLocal是什么呢?
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

它是一個保存Looper的TheadLocal實例,而ThreadLocal是線程私有的數(shù)據(jù)存儲類,可以來保存線程的Looper對象,這樣Handler就可以通過ThreadLocal來保存于獲取Looper對象了。

  • TheadLocal 如何保存與獲取Looper?
public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}

public T get() {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values != null) {
        Object[] table = values.table;
        int index = hash & values.mask;
        if (this.reference == table[index]) {
            return (T) table[index + 1];
        }
    } else {
       values = initializeValues(currentThread);
    }

    return (T) values.getAfterMiss(this);
}

在 set 中都是通過 values.put 保存當前線程的 Looper 實例,通過 values.getAfterMiss(this)獲取,其中put和getAfterMiss都有key和value,都是由Value對象的table數(shù)組保存的,那么在table數(shù)組里怎么存的呢?

table[index] = key.reference;
table[index + 1] = value;

很顯然在數(shù)組中,前一個保存著ThreadLocal對象引用的索引,后一個存儲傳入的Looper實例。

  • 接下來看Looper在loop中如何處理消息

在loop中,一個循環(huán),通過next取出MessageQueue中的消息

若取出的消息為null,則結束循環(huán),返回。

設置消息為空,可以通過MessageQueue的quit和quitSafely方法通知消息隊列退出。

若取出的消息不為空,則通過msg.target.dispatchMessage回調到handler中去。

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;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

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

        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        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();
    }
}

4、handler處理消息

Looper把消息回調到handler的dispatchMessage中進行消息處理:

  • 若該消息有callback,即通過Post(Runnable)的方式投遞消息,因為在投遞runnable時,把runnable對象賦值給了message的callback。

  • 若handler的mCallback不為空,則交由通過callback創(chuàng)建handler方式去處理。

  • 否則,由最常見創(chuàng)建handler對象的方式,在重寫handlerMessage中處理。

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容