Handler消息傳遞機制詳解
1、出於線程考慮,Android的UI不是線程安全的,者意味着如果有多個線程併發操作UI組件,可能導致線程安全問題。爲了解決這一問題,Android指定了一個簡單的規則:只允許UI線程修改Android裏的UI組件(UI線程也叫主線程)
那問題來了?因爲我們知道在Activity裏不能處理耗時任務,會引發ANR,新開啓個線程處理,但是新線程還不能修改UI組件。因此Android提供了一個handler消息傳遞機制來解決這個問題,(後面還有一個異步任務也能解決,其實這個異步任務的內部實現就是handler和Thread組成)
2、Handler主要作用:
①在新線程中發送消息。
②在主線程中獲取,處理消息。
上述似乎就兩步完成了,但是我們要考慮兩個問題,何時發送消息?何時接收消息呢?
爲了讓主線程中“適時”地處理消息,顯然只能用回調的方式來實現,----開發者通過只要重寫Handler類中處理信息的方法,當新啓動的線程發送消息是,消息會發送到與之關聯的MessageQueue,而Handler會不斷地從MessageQueue中獲取並處理消息-----這將導致Handler類中處理消息的方法被回調
3、Handler類包含的包含的方法
①void handlerMessage(Message msg):處理消息的方法
②final boolean hasMessages(int what):檢查消息隊列中是否包含what屬性爲指定值得消息
③final boolean has Messages(int what,Object object):檢查消息中是否包含what屬性爲指定值的消息,且object屬性爲指定對象的消息。(常用)
④sendEmptyMessage(int what):發送空消息
final boolean sendEmptyMessageDelayed(int what,long delayMillis):指定多少毫秒之後發送空消息。
⑤final boolean sendMessage(Message msg):立即發送消息(常用)(這是發送的是消息對象)
⑥final boolean sendMessageDelayed(Message msg,long delayMillis):指定多少毫秒之後發送消息。
下面看一個簡單的例子,
public class MainActivity extends Activity implements View.OnClickListener {
private static final int COMPLETED = 0;
private TextView stateText;
private Button btn;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == COMPLETED) {
stateText.setText("completed");
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
stateText = (TextView) findViewById(R.id.tv);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
new WorkThread().start();
}
//工作線程
private class WorkThread extends Thread {
@Override
public void run() {
//......處理比較耗時的操作,
//處理完成後給handler發送消息
Message msg = new Message();
msg.what = COMPLETED;
handler.sendMessage(msg);
}
}
}
4、下面,我們就來分析一下Android中的消息機制。
熟悉Windows編程的朋友知道Windows程序是消息驅動的,並且有全局的消息循環系統。Google參考了Windows的消息循環機制,也在Android系統中實現了消息循環機制。Android通過Looper、Handler來實現消息循環機制。Android的消息循環是針對線程的,每個線程都可以有自己的消息隊列和消息循環。
Android系統中的Looper負責管理線程的消息隊列和消息循環。通過Looper.myLooper()得到當前線程的Looper對象,通過Looper.getMainLooper()得到當前進程的主線程的Looper對象。
前面提到,Android的消息隊列和消息循環都是針對具體線程的,一個線程可以存在一個消息隊列和消息循環,特定線程的消息只能分發給本線程,不能跨線程和跨進程通訊。但是創建的工作線程默認是沒有消息隊列和消息循環的,如果想讓工作線程具有消息隊列和消息循環,就需要在線程中先調用Looper.prepare()來創建消息隊列,然後調用Looper.loop()進入消息循環。下面是我們創建的工作線程:
class WorkThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// 處理收到的消息
}
};
Looper.loop();
}
}
這樣一來,我們創建的工作線程就具有了消息處理機制了。
那麼,爲什麼前邊的示例中,我們怎麼沒有看到Looper.prepare()和Looper.loop()的調用呢?原因在於,我們的Activity是一個UI線程,運行在主線程中,Android系統會在Activity啓動時爲其創建一個消息隊列和消息循環。
前面提到最多的是消息隊列(MessageQueue)和消息循環(Looper),但是我們看到每個消息處理的地方都有Handler的存在,它是做什麼的呢?Handler的作用是把消息加入特定的Looper所管理的消息隊列中,並分發和處理該消息隊列中的消息。構造Handler的時候可以指定一個Looper對象,如果不指定則利用當前線程的Looper對象創建。下面是Handler的兩個構造方法:
/**
* Default constructor associates this handler with the queue for the
* current thread.
*
* If there isn't one, this handler won't be able to receive messages.
*/
public Handler() {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = null;
}
/**
* Use the provided queue instead of the default one.
*/
public Handler(Looper looper) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = null;
}
消息機制中幾個重要成員有 handler, Looper, MessageQueue
工作流程:首先在主線程中創建handler 對象,在handler構造函數中會獲取當前線程Looper對象,(而主線程中默認有一個Looper對象,這裏就不用去創建它了。否則其他子線程,就要先創建一個Looper對象,通過Looper.prepare()創建,Looper.loop()開啓looper)當工作線程中通過handler調用sendMessage(msg)時,就會將消息加入到主線程的Looper裏,然後會腳本handler的handlerMessage()方法處理。所以這裏的關鍵就是handler對象創建時是以主線程對象創建的。才能保證是否能和主線程通信
希望好好理解上面的話
線面看一個子線程處理消息的例子:
package org.crazyit.handler;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
public class CalPrime extends Activity
{
static final String UPPER_NUM = "upper";
EditText etNum;
CalThread calThread;
// 定義一個線程類
class CalThread extends Thread
{
public Handler mHandler;
public void run()
{
Looper.prepare();
mHandler = new Handler()
{
// 定義處理消息的方法
@Override
public void handleMessage(Message msg)
{
if(msg.what == 0x123)
{
int upper = msg.getData().getInt(UPPER_NUM);
List<Integer> nums = new ArrayList<Integer>();
// 計算從2開始、到upper的所有質數
outer:
for (int i = 2 ; i <= upper ; i++)
{
// 用i處於從2開始、到i的平方根的所有數
for (int j = 2 ; j <= Math.sqrt(i) ; j++)
{
// 如果可以整除,表明這個數不是質數
if(i != 2 && i % j == 0)
{
continue outer;
}
}
nums.add(i);
}
// 使用Toast顯示統計出來的所有質數
Toast.makeText(CalPrime.this , nums.toString()
, Toast.LENGTH_LONG).show();
}
}
};
Looper.loop();
}
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
etNum = (EditText)findViewById(R.id.etNum);
calThread = new CalThread();
// 啓動新線程
calThread.start();
}
// 爲按鈕的點擊事件提供事件處理函數
public void cal(View source)
{
// 創建消息
Message msg = new Message();
msg.what = 0x123;
Bundle bundle = new Bundle();
bundle.putInt(UPPER_NUM ,
Integer.parseInt(etNum.getText().toString()));
msg.setData(bundle);
// 向新線程中的Handler發送消息
calThread.mHandler.sendMessage(msg);
}
}
1、擴展:
一個Activity中可以創建出多個工作線程,如果這些線程把他們消息放入Activity主線程的消息隊列中,那麼消息就會在主線程中處理了。因爲主線程一般負責視圖組件的更新操作,對於不是線程安全的視圖組件來說,這種方式能夠很好的實現視圖的更新。
2、可能存在的疑惑:
對於子線程訪問主線程的Handler對象,你可能會問,多個子線程都訪問主線程的Handler對象,發送消息和處理消息的過程中會不會出現數據的不一致呢?答案是Handler對象不會出現問題,因爲Handler對象管理的Looper對象是線程安全的,不管是添加消息到消息隊列還是從消息隊列中讀取消息都是同步保護的,所以不會出現數據不一致現象。