Android的Handler消息機制

Handler

一套Android的消息傳遞機制/異步通信機制江掩。在多線程的應(yīng)用場景中逗抑,將工作線程中需要更新UI的操作信息傳遞到UI主線程,從而實現(xiàn)工作線程對UI的更新處理蝴蜓,最終實現(xiàn)異步消息的處理仅颇。

  • 消息

Message单默,是線程間通訊的數(shù)據(jù)單元(即Hnadler接受&處理的消息對象)。存儲需要操作的通信信息忘瓦。

  • 消息隊列

MessageQueue搁廓,一種數(shù)據(jù)結(jié)構(gòu)(存儲特點:先進(jìn)先出)。存儲Handler發(fā)送過來的消息(Message)耕皮。

  • 處理者

Handler境蜕,是主線程與子線程的通信媒介,也是線程消息的主要處理者明场。添加消息(Message)到消息隊列汽摹、處理循環(huán)器(Looper)分派過來的消息(Message)。

  • 循環(huán)器

Looper苦锨,是消息隊列(MessageQueue)與處理者(Handler)的通信媒介。消息循環(huán)即(循環(huán)取出消息隊列的消息)趴泌、消息分發(fā)(將取出的消息發(fā)送給對應(yīng)的處理者)

  • 系統(tǒng)為什么不允許在子線程中去訪問UI

這是因為Android的UI線程是不安全的舟舒,多線程并發(fā)訪問可能導(dǎo)致UI控件處于不可預(yù)期的狀態(tài)。

  • 為什么不加鎖呢

缺點有兩個:首先加上鎖機制會讓UI控件的訪問邏輯變得復(fù)雜嗜憔,其次鎖機制會降低UI的訪問效率秃励,鎖會堵塞默寫線程的執(zhí)行。

Handler創(chuàng)建時會采用當(dāng)前線程的Looper來構(gòu)建內(nèi)部消息循環(huán)系統(tǒng)吉捶,如果當(dāng)前線程沒有Looper就會報錯夺鲜。在當(dāng)前線程創(chuàng)建一個Looper皆尔,或者在有Looper的線程中創(chuàng)建Handler。

Handler通過post方法將一個Runnable投遞到Handler內(nèi)部中的Looper中去處理币励,也可以通過Handler的send方法發(fā)送一個消息慷蠕,這個消息同樣會在Looper中處理。(其實post方法最終也是由send方法完成的)食呻。
當(dāng)Handler的send方法被調(diào)用時流炕,它會調(diào)用MessageQueue的enqueueMessage方法將消息放入消息隊列中,然后Looper發(fā)現(xiàn)有新消息到來時就會處理這個消息仅胞,最終消息中的Runnable或者Handler的handMessage方法就會被調(diào)用每辟。
注意Looper是運行在創(chuàng)建Handler的線程中的,這樣一來Handler中的業(yè)務(wù)邏輯就被切換到創(chuàng)建Handler的線程中去了干旧。

handler.png

ThreadLocal的工作原理

ThreadLocal是一個線程內(nèi)部的數(shù)據(jù)存儲類渠欺,通過它可以在指定線程中存儲數(shù)據(jù),數(shù)據(jù)存儲之后椎眯,值可以在指定的線程中獲取到存儲數(shù)據(jù)峻堰,其他線程中獲取不到。

Looper盅视、ActivityThread以及AMS中都用到了ThreadLocal捐名。
Looper另一個使用場景是復(fù)雜邏輯下的對象傳遞,比如監(jiān)聽器的傳遞闹击。它可以讓監(jiān)聽器在線程內(nèi)作為一個全局對象的存镶蹋。

  • 不同線程訪問同一個ThreadLocal的get方法,ThreadLocal內(nèi)部會從各自線程中取出一個數(shù)組赏半,然后再從數(shù)組中根據(jù)當(dāng)前ThreadLocal的索引去查找對應(yīng)的value值贺归。
  • ThreadLocal的set方法
public void set(T value){
  Thread currentThread = Thread.currentThread();
  Values value = values(currentThread);
  if(values == null){
    values = initalizeValues(currentThread);
  }
  values.put(this,value);
}

