Handler 源碼分析 - Java 層

Handler最常見的使用場景就是下載回調,為了不影響用戶體驗Android不支持在主線程中進行耗時時操作,長時間的耗時操作會產生ANR異常,而下載無疑是耗時操作,所以我們會在子線程中進行下載。但,下載完畢進行UI操作卻會發生異常,原來谷歌為了不讓UI的操作出現沖突(線程的不可確定性),所以規定只能在子線程中進行UI操作,可這就尷尬了...即不讓在主線程中進行聯網操作,又不讓在子線程中進行UI操作,我們如何將告訴主線程我們已經下載完畢了呢?這時就要用到Handler了.

Handler的簡單使用

test

下面是最簡單的使用.

// 1.創建Handler對象, 重寫方法.
mTestHandler = new TestHandler(this);

-----------------------------點擊模擬下載---------------------------------
public void download(View view) {
  text.setText("開始下載...");
  new Thread(){
    @Override
    public void run() {
      // 2.創建消息對象
      Message msg = mTestHandler.obtainMessage();
      msg.obj = "下載完畢";
      msg.arg1 = 1;
      // 3.發送延時消息
      mTestHandler.sendMessageDelayed(msg, 2000);
      }
    }.start();
}
---------------------------創建一個類繼承Handler--------------------------------
 // * 用軟引用的方法持有Activity對象防止內存泄露
private WeakReference<MainActivity> activity ;
  public TestHandler(MainActivity activity){
    this.activity = new WeakReference<>(activity);
}

// 回調方法.
  @Override
  public void handleMessage(Message msg) {
    switch (msg.arg1){
      case 1:
        activity.get().text.setText((String) msg.obj);
      break;
    }
  }

繼承 Handler 對象并重寫 handleMessage() ,然后創建 Handler 對象,調用 obtainMessagee() 方法獲取 Message 對象,將數據賦予 Message ,并發送出去,而發送的消息會回調給 Handler 的 handleMessage() 方法.

在子線程中創建Handler對象.

剛才我們在主線程中創建了 Handler 對象,在子線程中調用 Handler 的 sendMessageDelayed() 方法將 Message 帶到主線程間完成了線程中的通信,那我們能在子線程中創建 Handler 嗎 ? 答案是不可以。在子線程中創建 Handler 對象會拋出如下異常:

Can't create handler inside thread that has not called Looper.prepare()

異常說的很明白它需要調用 Looper.prepare() 。我們來看看源碼,為什么在主線和中可以創建 Handler 對象呢?點擊 new Handler() ,在兩個參數的構造方法中我們發現了剛才拋出異常的代碼.

mLooper = Looper.myLooper();
  if (mLooper == null) {
    throw new RuntimeException(
    "Can't create handler inside thread that has not called Looper.prepare()");
  }
  mQueue = mLooper.mQueue;
  mCallback = callback;
  mAsynchronous = async;

可以看到異常拋出的原因是因為mLooper這個對象為null,我們來看看myLooper里面做了什么?

-----------可我們并沒有在主線程中調用這個方法啊?那是因為該方法在 ActivityThread 類里調用了,我們這里暫時不看它. 繼續查看 Looper.myLooper() 里面的內容.

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

myLooper()實現調用了 ThreadLocal 身上的 get() 方法,它返回一個 Looper ,既然有 get() 必然在一個地方 set() 了,于是在 Looper 中發現以下代碼

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));
}

ThreadLocal

我們把 Looper 對象存放在了 ThreadLocal 中,當需要 Looper 時也是從 ThreadLocal 中取的,為什么要將 Looper 存放在 ThreadLocal 中呢?下面是它的 get() 方法.

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();
}

ThreadLocal內部有一個 Map 集合,根據它所在的線程名取 Looper ,而 ThreadLocal 是在創建 Looper 時創建的所以 LooperThreadLocal 是同一線程的,而我們創建handler時會調用 myLooper() 方法,該方法調用 get() 方法時會根據handler所有線程名取 Looper 對象,所以我們要保證 Looper.prepare() 與 Handler 在同一線程中.

現在讓我們來理一下思路,首先創建 Handler 需要 Looper ,而 Looper 是在 prepare() 方法中創建的,也就是說如果我們想在子線程中創建 Handler 只需要在之前調用

