Handler學(xué)習(xí)

在android開發(fā)中handler是我們用到的最多的類之一,在與應(yīng)用程序交互時台舱,比如從網(wǎng)上下載的圖片如何更新到UI界面上呢竞惋?這時就會用到Handler機(jī)制拆宛,那如果不用到handler機(jī)制浑厚,或者在子線程中直接將圖片更新到我們的UI當(dāng)中钳幅,整個android當(dāng)中就會給我們拋出一個異常,告訴我們不能在一個非ui線程中去直接更新ui诬乞。

在最初的學(xué)習(xí)android中震嫉,我們只知道在子線程中簡單的發(fā)送一個sendMessage(Message msg)牡属,在發(fā)送一個消息之后湃望,它會自動回調(diào)我們這個handler的handleMessage(Message msg)方法痰驱,那么為什么一旦發(fā)送消息之后废士,會自動回調(diào)到handleMessage方法呢?還有我們都知道handler經(jīng)常與Looper和Message Quee經(jīng)常關(guān)聯(lián)丽旅,那么他們?nèi)咧g的關(guān)系又是什么呢,還有等等諸多的問題可能會困擾初學(xué)者。

一孙援、handler是什么扇雕?
注:線程分為主線程(主線程又叫UI線程镶奉,只能有一個主線程)和子線程(可以有多個)Handler只能在主線程里運(yùn)行础淤。

handler是android給我們提供用來更新UI的一套機(jī)制,也是一套消息處理的機(jī)制值骇,我們可以發(fā)送消息,也可以通過它處理消息吱瘩。

二、為什么要用handler使碾?

Android在設(shè)計的時候蜜徽,就封裝了一套消息創(chuàng)建、傳遞票摇、處理機(jī)制,如果不遵循這樣的機(jī)制,就沒有辦法更新UI信息祟剔,就會拋出異常信息。

三叛薯、handler的用法。

首先先用代碼嘗試下在子線程中來更新UI,看一下具體的異常信息抖拴。
布局文件如下:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="[http://schemas.android.com/apk/res/android"](http://schemas.android.com/apk/res/android%22);

   android:orientation="vertical"

   android:layout_width="match_parent"

   android:layout_height="match_parent">

  <TextView

     android:id="@+id/id_textview"

     android:layout_width="wrap_content"

     android:layout_height="wrap_content" />

</LinearLayout>

在子線程中來給TextView賦值操作:

private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_handler01);
     textView = findViewById(R.id.id_textview);
     //開辟一個子線程
     new Thread(){
      @Override
      public void run() {
          super.run();
      try {
            Thread.sleep(1000);
            textView.setText("update text");//在子線程給TextView賦值
         }catch (Exception e){
           e.printStackTrace();
       }
    }
  }.start();
}

運(yùn)行會發(fā)現(xiàn)程序沒有更新TextView忿项,會看到日志拋出了一個異常:

image.png
這個異常就是告訴我們程序在非U主線程(UI線程)中進(jìn)行去更新UI了。android是不允許這種機(jī)制去更新界面的城舞。這個異常是從android.view.ViewRootImpl的checkThread方法拋出的轩触,ViewRootImpl是ViewRoot的實現(xiàn)類。
:可以看到我們是讓程序sleep()了一秒后開始更新UI的家夺,如果沒有sleep直接在run方法中給textview賦值操作脱柱,可以賦值,不會拋出異常拉馋,是因為ViewRootImpl(ViewRootImpl是在WindowManagerGlobal的addView方法中創(chuàng)建的榨为。)的創(chuàng)建在onResume方法回調(diào)之后惨好,而我們一開篇是在onCreate方法中創(chuàng)建了子線程并訪問UI,在那個時刻随闺,ViewRootImpl是沒有創(chuàng)建的日川,無法檢測當(dāng)前線程是否是UI線程,所以程序沒有崩潰一樣能跑起來矩乐,而之后修改了程序龄句,讓線程休眠了1秒后,程序就崩了散罕。很明顯1秒后ViewRootImpl已經(jīng)創(chuàng)建了分歇,可以執(zhí)行checkThread方法檢查當(dāng)前線程。在onCreate方法中創(chuàng)建的子線程訪問UI是一種極端的情況欧漱,這個不仔細(xì)分析源碼是不知道的职抡。)