首先通過values方法去獲取當(dāng)前線程中ThreadLocal中的數(shù)據(jù)。在Thread類的內(nèi)部有一個成員專門用于存儲ThreadLocal中的數(shù)據(jù):ThreadValues.localValues断箫。在localValues內(nèi)部有一個table數(shù)組:private Object[] table拂酣,ThreadLocal的值就是存在在這個table數(shù)組中的。

ThreadLocal的值在table數(shù)組的存儲位置總是ThreadLocal的reference字段所標(biāo)識的對象的下一個位置仲义,比如ThreadLocal的reference對象在table數(shù)據(jù)中的索引是index婶熬,那么ThreadLocal的值在table數(shù)組中的索引就是index+1,table[index+1]=value埃撵。

  • ThreadLocal的set方法
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 = initalizeValues(currentThread);
  }
return (T)values.getAfterMiss(this);
}

從ThreadLocal的set/get方法中可以看出赵颅,他們操作的對象都是當(dāng)前線程中l(wèi)ocalValues的table數(shù)組,因此不同線程訪問同一個ThreadLocal的set和get方法暂刘,他們對ThreadLocal所做的讀寫/寫入操作僅限于各自線程內(nèi)部饺谬。這就是為什么ThreadLocal可以在多個線程中互補干擾的存儲和獲取數(shù)據(jù)。

MessageQueue的工作原理

MessageQueuq雖然叫做消息隊列谣拣,但實際是通過一個單鏈表結(jié)構(gòu)來維護(hù)的消息隊列募寨,單鏈表在插入和刪除上有優(yōu)勢

  • enqueueMessage(插入操作)

就是單鏈表的插入操作

  • next(讀取消息族展,并從消息隊列中刪除)

next方法是一個無線循環(huán)的方法,如果消息隊列中沒有消息拔鹰,next方法就會一直堵塞在這里仪缸,當(dāng)有新消息是,next方法會返回這條消息并將從單鏈表中移除格郁。

Looper的工作原理

Looper在Android消息機制中扮演著消息循環(huán)的角色腹殿,具體來說就是它會不停地從MessageQueue中查看是否有新消息,如果有新消息就會立即處理例书,否則會一直堵塞在那里锣尉。

  • 構(gòu)造方法
private Looper(boolean quitAllowed){
  //創(chuàng)建消息隊列
  mQueue = new MessageQueue(quitAllowed);
  //保存當(dāng)前線程對象
  mThread = Thread.currentThread();
}
  • Looper的創(chuàng)建
new Thread("Thread#2"){
  @Override
  public void run(){
      Looper.prepare();
      Handler handler = new Handler();
      Looper.loop();//開啟消息循環(huán)
  };
}.start();

Looper除了prepare方法外,還提供prepareMainLooper方法决采,這個方法主要是給主線程也就數(shù)ActivityThread創(chuàng)建Looper使用自沧,其本質(zhì)也是通過prepare實現(xiàn)的。
由于主線程Looper比較特殊树瞭,所以Looper提供一個getMainLooper方法拇厢,通過它可以在任何地方獲取主線程的Looper

  • Looper的退出

quit直接退出Looper
quitSafely設(shè)定一個退出標(biāo)記,然后把消息隊列中已有消息處理完后安全退出晒喷。

Looper退出后通過Handler發(fā)送消息就是失敗孝偎,這個時候Handler的send方法返回就是false。

在子線程中凉敲,如果手動創(chuàng)建了Looper衣盾,那么在所有的事情處理完成后應(yīng)立即調(diào)用quit方法來終止消息循環(huán),否則在這個子線程就會一直處于等待的狀態(tài)爷抓。

  • Looper中的loop方法
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;
Binder.clearCallingIdentity();
final long ident=Binder.clearCallingIdentity();

for(;;){
    Message msg=queue.next();
    if(msg==null){
        //沒有消息势决,退出死循環(huán)
        return;
    }

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

    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.getCalss().getName()+" "
        +msg.callback+" what="+msg.what);
    }
    msg.recycleUnchecked();
}
}

Hnadler的工作原理

Hnadler的工作主要包含消息的發(fā)送和接收過程蓝撇。通過post或send來實現(xiàn)果复。post最終也是send實現(xiàn)的。

  • send方法
