Handler源碼分析 - Java層

Handler最常見(jiàn)的使用場(chǎng)景就是下載回調(diào),為了不影響用戶體驗(yàn)Android不支持在主線程中進(jìn)行耗時(shí)時(shí)操作,長(zhǎng)時(shí)間的耗時(shí)操作會(huì)產(chǎn)生ANR異常,而下載無(wú)疑是耗時(shí)操作,所以我們會(huì)在子線程中進(jìn)行下載。但,下載完畢進(jìn)行UI操作卻會(huì)發(fā)生異常,原來(lái)谷歌為了不讓UI的操作出現(xiàn)沖突(線程的不可確定性),所以規(guī)定只能在主線程中進(jìn)行UI操作,可這就尷尬了...即不讓在主線程中進(jìn)行聯(lián)網(wǎng)操作,又不讓在子線程中進(jìn)行UI操作,我們?nèi)绾螌⒏嬖V主線程我們已經(jīng)下載完畢了呢?這時(shí)就要用到Handler了.

Handler的簡(jiǎn)單使用

test

下面是最簡(jiǎn)單的使用.

// 1.創(chuàng)建Handler對(duì)象, 重寫(xiě)方法.
mTestHandler = new TestHandler(this);

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

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

繼承Handler對(duì)象并重寫(xiě)handleMessage(),然后創(chuàng)建Handler對(duì)象,調(diào)用obtainMessagee()方法獲取Message對(duì)象,將數(shù)據(jù)賦予Message,并發(fā)送出去,而發(fā)送的消息會(huì)回調(diào)給HandlerhandleMessage()方法.

在子線程中創(chuàng)建Handler對(duì)象.

剛才我們?cè)谥骶€程中創(chuàng)建了Handler對(duì)象,在子線程中調(diào)用HandlersendMessageDelayed()方法將Message帶到主線程間完成了線程中的通信,那我們能在子線程中創(chuàng)建Handler嗎 ? 答案是不可以。在子線程中創(chuàng)建Handler對(duì)象會(huì)拋出如下異常:

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

異常說(shuō)的很明白它需要調(diào)用Looper.prepare()。我們來(lái)看看源碼,為什么在主線和中可以創(chuàng)建Handler對(duì)象呢?點(diǎn)擊new Handler(),在兩個(gè)參數(shù)的構(gòu)造方法中我們發(fā)現(xiàn)了剛才拋出異常的代碼.

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;

可以看到異常拋出的原因是因?yàn)閙Looper這個(gè)對(duì)象為null,我們來(lái)看看myLooper里面做了什么?
-----------可我們并沒(méi)有在主線程中調(diào)用這個(gè)方法啊?那是因?yàn)樵摲椒ㄔ?strong>ActivityThread類(lèi)里調(diào)用了,我們這里暫時(shí)不看它. 繼續(xù)查看Looper.myLooper()里面的內(nèi)容.

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

myLooper()實(shí)現(xiàn)調(diào)用了ThreadLocal身上的get()方法,它返回一個(gè)Looper,既然有get()必然在一個(gè)地方set()了,于是在Looper中發(fā)現(xiàn)以下代碼

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對(duì)象存放在了ThreadLocal中,當(dāng)需要Looper時(shí)也是從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內(nèi)部有一個(gè)Map集合,根據(jù)它所在的線程名取Looper,而ThreadLocal是在創(chuàng)建Looper時(shí)創(chuàng)建的所以LooperThreadLocal是同一線程的,而我們創(chuàng)建handler時(shí)會(huì)調(diào)用myLooper()方法,該方法調(diào)用get()方法時(shí)會(huì)根據(jù)handler所有線程名取Looper對(duì)象,所以我們要保證Looper.prepare()Handler在同一線程中.

現(xiàn)在讓我們來(lái)理一下思路,首先創(chuàng)建Handler需要Looper,而Looper是在prepare()方法中創(chuàng)建的,也就是說(shuō)如果我們想在子線程中創(chuàng)建Handler只需要在之前調(diào)用

test

Looper.prepare()即可,下面是我們測(cè)試的代碼.

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

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

// 這里我們看到了前面的發(fā)送延時(shí)消息方法,延時(shí)+當(dāng)時(shí)時(shí)間 并調(diào)用下一方法
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進(jìn)行綁定.
  if (mAsynchronous) {
    msg.setAsynchronous(true);
  }
  return queue.enqueueMessage(msg, uptimeMillis);
}

最后調(diào)用了MesageQueneenqueueMessage()方法,即入隊(duì)列,那MesageQuene對(duì)象是在什么時(shí)候創(chuàng)建的呢?是在創(chuàng)建Looper對(duì)象時(shí)創(chuàng)建的。

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

繼續(xù)來(lái)看MesageQueneenqueueMessage()方法.

...
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()就是入隊(duì)列的意思,Message 是單鏈表結(jié)構(gòu),它持有下一個(gè)Message 對(duì)象的引用,因?yàn)?strong>MesageQuene中可能存有多個(gè)未處理的消息,所以需要判斷MesageQuene中有多少消息,若有多個(gè)則將當(dāng)前消息放置在最后,可以看成集合的add方法.

Looper

sendMessage()方法只是將消息加入消息隊(duì)列中,那消息是如何取出并發(fā)給handler的呢?我們來(lái)看下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進(jìn)行了綁定),所以調(diào)用了handler身上的dispatchMessage()方法,而該方法最終又調(diào)用了handleMessage()方法,并將消息傳遞進(jìn)去.

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

上面就是handler發(fā)送消息的大致流程最后我們總結(jié)一下.
總結(jié):創(chuàng)建Handler對(duì)象時(shí)會(huì)調(diào)用Looper.prepare()方法,該方法會(huì)用從ThreadLocal對(duì)象中取出looper, 所以如果ThreadLocal中沒(méi)有先存Looper或不在同一線程中則取不到對(duì)象,Handler就會(huì)拋出異常.

  • 問(wèn)題1.:主線程在哪里調(diào)用了prepare()方法的?見(jiàn)底部代碼.

用handler發(fā)送消息方法時(shí),會(huì)調(diào)用到MesageQuene的方法, 而mQuene是在創(chuàng)建Looper對(duì)象時(shí)創(chuàng)建的, Looper對(duì)象是在調(diào)用prepare()方法時(shí)創(chuàng)建的, 也就是說(shuō)mQuene與Looper是在同一線程.

我們發(fā)送消息時(shí)會(huì)將所發(fā)送的消息加入消息隊(duì)列,而后調(diào)用Looper.loop()方法才能將消息取出并傳送給handler,如果不調(diào)用Looper.loop()則消息無(wú)法取出

  • 問(wèn)題2:為什么在主線中創(chuàng)建可以接收消息?見(jiàn)底部代碼.

最后在子線程中創(chuàng)建handler那handleMessage是在哪個(gè)線程回調(diào)的呢?
這個(gè)問(wèn)題在上面就已經(jīng)說(shuō)了,handleMessage在loop()方法調(diào)用,而loop()與Looper是在同一線程,也就是說(shuō)最終會(huì)在子線程回調(diào).那如何讓回調(diào)在主線程呢?調(diào)用prepareMainLooper()方法,而不是prepare()就可讓回調(diào)在主線程中運(yùn)行.
下面代碼很好回答了第1和第2問(wèn)題.

public static final void main(String[] args) {
....
// 1.主線程創(chuàng)建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();
...
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,672評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,965評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,690評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 56,019評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,188評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,438評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,667評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,845評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,252評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,590評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,384評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容