通過上面的程序我們可以看出在子線程中是不允許更新UI的,這時候我們就要用到handler了误甚,修改程序如下:

private TextView textView;
//創(chuàng)建handler對象
private Handler handler=new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler01);
textView = findViewById(R.id.id_textview);
new Thread(){
@Override
public void run() {
super.run();
try {
Thread.sleep(1000);
//利用handler發(fā)送一個消息
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("update text");
}
});

}catch (Exception e){
e.printStackTrace();
}
}
}.start();
}

handler的基本用法:
handler.post(Runnable r)
handler.postDelayed(Runnable r, long delayMillis)
handler.sendMessage(Message msg)
handler.sendEmptyMessage(int what)

我們利用handler.postDelayed()缚甩,每隔一秒來更新ImageView的圖片。

private ImageView imageView;
private Handler handler = new Handler();
private int images[] = {R.drawable.image1, R.drawable.image2, R.drawable.image3};
//創(chuàng)建一個索引
private int index;
private MyRunnable myRunnable=new MyRunnable();
//創(chuàng)建一個Runnable 并實現(xiàn)run方法
class MyRunnable implements Runnable{
@Override
public void run() {
index++;
index=index%3;
//設(shè)置圖片
imageView.setImageResource(images[index]);
//在runnable中不斷去輪詢這個圖片 每隔一秒去執(zhí)行這個操作
handler.postDelayed(myRunnable,1000);
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler01);
imageView = findViewById(R.id.ivImage);
handler.postDelayed(myRunnable,1000);
}

其他用法就不一一舉例說明了窑邦。我們在new handler()的時候發(fā)現(xiàn)handler的構(gòu)造還有一個傳遞Callback的方法Handler(Callback callback)擅威,callback回調(diào)里有一個返回值為boolean類型handleMessage方法。這個方法和handler方法體里面的void類型的handleMessage是有一定關(guān)系的奕翔,當(dāng)Handler.Callback里的handleMessage返回值是false的時候裕寨,msg會被傳遞到handler方法體里面的void類型的handleMessage中浩蓉,如果返回true派继,則代表被攔截了,就不會讓msg傳遞下去捻艳。

private Handler handler = new Handler(new Handler.Callback() {
     @Override
     public boolean handleMessage(Message msg) {
           //返回true后  消息將會被攔截不會傳到下面的handleMessage里   返回false后不會被攔截
             return false;
      }
  }){
    @Override
     public void handleMessage(Message msg) {
     super.handleMessage(msg);
      }
};

handler不只是可以發(fā)送消息驾窟,還可以移除一個消息,用到的方式是 handler.removeCallbacks(Runnable r);傳遞的參數(shù)是對應(yīng)的創(chuàng)建的Runnable對象认轨。

四绅络、handler的原理是什么?(Handler嘁字、Looper恩急、MessageQueen、Message的關(guān)系 )纪蜒。

image.png
Thread是最基礎(chǔ)的衷恭,Looper和MessageQueue都構(gòu)建在Thread之上,Handler又構(gòu)建在Looper和MessageQueue之上纯续。

Looper(輪詢器):
負(fù)責(zé)讀取MessageQueen中的消息随珠,讀到消息之后就把消息交給Handler去處理灭袁。消息隊列MessageQueue只是存儲Message的地方,真正讓消息隊列循環(huán)起來的是Looper窗看,這就好比消息隊列MessageQueue是個水車茸歧,那么Looper就是讓水車轉(zhuǎn)動起來的河水。Looper.Loop方法显沈,就是一個死循環(huán)软瞎,不斷的從MessageQuee取消息,如果有消息就處理构罗,沒有消息就阻塞铜涉。

MessageQuee:

最基礎(chǔ)最底層的是Thread,每個線程內(nèi)部都維護(hù)了一個消息隊列——MessageQueue遂唧,消息隊列MessageQueue芙代,顧名思義,就是存放消息的隊列盖彭。
Message:
Handler接收和處理的消息對象

