1.為什么要了解學(xué)習(xí)Handler?
1.為了以后面試做準(zhǔn)備:國(guó)內(nèi)一些大廠或者專業(yè)的互聯(lián)網(wǎng)技術(shù)型公司,在面試的時(shí)候都會(huì)或多或少提到Android的消息傳遞機(jī)制,如果你能深入了解并掌握這一方面的知識(shí)洞辣,會(huì)讓面試官對(duì)你的印象提升數(shù)倍。
2.為了完成從初級(jí)開(kāi)發(fā)到中高級(jí)開(kāi)發(fā)的轉(zhuǎn)變:初級(jí)開(kāi)發(fā)就是會(huì)簡(jiǎn)單調(diào)用代碼昙衅,想更進(jìn)一步扬霜,你就要理解你調(diào)用的代碼底層是怎么實(shí)現(xiàn)的,現(xiàn)在越來(lái)越多的框架極大的方便了我們的編程而涉,但是我們不能僅僅知其然著瓶,更要知其所以然。很多優(yōu)秀的框架都少不了對(duì)Handler的調(diào)用啼县,這時(shí)你如果掌握了handler的原理材原,會(huì)極大的幫助你理解那些復(fù)雜難懂的框架余蟹。
3.為了更好的調(diào)試程序:我們看日志的時(shí)候經(jīng)常會(huì)出現(xiàn)很多關(guān)于Handler這個(gè)類的各種調(diào)用威酒,如果你理解了這個(gè)機(jī)制挺峡,你會(huì)個(gè)更好的找到錯(cuò)誤點(diǎn)并結(jié)局它們沙郭。
2.為什么要引入Handler消息傳遞機(jī)制病线?
在Android開(kāi)發(fā)中鲤嫡,子線程沒(méi)有辦法對(duì)UI界面上的內(nèi)容進(jìn)行操作暖眼,如果操作诫肠,將拋出異常:CalledFromWrongThreadException
那么我們?yōu)榱藢?shí)現(xiàn)子線程中操作UI界面栋豫,Goodle在Android引入了Handler消息傳遞機(jī)制丧鸯,目的是打破對(duì)主線程操作UI的依賴性丛肢。
3.什么是Handler?
handler通俗一點(diǎn)講就是用來(lái)在各個(gè)線程之間發(fā)送數(shù)據(jù)的處理對(duì)象蜂怎。在任何線程中杠步,只要獲得了另一個(gè)線程的handler篮愉,則可以通過(guò) handler.sendMessage(message)方法向那個(gè)線程發(fā)送數(shù)據(jù)差导∩韬郑基于這個(gè)機(jī)制助析,我們?cè)谔幚矶嗑€程的時(shí)候可以新建一個(gè)thread外冀,這個(gè)thread擁有UI線程中的一個(gè)handler雪隧。當(dāng)thread處理完一些耗時(shí)的操作后通過(guò)傳遞過(guò)來(lái)的handler向UI線程發(fā)送數(shù)據(jù)脑沿,由UI線程去更新界面。
4.常用類
4.1簡(jiǎn)單介紹:
- Message:消息注服,被傳遞和處理的數(shù)據(jù)溶弟。其中包含了消息ID辜御,消息處理對(duì)象以及處理的數(shù)據(jù)等我抠,由MessageQueue統(tǒng)一列隊(duì)袜茧,終由Handler處理。
- Handler:處理者纳鼎,負(fù)責(zé)Message的發(fā)送及處理贱鄙。使用Handler時(shí)逗宁,需要實(shí)現(xiàn)handleMessage(Message msg)方法來(lái)對(duì)特定的Message進(jìn)行處理瞎颗,例如更新UI等哼拔。Handler類的主要作用:(有兩個(gè)主要作用)1)倦逐、在工作線程中發(fā)送消息檬姥;2)穿铆、在主線程中獲取荞雏、并處理消息凤优。
- MessageQueue:消息隊(duì)列筑辨,本質(zhì)是一個(gè)數(shù)據(jù)結(jié)構(gòu)暮现,用來(lái)存放Handler發(fā)送過(guò)來(lái)的消息栖袋,并按照FIFO(First Input First Outputl,先入先出隊(duì)列)規(guī)則執(zhí)行塘幅。當(dāng)然电媳,存放Message并非實(shí)際意義的保存匾乓,而是將Message串聯(lián)起來(lái)钝尸,等待Looper的抽取。
- Looper:消息泵或循環(huán)器剩愧,不斷從MessageQueue中抽取Message仁卷。因此锦积,一個(gè)MessageQueue需要一個(gè)Looper丰介。
- Thread:線程带膀,負(fù)責(zé)調(diào)度整個(gè)消息循環(huán)垛叨,即消息循環(huán)的執(zhí)行場(chǎng)所柜某。
4.2Handler剂癌、Looper珍手、Message辞做、MessageQueue之間的關(guān)系:
1.** Looper和MessageQueue一一對(duì)應(yīng)琳要,創(chuàng)建一個(gè)Looper的同時(shí),會(huì)創(chuàng)建一個(gè)MessageQueue**秤茅;
- 而Handler與它們的關(guān)系稚补,只是簡(jiǎn)單的聚集關(guān)系,即Handler里會(huì)引用當(dāng)前線程里的特定Looper和MessageQueue框喳;
- 在一個(gè)線程中课幕,只能有一個(gè)Looper和MessageQueue,但是可以有多個(gè)Handler五垮,而且這些Handler可以共享一個(gè)Looper和MessageQueue乍惊;
- Message被存放在 MessageQueue中润绎,一個(gè) MessageQueue中可以包含多個(gè)Message對(duì)象。
【注意:】
Looper對(duì)象用來(lái)為一個(gè)線程開(kāi)啟一個(gè)消息循環(huán)涂佃,從而操作MessageQueue;
默認(rèn)情況下,Android創(chuàng)建的線程沒(méi)有開(kāi)啟消息循環(huán)Looper,但是主線程例外。
系統(tǒng)自動(dòng)為主線程創(chuàng)建Looper對(duì)象,開(kāi)啟消息循環(huán);
所以主線程中使用new來(lái)創(chuàng)建Handler對(duì)象。而子線程中不能直接new來(lái)創(chuàng)建Handler對(duì)象就會(huì)異常。
子線程中創(chuàng)建Handler對(duì)象,步驟如下:
Looper.prepare();
Handler handler = new Handler() {
//handlemessage(){}
}
Looper.loop();
4.3Handler類中常用方法:
- handleMessage() 用在主線程中喳钟,構(gòu)造Handler對(duì)象時(shí)酬蹋,重寫(xiě)handleMessage()方法匕垫。該方法根據(jù)工作線程返回的消息標(biāo)識(shí),來(lái)分別執(zhí)行不同的操作春寿。
- sendEmptyMessage() 用在工作線程中,發(fā)送空消息。
- sendMessage() 用在工作線程中,立即發(fā)送消息析藕。
4.4Message消息類中常用屬性:
- arg1 用來(lái)存放整型數(shù)據(jù)
- arg2 用來(lái)存放整型數(shù)據(jù)
- obj 用來(lái)存放Object數(shù)據(jù)
- what 用于指定用戶自定義的消息代碼筹煮,這樣便于主線程接收后劫扒,根據(jù)消息代碼不同而執(zhí)行不同的相應(yīng)操作澎灸。
示例代碼(1)簡(jiǎn)單的加載網(wǎng)絡(luò)圖片:
private TextView text_main_info;
private ProgressDialog pDialog;
private ImageView image_main;
private Handler handler = null;
public static final String urlString = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1517382140&di=f11ad1bb20b04cfb4ebc96d337d0ff21&imgtype=jpg&er=1&src=http%3A%2F%2Fpic4.nipic.com%2F20091217%2F3885730_124701000519_2.jpg";
@SuppressLint("HandlerLeak")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text_main_info = (TextView) findViewById(R.id.text_main_info);
pDialog = new ProgressDialog(MainActivity.this);
pDialog.setMessage("Loading...");
image_main = (ImageView) findViewById(R.id.image_main);
// 主線程中的handler對(duì)象會(huì)處理工作線程中發(fā)送的Message其兴。根據(jù)Message的不同編號(hào)進(jìn)行相應(yīng)的操作。
handler = new Handler() {
public void handleMessage(android.os.Message msg) {
// 工作線程中要發(fā)送的信息全都被放到了Message對(duì)象中,也就是上面的參數(shù)msg中。要進(jìn)行操作就要先取出msg中傳遞的數(shù)據(jù)柒莉。
switch (msg.what) {
case 0:
// 工作線程發(fā)送what為0的信息代表線程開(kāi)啟了。主線程中相應(yīng)的顯示一個(gè)進(jìn)度對(duì)話框
pDialog.show();
break;
case 1:
// 工作線程發(fā)送what為1的信息代表要線程已經(jīng)將需要的數(shù)據(jù)加載完畢搀擂。本案例中就需要將該數(shù)據(jù)獲取到哨颂,顯示到指定ImageView控件中即可箫措。
image_main.setImageBitmap((Bitmap) msg.obj);
break;
case 2:
// 工作線程發(fā)送what為2的信息代表工作線程結(jié)束弦牡。本案例中,主線程只需要將進(jìn)度對(duì)話框取消即可。
pDialog.dismiss();
break;
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
// 當(dāng)工作線程剛開(kāi)始啟動(dòng)時(shí)餐塘,希望顯示進(jìn)度對(duì)話框妥衣,此時(shí)讓handler發(fā)送一個(gè)空信息即可。
// 當(dāng)發(fā)送這個(gè)信息后戒傻,主線程會(huì)回調(diào)handler對(duì)象中的handleMessage()方法税手。handleMessage()方法中
// 會(huì)根據(jù)message的what種類來(lái)執(zhí)行不同的操作。
handler.sendEmptyMessage(0);
// 工作線程執(zhí)行訪問(wèn)網(wǎng)絡(luò)需纳,加載網(wǎng)絡(luò)圖片的任務(wù)芦倒。
Bitmap bitmap=HttpClientHelper.getImageBitmap(urlString);
// 工作線程將要發(fā)送給主線程的信息都放到一個(gè)Message信息對(duì)象中。
// 而Message對(duì)象的構(gòu)建建議使用obtain()方法生成不翩,而不建議用new來(lái)生成兵扬。
Message msgMessage = Message.obtain();
// 將需要傳遞到主線程的數(shù)據(jù)放到Message對(duì)象的obj屬性中,以便于傳遞到主線程口蝠。
msgMessage.obj = bitmap;
// Message對(duì)象的what屬性是為了區(qū)別信息種類器钟,而方便主線程中根據(jù)這些類別做相應(yīng)的操作。
msgMessage.what = 1;
// handler對(duì)象攜帶著Message中的數(shù)據(jù)返回到主線程
handler.sendMessage(msgMessage);
// handler再發(fā)出一個(gè)空信息妙蔗,目的是告訴主線程工作線程的任務(wù)執(zhí)行完畢傲霸。一般主線程會(huì)接收到這個(gè)消息后,
// 將進(jìn)度對(duì)話框關(guān)閉
handler.sendEmptyMessage(2);
}
}).start();
}
在網(wǎng)速很快的情況下會(huì)看不到進(jìn)度條灭必,你們可以用打斷點(diǎn)的方式去查看代碼執(zhí)行順序狞谱。
(2)打地鼠小游戲:
新建一個(gè)頁(yè)面代碼如下:
private Handler handler = null;
private ImageView image_main_mouse;
private int position;
private boolean flag = false;
private int[][] positionArr;
private int countNum;
private int beatNum;
private TextView mTvStop;
@SuppressLint("HandlerLeak")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_beat_mouse);
findView();
initData();
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
image_main_mouse.setVisibility(View.VISIBLE);
// 獲取0-8之間的隨機(jī)數(shù)[0,8),半閉合區(qū)間禁漓。目的是隨機(jī)獲取給定的8個(gè)坐標(biāo)位置跟衅。
// 獲取隨機(jī)數(shù)有兩種辦法:
// 方法一:
// Math.random()*positionArr.length,注意偽隨機(jī)數(shù)是個(gè)半閉合區(qū)間播歼。即隨機(jī)數(shù)不可能為positionArr.length
// 方法二:
// new Random().nextInt(positionArr.length);
position = (int) (Math.random() * positionArr.length);
image_main_mouse.setX(positionArr[position][0]);
image_main_mouse.setY(positionArr[position][1]);
break;
default:
break;
}
}
};
image_main_mouse = (ImageView) findViewById(R.id.image_main_mouse);
image_main_mouse.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
image_main_mouse.setVisibility(View.GONE);
beatNum++;
Toast.makeText(BeatMouseActivity.this, "總出現(xiàn)次數(shù):" + countNum + ";擊中次數(shù):" + beatNum, Toast.LENGTH_SHORT).show();
return false;
}
});
mTvStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (flag) {
mTvStop.setText("開(kāi)始");
} else {
mTvStop.setText("停止");
}
flag = !flag;
new Thread(new Runnable() {
@Override
public void run() {
while (flag) {
try {
countNum++;
// 獲取0-500之間的隨機(jī)數(shù)伶跷,再加上500,目的是讓老鼠出現(xiàn)的間隙時(shí)間也隨機(jī)秘狞,最短出現(xiàn)間隙為500毫秒叭莫,最長(zhǎng)為999毫秒。
Thread.sleep(new Random().nextInt(500) + 500);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
}
}).start();
}
});
}
private void initData() {
positionArr = new int[8][2];
for (int i = 0; i < positionArr.length; i++) {
//設(shè)置X軸坐標(biāo)
positionArr[i][0] = getWindowWidth(this) / positionArr.length * i;
//設(shè)置Y軸坐標(biāo)
positionArr[i][1] = getWindowHeight(this) / positionArr.length * i;
}
}
private void findView() {
image_main_mouse = (ImageView) findViewById(R.id.image_main_mouse);
mTvStop = (TextView) findViewById(R.id.tv_stop);
}
/**
* 獲取屏幕寬度
*/
public static int getWindowWidth(Activity activity) {
DisplayMetrics dm = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
return dm.widthPixels;
}
/**
* 獲取屏幕高度
*/
public static int getWindowHeight(Activity activity) {
DisplayMetrics dm = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
return dm.heightPixels;
}
下面是示例圖:
我把以上兩個(gè)示例代碼合并到了一個(gè)項(xiàng)目里烁试,并上傳Github雇初,鏈接為:https://github.com/EspressoToast/HandlerDemo
這一講先告一段落。下一講我會(huì)為大家分析一下Handler源碼减响。