在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忿项,會看到日志拋出了一個異常:
(注:可以看到我們是讓程序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)系 )纪蜒。
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ù)铺呵,簽名分別如下所示:
- publicHandler()
- publicHandler(Callbackcallback)
- publicHandler(Looperlooper)
- 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讥脐,我們有兩種辦法:
- 向Hanlder的構(gòu)造函數(shù)傳入一個Handler.Callback對象,并實現(xiàn)Handler.Callback的handleMessage方法
- 無需向Hanlder的構(gòu)造函數(shù)傳入Handler.Callback對象啼器,但是需要重寫Handler本身的handleMessage方法
也就是說無論哪種方式旬渠,我們都得通過某種方式實現(xiàn)handleMessage方法,這點(diǎn)與Java中對Thread的設(shè)計有異曲同工之處端壳。
下面我們通過一張圖來理解Thread告丢、MessageQuee、looper以及Handler的之間的關(guān)系更哄。
我們可以把傳送帶上的貨物看做是一個個的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類习瑰,然后把它啟動起來绪颖。