Handler:
Handler是暴露給開發(fā)者最頂層的一個類纹烹,其構(gòu)建在Thread、Looper與MessageQueue之上召边。
Handler具有多個構(gòu)造函數(shù)铺呵,簽名分別如下所示:

  1. publicHandler()
  2. publicHandler(Callbackcallback)
  3. publicHandler(Looperlooper)
  4. publicHandler(Looperlooper, Callbackcallback)

第1個和第2個構(gòu)造函數(shù)都沒有傳遞Looper,這兩個構(gòu)造函數(shù)都將通過調(diào)用Looper.myLooper()獲取當(dāng)前線程綁定的Looper對象隧熙,然后將該Looper對象保存到名為mLooper的成員字段中片挂。

第3個和第4個構(gòu)造函數(shù)傳遞了Looper對象,這兩個構(gòu)造函數(shù)會將該Looper保存到名為mLooper的成員字段中贞盯。

第2個和第4個構(gòu)造函數(shù)還傳遞了Callback對象音念,Callback是Handler中的內(nèi)部接口,需要實現(xiàn)其內(nèi)部的handleMessage方法躏敢,Callback代碼如下:

public interface Callback {

public boolean handleMessage(Message msg); 
 
}

Handler.Callback是用來處理Message的一種手段闷愤,如果沒有傳遞該參數(shù),那么就應(yīng)該重寫Handler的handleMessage方法件余,也就是說為了使得Handler能夠處理Message讥脐,我們有兩種辦法:

  1. 向Hanlder的構(gòu)造函數(shù)傳入一個Handler.Callback對象,并實現(xiàn)Handler.Callback的handleMessage方法
  2. 無需向Hanlder的構(gòu)造函數(shù)傳入Handler.Callback對象啼器,但是需要重寫Handler本身的handleMessage方法
    也就是說無論哪種方式旬渠,我們都得通過某種方式實現(xiàn)handleMessage方法,這點(diǎn)與Java中對Thread的設(shè)計有異曲同工之處端壳。

下面我們通過一張圖來理解Thread告丢、MessageQuee、looper以及Handler的之間的關(guān)系更哄。


image.png

我們可以把傳送帶上的貨物看做是一個個的Message芋齿,而承載這些貨物的傳送帶就是裝載Message的消息隊列MessageQueue腥寇。傳送帶是靠發(fā)送機(jī)滾輪帶動起來轉(zhuǎn)動的,我們可以把發(fā)送機(jī)滾輪看做是Looper觅捆,而發(fā)動機(jī)的轉(zhuǎn)動是需要電源的赦役,我們可以把電源看做是線程Thread,所有的消息循環(huán)的一切操作都是基于某個線程的栅炒。一切準(zhǔn)備就緒掂摔,我們只需要按下電源開關(guān)發(fā)動機(jī)就會轉(zhuǎn)動起來,這個開關(guān)就是Looper的loop方法赢赊,當(dāng)我們按下開關(guān)的時候乙漓,我們就相當(dāng)于執(zhí)行了Looper的loop方法,此時Looper就會驅(qū)動著消息隊列循環(huán)起來释移。

那Hanlder在傳送帶模型中相當(dāng)于什么呢叭披?
我們可以將Handler看做是放入貨物以及取走貨物的管道:貨物從一端順著管道劃入傳送帶,貨物又從另一端順著管道劃出傳送帶玩讳。我們在傳送帶的一端放入貨物的操作就相當(dāng)于我們調(diào)用了Handler的sendMessageXXX涩蜘、sendEmptyMessageXXX或postXXX方法,這就把Message對象放入到了消息隊列MessageQueue中了熏纯。當(dāng)貨物從傳送帶的另一端順著管道劃出時同诫,我們就相當(dāng)于調(diào)用了Hanlder的dispatchMessage方法,在該方法中我們完成對Message的處理樟澜。

補(bǔ)充:Thread就是ActivityThread误窖,默認(rèn)整個應(yīng)用程序是通過ActivityThread創(chuàng)建的,是Android應(yīng)用的主線程(UI線程)秩贰,說起ActivityThread霹俺,不得不提到Activity的創(chuàng)建、啟動過程以及ActivityManagerService萍膛, 以下引用自羅升陽大師的博客:《Android應(yīng)用程序的Activity啟動過程簡要介紹和學(xué)習(xí)計劃》

