Android的消息傳遞機(jī)制是另一種形式的“事件處理”毛好,這種機(jī)制主要是為了解決Android應(yīng)用的多線程問題——Android平臺(tái)只允許UI線程修改Activity里的UI組件表窘,這會(huì)導(dǎo)致新啟動(dòng)的線程無法動(dòng)態(tài)改變界面組件的屬性值匣屡。但在實(shí)際Android應(yīng)用開發(fā)中,需要讓新啟動(dòng)的線程周期性的改變界面組件的屬性值女气,這就需要借助于Handler的消息機(jī)制來實(shí)現(xiàn)了杏慰。
多線程與異步
在學(xué)習(xí)Handler之前,有必要了解一下多線程與異步炼鞠。
當(dāng)一個(gè)程序第一次啟動(dòng)時(shí)缘滥,Android會(huì)同時(shí)啟動(dòng)一條主線程(Main Thread),主線程主要負(fù)責(zé)處理與UI相關(guān)的事件谒主,所以朝扼,主線程通常又被叫做UI線程。
當(dāng)在主線程中進(jìn)行耗時(shí)操作時(shí)(例如請求網(wǎng)絡(luò)資源)霎肯,主線程可能被卡死擎颖,這就需要?jiǎng)?chuàng)建一個(gè)新的線程來完成耗時(shí)操作,該操作完成后再通知主線程(Handler可以完成線程與線程之間的通信工作)观游,這幾個(gè)線程同時(shí)工作搂捧,就是多線程,而這種處理方式就是異步懂缕。
什么是Handler允跑?
一個(gè)Handler允許發(fā)送、處理消息和與線程消息隊(duì)列相關(guān)的可執(zhí)行對象搪柑。
Handler類的主要作用:
- 在新啟動(dòng)的線程中發(fā)送消息聋丝。
- 在主線程中獲取、處理消息工碾。
Handler類包含如下方法用于發(fā)送潮针、處理消息:
- void handleMessage(Message msg):處理消息的方法。該方法通常用于被重寫倚喂。
- final boolean hasMessages(int what):檢查消息隊(duì)列中是否包含what屬性為指定值的消息。
- final boolean hasMessages(int what, Object object):檢查消息隊(duì)列中是否包含what屬性為指定值且object屬性為指定對象的消息瓣戚。
- 多個(gè)重載的Message obtainMessage():獲取消息端圈。
- sendEmptyMessage(int what):發(fā)送空消息。
- final boolean sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒后發(fā)送空消息子库。
- final boolean sendMessage(Message msg):立即發(fā)送消息舱权。
- final boolean sendMessageDelayed(Message msg, long delayMillis):指定多少毫秒后發(fā)送消息。
Message仑嗅、Handler宴倍、MessageQueue张症、Looper工作原理
- Message:Handler接收和處理的消息對象。
- 2個(gè)整型數(shù)值:輕量級(jí)存儲(chǔ)int類型的數(shù)據(jù)鸵贬。
- 1個(gè)Object:任意對象俗他。
- replyTo:線程通信時(shí)使用。
- what:用戶自定義的消息碼阔逼,讓接收者識(shí)別消息兆衅。
- MessageQueue:Message的隊(duì)列。
- 采用先進(jìn)先出的方式管理Message嗜浮。
- 每一個(gè)線程最多可以擁有一個(gè)羡亩。
- Looper:消息泵,是MessageQueue的管理者危融,會(huì)不斷從MessageQueue中取出消息畏铆,并將消息分給對應(yīng)的Handler處理。
- 每個(gè)線程只有一個(gè)Looper吉殃。
- Looper.prepare():為當(dāng)前線程創(chuàng)建Looper對象辞居。
- Looper.myLooper():可以獲得當(dāng)前線程的Looper對象。
- Handler:能把消息發(fā)送給MessageQueue寨腔,并負(fù)責(zé)處理Looper分給它的消息速侈。
異步消息機(jī)制處理流程圖如下:
異步消息處理的整個(gè)流程如上圖所示,首先需要在主線程當(dāng)中創(chuàng)建一個(gè)Handler對象迫卢,并重寫handleMessage()方法倚搬。然后當(dāng)子線程中需要進(jìn)行UI操作時(shí),就創(chuàng)建一個(gè)Message對象乾蛤,并通過Handler將這條消息發(fā)送出去每界。之后這條消息會(huì)被添加到MessageQueue的隊(duì)列中等待被處理,而Looper則會(huì)一直嘗試從MessageQueue中取出待處理消息家卖,最后分發(fā)回Handler的handleMessage()方法中眨层。由于Handler是在主線程中創(chuàng)建的,所以此時(shí)handleMessage()方法中的代碼也會(huì)在主線程中運(yùn)行上荡,于是在這里就可以安心地進(jìn)行UI操作了趴樱。
實(shí)現(xiàn)倒計(jì)時(shí)Demo
效果如下:
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.trampcr.countdowndemo.MainActivity">
<TextView
android:id="@+id/count_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text=""
android:textColor="#000000"
android:textSize="50sp" />
<Button
android:id="@+id/start"
android:layout_width="150dp"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="30dp"
android:text="開始倒計(jì)時(shí)"
android:textColor="#000000"
android:textSize="18sp" />
</RelativeLayout>
包含一個(gè)文本用于顯示倒計(jì)時(shí)數(shù)字,一個(gè)按鈕用于開啟倒計(jì)時(shí)酪捡。
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TextView mCountNumber;
private Button mStart;
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 88888:
int value = (int) msg.obj;
mCountNumber.setText(String.valueOf(value / 1000));
msg = Message.obtain();//重新獲取消息
msg.arg1 = 0;
msg.arg2 = 1;
msg.what = 88888;
msg.obj = value - 1000;
if (value > 0){
sendMessageDelayed(msg, 1000);
}
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCountNumber = (TextView) findViewById(R.id.count_number);
mStart = (Button) findViewById(R.id.start);
mStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message message = handler.obtainMessage();
message.arg1 = 0;
message.arg2 = 1;
message.what = 88888;
message.obj = 10000;
handler.sendMessageDelayed(message, 1000);
}
});
}
}
首先創(chuàng)建一個(gè)Handler對象叁征,并實(shí)現(xiàn)handleMessage方法,用于接收消息逛薇。
接下來在點(diǎn)擊事件中創(chuàng)建Message對象捺疼,不建議使用new Message(),而應(yīng)該用handler.obtainMessage()來創(chuàng)建Message永罚,然后使用handler.sendMessageDealyed延遲發(fā)送消息啤呼,發(fā)出的消息在上面創(chuàng)建好的handler中可以接收到卧秘,然后處理消息。
以上代碼就實(shí)現(xiàn)了倒計(jì)時(shí)效果官扣,但是我們看到handler這部分代碼的背景色為黃色翅敌,究其原因是handler是一個(gè)內(nèi)部類,可能產(chǎn)生內(nèi)存泄漏醇锚。
解決方法:使用外部類哼御。
代碼如下:
public class MainActivity extends AppCompatActivity {
private TextView mCountNumber;
private Button mStart;
private CountDownHandler mCountDownHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCountNumber = (TextView) findViewById(R.id.count_number);
mStart = (Button) findViewById(R.id.start);
mCountDownHandler = new CountDownHandler(this);
mStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message message = mCountDownHandler.obtainMessage();
message.arg1 = 0;
message.arg2 = 1;
message.what = 88888;
message.obj = 10000;
mCountDownHandler.sendMessageDelayed(message, 1000);
}
});
}
public TextView getmCountNumber() {
return mCountNumber;
}
public static class CountDownHandler extends Handler {
public final WeakReference<MainActivity> mainActivityWeakReference;
public CountDownHandler(MainActivity activity) {
mainActivityWeakReference = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity mainActivity = mainActivityWeakReference.get();
switch (msg.what) {
case 88888:
int value = (int) msg.obj;
mainActivity.getmCountNumber().setText(String.valueOf(value / 1000));
msg = Message.obtain();//重新獲取消息
msg.arg1 = 0;
msg.arg2 = 1;
msg.what = 88888;
msg.obj = value - 1000;
if (value > 0) {
sendMessageDelayed(msg, 1000);
}
break;
}
}
}
}
定義一個(gè)外部類繼承Handler,并實(shí)現(xiàn)TextView的getter方法焊唬,最后運(yùn)行效果和原來一樣恋昼,但是該方法可避免內(nèi)存泄漏。