概述
Android消息機(jī)制主要是指Handler的運(yùn)行機(jī)制以及Handler所附帶的MessageQueue和Looper的工作過程。而Handler的主要作用是將一個任務(wù)切換到某個指定的線程中去執(zhí)行。
- Android為什么需要提供在某個線程中執(zhí)行任務(wù)這種功能?
因?yàn)锳ndroid規(guī)定只能在主線程中進(jìn)行訪問UI,如果子線程中訪問UI,那么程序就會拋出異常。在ViewRootImpl對UI操作進(jìn)行了驗(yàn)證,如下:
void checkThread(){
if (mThread != Thread.currentThread){
throw new CalledFromWrongThreadException(
"Only the original thread that created a view
hierarchy can touch its views.");
}
}
其中mThread表示為主線程。
為什么系統(tǒng)不允許在子線程中訪問UI?
因?yàn)锳ndroid的UI控件不是線程安全的,如果在多線程中并發(fā)訪問可能會導(dǎo)致UI空間不可預(yù)期的狀態(tài)。再談Handler的作用:
對于一般耗時的任務(wù)通常會在子線程中執(zhí)行,防止阻塞主線程。任務(wù)執(zhí)行后產(chǎn)生的數(shù)據(jù),如果需要將數(shù)據(jù)傳遞給UI,進(jìn)行對UI的修改,那么就需要Handler將數(shù)據(jù)傳遞給主線程,通過主線程對UI進(jìn)行修改。
Handler的使用
Handler主要包含兩種使用方法:
- 通過sendMessage方法發(fā)送Message,然后在handleMessage方法中獲取到Message數(shù)據(jù)。
- 通過創(chuàng)建一個Runnable,在Runnable中對UI進(jìn)行更新,并且將Runnable對象提交給Handler執(zhí)行。
public class MainActivity extends AppCompatActivity {
private TextView textView1;
private TextView textView2;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:
textView1.setText(msg.obj.toString());
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView1 = (TextView) findViewById(R.id.text_handler1);
textView2 = (TextView) findViewById(R.id.text_handler2);
download1();
download2();
}
private void download1(){
new Thread(){
@Override
public void run() {
//模擬執(zhí)行任務(wù)
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
msg.what = 0;
msg.obj = "任務(wù)一下載完成";
handler.sendMessage(msg);
}
}.start();
}
private void download2(){
new Thread(){
@Override
public void run() {
//模擬執(zhí)行任務(wù)
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Runnable runnable = new Runnable() {
@Override
public void run() {
textView2.setText("任務(wù)二下載完成");
}
};
handler.post(runnable);
}
}.start();
}
}
如上為Handler的兩種使用方法,在這里要注意的是第一種方法,定義Message時,設(shè)置.what來判斷消息,設(shè)置.obj來傳遞數(shù)據(jù)。
Handler的工作原理
Handler創(chuàng)建時會采用當(dāng)前線程的Looper來構(gòu)建內(nèi)部的消息循環(huán)系統(tǒng);創(chuàng)建完畢后,當(dāng)Handler的send方法被調(diào)用或調(diào)用post方法提交一個Runnable對象,它會調(diào)用MessageQueue的enqueueMessage方法將這個消息放入消息隊(duì)列中,然后Looper發(fā)現(xiàn)有新消息時,就會處理這個消息,最終消息中的Runnable或Handler中的handleMessage方法會被調(diào)用。
消息隊(duì)列的工作原理
消息隊(duì)列在Android中表示為MessageQueue,MessageQueue主要包含了兩個操作:插入和讀取。讀取操作本身會伴隨著刪除操作,插入和讀取對應(yīng)的方法分別為enqueueMessage和next。盡管MessageQueue被稱為“消息隊(duì)列”,但實(shí)際上它是通過一個單鏈表來維護(hù)消息列表,因?yàn)閱捂湵碓诓迦牒蛣h除上有優(yōu)勢。
Looper的工作原理
Looper在消息機(jī)制中扮演著消息循環(huán)的角色,它會不停地從MessageQueue中查看是否有新消息,如果有新消息,那么會立刻處理,否則會一直阻塞在那。
我們知道Handler的工作需要Looper,沒有Looper的線程就會報錯,那么如何在一個線程中創(chuàng)建Looper呢?
首先通過Looper.prepare()方法創(chuàng)建一個Looper,然后通過Looper.loop()方法來開啟消息循環(huán)。
new Thread("test"){
public void run(){
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();
也許你會納悶,為什么我們在之前的使用中創(chuàng)建Handler對象時,并沒有調(diào)用Looper.prepare()來創(chuàng)建Looper對象呢?
實(shí)際上,是因?yàn)槲覀冎暗氖褂脮r在主線程中創(chuàng)建的Handler對象,而在主線程中,系統(tǒng)已經(jīng)把我們創(chuàng)建好了Looper對象。
如下,我們在子線程中創(chuàng)建了Handler對象,并接收到另一個子線程傳遞過來的數(shù)據(jù):
public class MainActivity extends AppCompatActivity {
private Handler handler1 = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
download1();
download2();
}
private void download1(){
new Thread("Thread#1"){
@Override
public void run() {
Looper.prepare();
handler1 = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:
Log.d("tag","get msg from thread2: " + msg.obj.toString());
break;
}
}
};
Looper.loop();
}
}.start();
}
private void download2(){
new Thread("Thread#2"){
@Override
public void run() {
//模擬執(zhí)行任務(wù)
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
msg.what = 0;
msg.obj = "Thread2下載任務(wù)完成";
handler1.sendMessage(msg);
}
}.start();
}
}