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)系作者
  • 序言:七十年代末诫惭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子洽故,更是在濱河造成了極大的恐慌贝攒,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,332評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件时甚,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡哈踱,警方通過(guò)查閱死者的電腦和手機(jī)荒适,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)开镣,“玉大人刀诬,你說(shuō)我怎么就攤上這事⌒安疲” “怎么了陕壹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,812評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)树埠。 經(jīng)常有香客問(wèn)我糠馆,道長(zhǎng),這世上最難降的妖魔是什么怎憋? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,607評(píng)論 1 284
  • 正文 為了忘掉前任又碌,我火速辦了婚禮九昧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘毕匀。我一直安慰自己铸鹰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,728評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布皂岔。 她就那樣靜靜地躺著蹋笼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪躁垛。 梳的紋絲不亂的頭發(fā)上姓建,一...
    開(kāi)封第一講書(shū)人閱讀 49,919評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音缤苫,去河邊找鬼速兔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛活玲,可吹牛的內(nèi)容都是我干的涣狗。 我是一名探鬼主播,決...
    沈念sama閱讀 39,071評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼舒憾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼镀钓!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起镀迂,我...
    開(kāi)封第一講書(shū)人閱讀 37,802評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤丁溅,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后探遵,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體窟赏,經(jīng)...
    沈念sama閱讀 44,256評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,576評(píng)論 2 327
  • 正文 我和宋清朗相戀三年箱季,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涯穷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,712評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡藏雏,死狀恐怖拷况,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掘殴,我是刑警寧澤赚瘦,帶...
    沈念sama閱讀 34,389評(píng)論 4 332
  • 正文 年R本政府宣布,位于F島的核電站奏寨,受9級(jí)特大地震影響起意,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜服爷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,032評(píng)論 3 316
  • 文/蒙蒙 一杜恰、第九天 我趴在偏房一處隱蔽的房頂上張望获诈。 院中可真熱鬧,春花似錦心褐、人聲如沸舔涎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)亡嫌。三九已至,卻和暖如春掘而,著一層夾襖步出監(jiān)牢的瞬間挟冠,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工袍睡, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留知染,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,473評(píng)論 2 360
  • 正文 我出身青樓斑胜,卻偏偏與公主長(zhǎng)得像控淡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子止潘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,606評(píng)論 2 350

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