Step 1. 無論是通過Launcher來啟動Activity吭服,還是通過Activity內(nèi)部調(diào)用startActivity接口來啟動新的Activity嚷堡,都通過Binder進(jìn)程間通信進(jìn)入到ActivityManagerService進(jìn)程中蝗罗,并且調(diào)用ActivityManagerService.startActivity接口;

Step 2. ActivityManagerService調(diào)用ActivityStack.startActivityMayWait來做準(zhǔn)備要啟動的Activity的相關(guān)信息蝌戒;

Step 3. ActivityStack通知ApplicationThread要進(jìn)行Activity啟動調(diào)度了串塑,這里的ApplicationThread代表的是調(diào)用ActivityManagerService.startActivity接口的進(jìn)程,對于通過點(diǎn)擊應(yīng)用程序圖標(biāo)的情景來說北苟,這個進(jìn)程就是Launcher了桩匪,而對于通過在Activity內(nèi)部調(diào)用startActivity的情景來說,這個進(jìn)程就是這個Activity所在的進(jìn)程了友鼻;

Step 4. ApplicationThread不執(zhí)行真正的啟動操作傻昙,它通過調(diào)用ActivityManagerService.activityPaused接口進(jìn)入到ActivityManagerService進(jìn)程中闺骚,看看是否需要創(chuàng)建新的進(jìn)程來啟動Activity;

Step 5. 對于通過點(diǎn)擊應(yīng)用程序圖標(biāo)來啟動Activity的情景來說妆档,ActivityManagerService在這一步中僻爽,會調(diào)用startProcessLocked來創(chuàng)建一個新的進(jìn)程,而對于通過在Activity內(nèi)部調(diào)用startActivity來啟動新的Activity來說贾惦,這一步是不需要執(zhí)行的胸梆,因為新的Activity就在原來的Activity所在的進(jìn)程中進(jìn)行啟動;

Step 6. ActivityManagerServic調(diào)用ApplicationThread.scheduleLaunchActivity接口须板,通知相應(yīng)的進(jìn)程執(zhí)行啟動Activity的操作碰镜;

Step 7. ApplicationThread把這個啟動Activity的操作轉(zhuǎn)發(fā)給ActivityThread,ActivityThread通過ClassLoader導(dǎo)入相應(yīng)的Activity類习瑰,然后把它啟動起來绪颖。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市甜奄,隨后出現(xiàn)的幾起案子菠发,更是在濱河造成了極大的恐慌,老刑警劉巖贺嫂,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滓鸠,死亡現(xiàn)場離奇詭異,居然都是意外死亡第喳,警方通過查閱死者的電腦和手機(jī)糜俗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來曲饱,“玉大人悠抹,你說我怎么就攤上這事±┑恚” “怎么了楔敌?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長驻谆。 經(jīng)常有香客問我卵凑,道長,這世上最難降的妖魔是什么胜臊? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任勺卢,我火速辦了婚禮,結(jié)果婚禮上象对,老公的妹妹穿的比我還像新娘黑忱。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布甫煞。 她就那樣靜靜地躺著菇曲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抚吠。 梳的紋絲不亂的頭發(fā)上羊娃,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音埃跷,去河邊找鬼蕊玷。 笑死,一個胖子當(dāng)著我的面吹牛弥雹,可吹牛的內(nèi)容都是我干的垃帅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼剪勿,長吁一口氣:“原來是場噩夢啊……” “哼贸诚!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起厕吉,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤酱固,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后头朱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體运悲,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年项钮,在試婚紗的時候發(fā)現(xiàn)自己被綠了班眯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡烁巫,死狀恐怖署隘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情亚隙,我是刑警寧澤磁餐,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站阿弃,受9級特大地震影響诊霹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恤浪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一畅哑、第九天 我趴在偏房一處隱蔽的房頂上張望肴楷。 院中可真熱鬧水由,春花似錦、人聲如沸赛蔫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鞠值,卻和暖如春媚创,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背彤恶。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工钞钙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人声离。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓芒炼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親术徊。 傳聞我的和親對象是個殘疾皇子本刽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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