本文包含與Handler有關(guān)的以下問題
1.Handler的作用
2.為什么Android中要設(shè)計為只能在UI線程中去更新UI呢?
3.Handler的兩個異常
4.Handler口锭、Looper MessageQueue之間的關(guān)系(源碼角度)
5.跟線程相關(guān)的Handler拜效,即HandlerThread(源碼角度分析)
6.主線程往子線程發(fā)消息
7.子線程往子線程發(fā)消息
8.在同一線程中android.Handler和android.MessaegQueue的數(shù)量對應(yīng)關(guān)系是怎樣的壤靶?
一比然、Handler的作用
1.在非UI線程中完成耗時操作坷备,在UI線程中去更新UI。
2.可以在主線程中發(fā)送延時消息。
二谐岁、為什么Android中要設(shè)計為只能在UI線程中去更新UI
1.解決多線程并發(fā)問題(根本原因)
2.提高界面更新的性能問題
3.架構(gòu)設(shè)計的簡單
你可能會說墨礁,既然是擔(dān)心多線程并發(fā)問題幢竹,那我在子線程中加鎖進行更新UI行不行呢?你這樣想的話恩静,會容易造成UI卡頓的焕毫,而且性能也不好。
注1:大部分面試者很難去說出一個令面試官滿意的答案驶乾。
注2:關(guān)于多線程咬荷,這里舉一個例子,比如說銀行取款的問題轻掩。正常情況下幸乒,銀行卡余額不能少于取款金額,如果多線程進行取款的話唇牧,就會造成線程不安全罕扎。
注3:Android中之所以說架構(gòu)簡單,是因為幫我們封裝了很多更新UI的操作丐重。
三腔召、Handler的兩個異常
在使用Handler時,經(jīng)常會出現(xiàn)以下兩個異常:
1.CalledFromWrongThreadException:這種異常是因為嘗試在子線程中去更新UI扮惦,進而產(chǎn)生異常臀蛛。
2.Can't create handle inside thread that ha not called Looper.prepared:是因為我們在子線程中去創(chuàng)建Handler,沒有調(diào)用Looper.prepared而產(chǎn)生的異常崖蜜。
四浊仆、Handler、Looper MessageQueue之間的關(guān)系(源碼角度)
原理分析:
Handler是Android類庫提供的用于發(fā)送豫领、處理消息或Runnable對象的處理類抡柿,它結(jié)合Message、MessageQueue和Looper類以及當(dāng)前線程實現(xiàn)了一個消息循環(huán)機制等恐,用于實現(xiàn)任務(wù)的異步加載和處理洲劣。整個異步消息處理流程的示意圖如下圖所示:
根據(jù)上面的圖片,我們現(xiàn)在來解析一下異步消息處理機制:
Message:消息體课蔬,用于裝載需要發(fā)送的對象囱稽。
Handler:它直接繼承自O(shè)bject。作用是:在子線程中發(fā)送Message或者Runnable對象到MessageQueue中二跋;在UI線程中接收战惊、處理從MessageQueue分發(fā)出來的Message或者Runnable對象。發(fā)送消息一般使用Handler的sendMessage()方法同欠,而發(fā)出去的消息經(jīng)過處理后最終會傳遞到Handler的handlerMessage()方法中样傍。
MessageQueue:用于存放Message或Runnable對象的消息隊列横缔。它由對應(yīng)的Looper對象創(chuàng)建,并由Looper對象管理衫哥。每個線程中都只會有一個MessageQueue對象茎刚。
Looper:是每個線程中的MessageQueue的管家,負(fù)責(zé)接收和分發(fā)Message或Runnable的工作撤逢。調(diào)用Looper.loop()方法膛锭,就是一個死循環(huán),不斷地從MessageQueue中取消息:如果有消息蚊荣,就取出初狰,并調(diào)用Handler的handlerMessage()方法;如果沒有消息阻塞互例。
現(xiàn)在可以做出如下總結(jié):
(1)Handler負(fù)責(zé)發(fā)送消息奢入,Looper負(fù)責(zé)接收Handler發(fā)送的消息放到MessageQueue,Looper又將消息回傳給Handler自己媳叨。
(2)一個Handler對應(yīng)一個Looper對象腥光,一個Looper對應(yīng)一個MessageQueue對象(Looper內(nèi)部包含一個MessageQueue),一個Handler可以生成多個Message糊秆。
(3)Handler就是公開給外部線程的接口武福,用于線程間的通信。Looper是由系統(tǒng)支持的用于創(chuàng)建和管理MessageQueue的依附于一個線程的循環(huán)處理對象痘番,而Handler是用于操作線程內(nèi)部的消息隊列的捉片,所以 Handler也必須依附一個線程,而且只能是一個線程汞舱。
(4)由于Handler是在主線程中創(chuàng)建的伍纫,所以此時handleMessage()方法中的代碼也會在主線程中運行,于是我們在這里就可以安心地進行UI操作了
五兵拢、跟線程相關(guān)的Handler翻斟,即HandlerThread(源碼角度分析)
HandlerThread本質(zhì)上就是一個普通Thread,只不過內(nèi)部建立了Looper
HandlerThread的特點
1.HandlerThread將loop轉(zhuǎn)到子線程中處理,說白了就是將分擔(dān)MainLooper的工作量说铃,降低了主線程的壓力,使主界面更流暢嘹履。
2.開啟一個線程起到多個線程的作用腻扇。處理任務(wù)是串行執(zhí)行,按消息發(fā)送順序進行處理砾嫉。
3.相比多次使用new Thread(){…}.start()這樣的方式節(jié)省系統(tǒng)資源幼苛。
4.但是由于每一個任務(wù)都將以隊列的方式逐個被執(zhí)行到,一旦隊列中有某個任務(wù)執(zhí)行時間過長焕刮,那么就會導(dǎo)致后續(xù)的任務(wù)都會被延遲處理舶沿。
5.HandlerThread擁有自己的消息隊列墙杯,它不會干擾或阻塞UI線程。
6.通過設(shè)置優(yōu)先級就可以同步工作順序的執(zhí)行括荡,而又不影響UI的初始化高镐;
六、主線程往子線程發(fā)消息
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity implements OnClickListener {
public static final int UPDATE_TEXT = 1;
private TextView tv;
private Button btn;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(this);
//疑問:為什么這段代碼如果寫在onClick方法里面會報空指針畸冲?
new Thread(new Runnable() {
@Override
public void run() {
//1嫉髓、準(zhǔn)備Looper對象
Looper.prepare();
//2、在子線程中創(chuàng)建Handler
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i("handleMessage:", Thread.currentThread().getName());
Log.i("后臺輸出", "收到了消息對象");
}
};
//3邑闲、調(diào)用Looper的loop()方法算行,取出消息對象
Looper.loop();
}
}).start();
}
@Override
public void onClick(View v) {
Log.i("onClick:", Thread.currentThread().getName());
switch (v.getId()) {
case R.id.btn:
Message msg = handler.obtainMessage();
handler.sendMessage(msg);
break;
default:
break;
}
}
}
總結(jié):
首先執(zhí)行Looper的prepare()方法,這個方法有兩個作用:一是生成Looper對象苫耸,而是把Looper對象和當(dāng)前線程對象形成鍵值對(線程為鍵)州邢,存放在ThreadLocal當(dāng)中,然后生成handler對象褪子,調(diào)用Looper的myLooper()方法偷霉,得到與Handler所對應(yīng)的Looper對象,這樣的話褐筛,handler类少、looper 、消息隊列就形成了一一對應(yīng)的關(guān)系渔扎,然后執(zhí)行上面的第三個步驟硫狞,即Looper在消息隊列當(dāng)中循環(huán)的取數(shù)據(jù)。
另外晃痴,在本文最開頭的第一段中残吩,我們在主線程中創(chuàng)建Handler也沒有調(diào)用Looper.prepare()方法,為什么就沒有崩潰呢倘核?泣侮,這是由于在程序啟動的時候,系統(tǒng)已經(jīng)幫我們自動調(diào)用了Looper.prepare()方法紧唱。
七活尊、子線程往子線程發(fā)送消息
new Thread(new Runnable() {
@Override
public void run() {
String msg;
Looper.prepare();
childHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
System.out.println("這個消息是從-->>" + msg.obj+ "過來的,在" + "btn的子線程當(dāng)中" + "中執(zhí)行的");
}
};
Looper.loop();//開始輪循
}
}).start();
創(chuàng)建第二個線程
new Thread(new Runnable() {
@Override
public void run() {
Looper loop = Looper.myLooper();
Message msg = childHandler.obtainMessage();
msg.obj = "btn2當(dāng)中子線程";
childHandler.sendMessage(msg);
}
}).start();
其實子線程向子線程之間通信漏益,其實就是在一個子線程中創(chuàng)建一個Handler蛹锰,它的回調(diào)自然就在此子線程中,然后在另一個子線程中調(diào)用此handler來發(fā)送消息就可以了绰疤,不過記得寫上Looper哦铜犬。
八、在同一線程中android.Handler和android.MessaegQueue的數(shù)量對應(yīng)關(guān)系是怎樣的?
N(Handler):1(MessageQueue)
在同一線程中肯定會調(diào)用一個 Loop.prepare() 癣猾,其中就生成一個 MessageQueue .
而代碼中可以 new 出多個 Handler 發(fā)送各自的 Message 到這個 MessageQueue 中敛劝,最后調(diào)用 msg.target.dispatch 中這個>target來捕獲自己發(fā)送的massge,所以明顯是 N 個 Handler 對應(yīng)一個 MessageQueue.