本文是在各個著名博客的基礎上總結而成,感謝!
1、簡單例子
private TextView stateText;
private Button btn;
@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() {
//......處理比較耗時的操作
//處理完成後改變狀態
stateText.setText("completed");
}
}
}
ERROR/AndroidRuntime(421): FATAL EXCEPTION: Thread-8
ERROR/AndroidRuntime(421): android.view.ViewRoot$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
到底是怎麼回事呢?原因在於,Android系統中的視圖組件並不是線程安全的,如果要更新視圖,必須在主線程中更新,不可以在子線程中執行更新的操作。
既然這樣,我們就在子線程中通知主線程,讓主線程做更新操作吧。那麼,我們如何通知主線程呢?我們需要使用到Handler對象。
我們稍微修改一下上面的代碼:
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);
}
}
}
2、Android中的消息機制分析
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// 處理收到的消息
}
};
Looper.loop();
}
}
下面是消息機制中幾個重要成員的關係圖:
一個Activity中可以創建出多個工作線程,如果這些線程把他們消息放入Activity主線程的消息隊列中,那麼消息就會在主線程中處理了。因爲主線程一般負責視圖組件的更新操作,對於不是線程安全的視圖組件來說,這種方式能夠很好的實現視圖的更新 。
那麼,子線程如何把消息放入主線程的消息隊列中呢?只要Handler對象以主線程的Looper創建,那麼當調用Handler的sendMessage方法,系統就會把消息主線程的消息隊列,並且將會在調用handleMessage方法時處理主線程消息隊列中的消息 。
對於子線程訪問主線程的Handler對象,你可能會問,多個子線程都訪問主線程的Handler對象,發送消息和處理消息的過程中會不會出現數據的不一致呢?答案是Handler對象不會出現問題,因爲Handler對象管理的Looper對象是線程安全的,不管是添加消息到消息隊列還是從消息隊列中讀取消息都是同步保護的,所以不會出現數據不一致現象。
Android中的Looper類,是用來封裝消息循環和消息隊列的一個類,用於在android線程中進行消息處理。handler其實可以看做是一個工具類,用來向消息隊列中插入消息的。
(1) Looper類用來爲一個線程開啓一個消息循環。 默認情況下android中新誕生的線程是沒有開啓消息循環的。(主線程除外,主線程系統會自動爲其創建Looper對象,開啓消息循環。) Looper對象通過MessageQueue來存放消息和事件。一個線程只能有一個Looper,對應一個MessageQueue。
(2) 通常是通過Handler對象來與Looper進行交互的。Handler可看做是Looper的一個接口,用來向指定的Looper發送消息及定義處理方法。 默認情況下Handler會與其被定義時所在線程的Looper綁定,比如,Handler在主線程中定義,那麼它是與主線程的Looper綁定。 mainHandler = new Handler() 等價於new Handler(Looper.myLooper()). Looper.myLooper():獲取當前進程的looper對象,類似的 Looper.getMainLooper() 用於獲取主線程的Looper對象。
(3) 在非主線程中直接new Handler() 會報如下的錯誤: E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception E/AndroidRuntime( 6173): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 原因是非主線程中默認沒有創建Looper對象,需要先調用Looper.prepare()啓用Looper。
(4) Looper.loop(); 讓Looper開始工作,從消息隊列裏取消息,處理消息。
注意:寫在Looper.loop()之後的代碼不會被執行,這個函數內部應該是一個循環,當調用mHandler.getLooper().quit()後,loop纔會中止,其後的代碼才能得以運行。把下面例子中的mHandler聲明成類成員,在主線程通過mHandler發送消息即可。 Android官方文檔中Looper的介紹: Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.
Most interaction with a message loop is through the Handler class.
This is a typical example of the implementation of a Looper thread, using the separation of prepare() and loop() to create an initial Handler to communicate with the Looper.
class LooperThread extends Thread
{
public Handler mHandler;
public void run()
{
Looper.prepare();
mHandler = new Handler()
{
public void handleMessage(Message msg)
{
// process incoming messages here
}
};
Looper.loop();
}
3、線程隊列
Handler 就是實現隊列的形式,一個 Handler 共有兩個隊列:一個是線程隊列,另一個是消息隊列 。
要應用 Handler 進行線程隊列,其流程主要是:
1. 要定義一個 Handler 對象;
2. 定義一個線程,並覆寫其 run 方法;
3. 通過 Handler 對象把第二的線程對象壓到隊列中等待執行;
4. Handler 對象把第二的線程對象從隊列中去除(當不想執行線程時 )。
注:如果需要循環執行線程,即可在處理線程後,再次利用 Handler 對象把線程對象壓到隊列中。
定義一個 Handler 對象的代碼如下:
Handler myFirstHandler= new Handler
定義一個線程的代碼如下:
Runnable myThread = new Runnable() {
public void run() {
// TODO Auto-generated method stub
}
};
myFirstHandler.post(myThread); //把一個線程放到隊列裏面去
Handler 對象把線程從隊列中去除的方法是:
myFirstHandler.removeCallbacks(myThread);//把線程從隊列中去除
通過上面我們的處理,雖然通過 Handler 對象往隊列裏面加入了一個新的線程,但實際上Handler 和它所屬的 Activity 是處於同一個線程中。因爲我們通過 Handler 把線程加到隊列中,實際是直接執行了 Runable 裏面的 run 方法,而且沒有像 JAVA 經典多線程編程一樣調用線程的start 方法,所以只是把線程對象加到 Activity 的線程中一起執行,而不是新開一個線程,即不是多真正的多線程。
4、runOnUiThread更新主線程
更新UI採用Handle+Thread,需要發送消息,接受處理消息(在回調方法中處理),比較繁瑣。除此之外,還可以使用runOnUiThread方法。
利用Activity.runOnUiThread(Runnable)把更新ui的代碼創建在Runnable中,然後在需要更新ui時,把這個Runnable對象傳給Activity.runOnUiThread(Runnable)。
Runnable對像就能在ui程序中被調用。如果當前線程是UI線程,那麼行動是立即執行。如果當前線程不是UI線程,操作是發佈到事件隊列的UI線程。
public class TestActivity extends Activity {
Button btn;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.handler_msg);
btn = (Button) findViewById(R.id.button1);
btn.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
// 模擬耗時的操作。
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 更新主線程UI
TestActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
btn.setText("更新完畢!");
}
});
}
}).start();
}
});
}
5、不是所有的Handler都能更新UI
Handler處理消息總是在創建Handler的線程裏運行。而我們的消息處理中,不乏更新UI的操作,不正確的線程直接更新UI將引發異常。因此,需要時刻關心Handler在哪個線程裏創建的。如何更新UI才能不出異常呢?SDK告訴我們,有以下4種方式可以從其它線程訪問UI線程(也即線程間通信):
· Activity.runOnUiThread(Runnable)
· View.post(Runnable)
· View.postDelayed(Runnable, long)
· 在UI線程中創建的Handler
其中,重點說一下的是View.post(Runnable)方法。在post(Runnableaction)方法裏,View獲得當前線程(即UI線程)的Handler,然後將action對象post到Handler裏。在Handler裏,它將傳遞過來的action對象包裝成一個Message(Message的callback爲action),然後將其投入UI線程的消息循環中。在 Handler再次處理該Message時,有一條分支(未解釋的那條)就是爲它所設,直接調用runnable的run方法。而此時,已經路由到UI線程裏,因此,我們可以毫無顧慮的來更新UI。
幾點小結
· Handler的處理過程運行在創建Handler的線程裏
· 一個Looper對應一個MessageQueue,一個線程對應一個Looper,一個Looper可以對應多個Handler
· 不確定當前線程時,更新UI時儘量調用View.post方法
· handler應該由處理消息的線程創建。
· handler與創建它的線程相關聯,而且也只與創建它的線程相關聯。handler運行在創建它的線程中,所以,如果在handler中進行耗時的操作,會阻塞創建它的線程。
· Android的線程分爲有消息循環的線程和沒有消息循環的線程,有消息循環的線程一般都會有一個Looper。主線程(UI線程)就是一個消息循環的線程。
· Looper.myLooper(); //獲得當前的Looper
Looper.getMainLooper() //獲得UI線程的Lopper
· Handle的初始化函數(構造函數),如果沒有參數,那麼他就默認使用的是當前的Looper,如果有Looper參數,就是用對應的線程的Looper。
· 如果一個線程中調用Looper.prepare(),那麼系統就會自動的爲該線程建立一個消息隊列,然後調用Looper.loop();之後就進入了消息循環,這個之後就可以發消息、取消息、和處理消息。
6、HandlerThread
在上面的總結中指出,Android的線程分爲有消息循環的線程和沒有消息循環的線程,有消息循環的線程一般都會有一個Looper。事實上Android提供了一個封裝好的帶有looper的線程類,即爲HandlerThread,具體可參見下面的代碼:
public class HandlerThreadActivity extends Activity {
private static final String TAG = "HandlerThreadActivity";
private HandlerThreadmHandlerThread;
private MyHandler mMyHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generatedmethod stub
super.onCreate(savedInstanceState);
TextView text = new TextView(this);
text.setText("HandlerThreadActivity");
setContentView(text);
Log.d(TAG, "The mainthread id = " + Thread.currentThread().getId());
//生成一個HandlerThread對象,實現了使用Looper來處理消息隊列的功能,
//這個類由Android應用程序框架提供
mHandlerThread = new HandlerThread("handler_thread");
//在使用HandlerThread的getLooper()方法之前,必須先調用該類的start();
mHandlerThread.start();
//即這個Handler是運行在mHandlerThread這個線程中
mMyHandler = new MyHandler(mHandlerThread.getLooper());
mMyHandler.sendEmptyMessage(1);
}
private class MyHandler extends Handler {
public MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "MyHandler-->handleMessage-->threadid = " +Thread.currentThread().getId());
super.handleMessage(msg);
}
}
}