public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg,0):
}

public final boolean sendMessageDelayed(Message msg,long delayMillis){
    if(delayMillis<0){
        delayMillis=0;
    }
    return sendMessageAtTime(msg,SystemClock.uptimeMillis()+delayMillis);

}

public boolean sendMessageAtTime(Message msg,long uptimeMillis){
    MessageQueue queue=mQueue;
    if(queue==null){
        RuntimeException e=new RuntimeException(this+"sendMessageAtTime() called with no mQueue");
        Log.w("Looper",e.getMessage(),e);
        return false;
    }
    return enqueueMessage(queue,msg,uptimeMillis);
}

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

可以發(fā)現(xiàn)Handler發(fā)送消息過程僅僅是向消息隊列中插入一條消息渤昌,MessageQueue的next方法就會返回一條消息給Looper虽抄,Looper收到消息后開始處理,最終消息由Looper交由Handler處理耘沼,即Handler的dispatchMessage方法被調(diào)用极颓,這是Handler進(jìn)入消息處理階段。

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

主線程的消息循環(huán)

Android的主線程就是ActivityThread群嗤,主線程的入口方法是main,在main方法中系統(tǒng)會通過Looper.prepareMainLooper()方法來創(chuàng)建主線程的Looper和MessageQueue兵琳,并通過Looper.loop()來開啟主線程的消息循環(huán)狂秘。

  • main方法
public static void main(String[] args){
  ...
  Process.setArgV0("<pre-initialized>");

  Looper.prepareMainLooper();

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

  if(sMainThreadHandler==null){
      sMainThreadHandler==thread.getHandler();
  }

  AsyncTask.init();

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

  Looper.loop();

  throws new RuntimeException("Main thread loop unexpectedly exited");
}

主線程消息循環(huán)開啟后骇径,ActivityThread還需要一個Handler來和消息隊列進(jìn)行交互,這個Handler就是ActivityThread.H者春,它內(nèi)部定義了一組消息模型破衔,主要包含四大組件的啟動和停止過程。
ActivityThread通過applicationThread和AMS進(jìn)行進(jìn)程間通信钱烟,AMS以進(jìn)程間通信的方式完成ActivityThread的請求后會回調(diào)ApplicationThread中Binder方法晰筛,然后ApplicationThread會向H發(fā)送消息,H收到消息后會將ApplicationThread中的邏輯切換到ActivityThread中執(zhí)行拴袭,即切換到主線程中去執(zhí)行读第,這個過程就是主線程中的消息模型。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拥刻,一起剝皮案震驚了整個濱河市怜瞒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌般哼,老刑警劉巖吴汪,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蒸眠,居然都是意外死亡漾橙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門楞卡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霜运,“玉大人,你說我怎么就攤上這事臀晃【蹩剩” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵徽惋,是天一觀的道長案淋。 經(jīng)常有香客問我,道長险绘,這世上最難降的妖魔是什么踢京? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮宦棺,結(jié)果婚禮上瓣距,老公的妹妹穿的比我還像新娘。我一直安慰自己代咸,他們只是感情好蹈丸,可當(dāng)我...
    茶點故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般逻杖。 火紅的嫁衣襯著肌膚如雪奋岁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天荸百,我揣著相機與錄音闻伶,去河邊找鬼。 笑死够话,一個胖子當(dāng)著我的面吹牛蓝翰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播女嘲,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼畜份,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了澡为?” 一聲冷哼從身側(cè)響起漂坏,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎媒至,沒想到半個月后顶别,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡拒啰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年驯绎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谋旦。...
    茶點故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡剩失,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出册着,到底是詐尸還是另有隱情拴孤,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布甲捏,位于F島的核電站演熟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏司顿。R本人自食惡果不足惜芒粹,卻給世界環(huán)境...
    茶點故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望大溜。 院中可真熱鬧化漆,春花似錦、人聲如沸钦奋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至疙教,卻和暖如春棺聊,著一層夾襖步出監(jiān)牢的瞬間伞租,已是汗流浹背贞谓。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留葵诈,地道東北人裸弦。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像作喘,于是被迫代替她去往敵國和親理疙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,515評論 2 359

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