test

``
public void run() {
   Looper.prepare();
   mTestHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
      Log.d("MainActivity", Thread.currentThread().getName());
    }
  };
  mTestHandler.sendMessage(mTestHandler.obtainMessage());
}

MesageQuene

可是問題又來了,雖然沒有拋出異常, handleMessage() 方法卻一直接收不到消息.既然是收不到消息,那我們來看看Handler內部是怎么發送消息的吧,我們繼續查看 sendMessage() 方法.

// 這里我們看到了前面的發送延時消息方法,延時+當時時間 并調用下一方法
sendMessage(msg) -> sendMessageDelayed(msg, 0)
    -> sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)

----------------------------------------------------------------------
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  MessageQueue queue = mQueue;
  if (queue == null) {
     ...
    return false;
  }
  return enqueueMessage(queue, msg, uptimeMillis);
}

----------------------------------------------------------------------
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  msg.target = this; // this = handler; 把消息與handler進行綁定.
  if (mAsynchronous) {
    msg.setAsynchronous(true);
  }
  return queue.enqueueMessage(msg, uptimeMillis);
}

最后調用了 MesageQuene 的 enqueueMessage() 方法,即入隊列,那 MesageQuene 對象是在什么時候創建的呢?是在創建 Looper 對象時創建的。

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

繼續來看 MesageQuene 的 enqueueMessage() 方法.

...
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;
...

enqueueMessage() 就是入隊列的意思, Message 是單鏈表結構,它持有下一個 Message 對象的引用,因為 MesageQuene 中可能存有多個未處理的消息,所以需要判斷 MesageQuene 中有多少消息,若有多個則將當前消息放置在最后,可以看成集合的add方法.

Looper

sendMessage()方法只是將消息加入消息隊列中,那消息是如何取出并發給handler的呢?我們來看下 Looper.loop()

...
for (;;) {
  Message msg = queue.next(); // might block
...
  try {
    msg.target.dispatchMessage(msg);
  } finally {
    if (traceTag != 0) {
    Trace.traceEnd(traceTag);
  }
}
...

這里的target就是handler(在handler的 enqueueMessage() 方法里,我們將消息與handler進行了綁定),所以調用了handler身上的 dispatchMessage() 方法,而該方法最終又調用了 handleMessage() 方法,并將消息傳遞進去.

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

上面就是handler發送消息的大致流程最后我們總結一下.

總結:創建 Handler 對象時會調用 Looper.prepare() 方法,該方法會用從 ThreadLocal 對象中取出looper, 所以如果 ThreadLocal 中沒有先存 Looper 或不在同一線程中則取不到對象, Handler 就會拋出異常.

  • 問題1.:主線程在哪里調用了 prepare() 方法的?見底部代碼.

用handler發送消息方法時,會調用到 MesageQuene 的方法, 而mQuene是在創建 Looper 對象時創建的, Looper 對象是在調用 prepare() 方法時創建的, 也就是說mQuene與 Looper 是在同一線程.

我們發送消息時會將所發送的消息加入消息隊列,而后調用Looper.loop()方法才能將消息取出并傳送給handler,如果不調用Looper.loop()則消息無法取出

  • 問題2:為什么在主線中創建可以接收消息?見底部代碼.

最后在子線程中創建handler那handleMessage是在哪個線程回調的呢?

這個問題在上面就已經說了,handleMessage在loop()方法調用,而loop()與Looper是在同一線程,也就是說最終會在子線程回調.那如何讓回調在主線程呢?調用prepareMainLooper()方法,而不是prepare()就可讓回調在主線程中運行.

下面代碼很好回答了第1和第2問題.

public static final void main(String[] args) {
....
// 1.主線程創建Looper 
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
  sMainThreadHandler = new Handler();
}

ActivityThread thread = new ActivityThread();
thread.attach(false);

if (false) {
  Looper.myLooper().setMessageLogging(new
  LogPrinter(Log.DEBUG, "ActivityThread"));
}

Looper.loop();
...

 

來自:http://www.jianshu.com/p/1bd6e015653f

 

 本文由用戶 icya3508 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!