Android中的訊息處理執行個體與分析
Android中的訊息處理執行個體與分析摘要
本文介紹了Android中的訊息處理機制,給出了Android訊息處理中的幾個重點類Handler、Message、MessageQueue、Looper、Runnable、Thread的詳細介紹,提供了兩個訊息處理的執行個體代碼,並深入分析了使用Android訊息機制應該遵循的幾個原則。
閱讀本文的收穫
在具有java基礎的情況下,Android的學習比較輕鬆,很多人在沒有深刻瞭解Android訊息處理機制的背景下,已經能夠開發出可用的app,很多人開始想學習Android訊息處理機制的第一個動機是遇到了一個著名的bug“CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.”。這個bug的含義即“只有建立一個view層次的線程能夠更新此view”,在更多情況下,它是想說“只有主線程能夠更新UI”。
本文即是來解釋如何利用Android的訊息機制從主線程或者子線程中更新UI或完成其他動作。你可以學到:
1. 如何使用Android的訊息實現同步、非同步作業;
2. 如何在主線程和子線程發送並接收訊息;
3. 訊息是如何得到處理的;
4. 使用Android的訊息處理機制應該遵循的幾個原則;
5. 兩個具體的訊息處理執行個體原始碼。
閱讀本文需要的技術背景
你可能需要如下技術背景才能完全理解本文的內容,如何你沒有以下背景,建議先學習相關知識:
1. Java語言基礎
2. Java多線程技術
3. Android開發基本知識
第一個例子:在主線程和子線程中發送訊息,在主線程中處理訊息
先來看一個代碼,代碼地址為:
http://download.csdn.net/detail/logicteamleader/8827099
本例的作用是:建立一個Handler(處理訊息的類),在介面上點擊按鈕就會向此Handler發送訊息,Handler可以處理這些訊息(後面會詳細解釋,訊息的處理其實是多個類共同合作的結果),對UI進行修改。介面上有八個按鈕,從上至下其效果分別是:
1. 使用Handler的post來傳遞一個Runnable的執行個體,該執行個體的run方法會在按鈕點擊時運行;
2. 使用Handler的postDelayed(Runnable r, long delayMillis)來傳遞一個Runnable的執行個體,該執行個體的run方法會在一段時間delayMillis後執行;
3. 使用Handler的sendMessage方法傳遞一個訊息,該訊息在Handler的handleMessage方法中被解析執行;
4. 使用Message的sendToTarget方法向獲得該Message的Handler傳遞一個訊息,該訊息在Handler的handleMessage方法中被解析執行;
第5、6、7、8個方法與上面四個方法類似,不同的是它們都是在子線程中調用的。
原始碼如下:
package com.example.wxbhandlertest;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.os.MessageQueue;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;/** * @author wxb * Android中的訊息處理執行個體之一 * * 一、在主線程內發送訊息 * 1.使用post * 2.使用postDelay * 3.使用sendMessage * 4.使用Message.sentToTarget * 二、在子線程中使用Handler * 1.使用post * 2.使用postDelay * 3.使用sendMessage * 4.使用Message.sentToTarget */public class MainActivity extends Activity { private Runnable runnable=null; private Runnable runnableDelay=null; private Runnable runnableInThread=null; private Runnable runnableDelayInThread=null; private static TextView tv; private static TextView tvOnOtherThread; //自訂Message類型 public final static int MESSAGE_WXB_1 = 1; public final static int MESSAGE_WXB_2 = 2; public final static int MESSAGE_WXB_3 = 3; public final static int MESSAGE_WXB_4 = 4; public final static int MESSAGE_WXB_5 = 5; private static Handler mHandler=new Handler(){ @Override public void handleMessage(Message msg) { switch(msg.what){ case MESSAGE_WXB_1: tv.setText("invoke sendMessage in main thread"); break; case MESSAGE_WXB_2: tv.setText("Message.sendToTarget in main thread"); break; case MESSAGE_WXB_3: tvOnOtherThread.setText("invoke sendMessage in other thread"); break; case MESSAGE_WXB_4: tvOnOtherThread.setText("Message.sendToTarget in other thread"); break; } super.handleMessage(msg); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) this.findViewById(R.id.tvOnMainThread); tvOnOtherThread = (TextView) this.findViewById(R.id.tvOnOtherThread); //方法1.post runnable = new Runnable(){ public void run(){ tv.setText(getString(R.string.postRunnable)); } }; Button handler_post = (Button) this.findViewById(R.id.btnHandlerpost); handler_post.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mHandler.post(runnable); } }); //方法2:postDelay runnableDelay = new Runnable(){ public void run(){ tv.setText(getString(R.string.postRunnableDelay)); } }; Button handler_post_delay = (Button) this.findViewById(R.id.btnHandlerPostdelay); handler_post_delay.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mHandler.postDelayed(runnableDelay, 1000); //1秒後執行 } }); //方法3:sendMessage Button btnSendMessage = (Button) this.findViewById(R.id.btnSendMessage); btnSendMessage.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Message msg = mHandler.obtainMessage(); msg.what = MESSAGE_WXB_1; mHandler.sendMessage(msg); } }); //方法4:Message.sendToTarget Button btnSendtoTarget = (Button) this.findViewById(R.id.btnSendtoTarget); btnSendtoTarget.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Message msg = mHandler.obtainMessage(); msg.what = MESSAGE_WXB_2; msg.sendToTarget(); } }); //在其他線程中發送訊息 //1.post runnableInThread = new Runnable(){ public void run(){ tvOnOtherThread.setText(getString(R.string.postRunnableInThread)); } }; Button btnPost = (Button) this.findViewById(R.id.btnPost); btnPost.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { new Thread(){ @Override public void run() { super.run(); mHandler.post(runnableInThread); } }.start(); } }); //2.postDelay runnableDelayInThread = new Runnable(){ public void run(){ tvOnOtherThread.setText(getString(R.string.postRunnableDelayInThread)); } }; Button btnPostDelay = (Button) this.findViewById(R.id.btnPostDelay); btnPostDelay.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { new Thread(){ @Override public void run() { super.run(); mHandler.postDelayed(runnableDelayInThread, 1000); } }.start(); } }); //3.sendMessage Button btnSendMessage2 = (Button) this.findViewById(R.id.btnSendMessage2); btnSendMessage2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { new Thread(){ @Override public void run() { super.run(); Message msg = mHandler.obtainMessage(); msg.what = MESSAGE_WXB_3; mHandler.sendMessage(msg); } }.start(); } }); //方法4:Message.sendToTarget Button btnSendToTarget2 = (Button) this.findViewById(R.id.btnSendToTarget2); btnSendToTarget2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { new Thread(){ @Override public void run() { super.run(); mHandler.obtainMessage(MESSAGE_WXB_4).sendToTarget(); } }.start(); } }); }}
幾個規則
看完代碼後,在解釋具體類之前,先瞭解幾個規則:
第一, 只有建立view的線程能夠更新此view;一般來說,建立UI的是Android主線程,因此只有在主線程中才能更新UI;
第二, 處理訊息的類是Handler,它依附於建立自己的線程;如果在主線程中建立Handler mHandler,則向mHandler發送的訊息會在主線程中被解析;如果在子線程中建立Handler sHandler,則向sHandler發送的訊息會在子線程中被解析;
第三, 發送訊息有三類方法,Handler的post方法,Handler的sendMessage方法,以及Message的sentToTarget方法,它們最終其實都調用了Handler的sendMessage方法;
第四, 訊息的方法可以即時也可以延時,代表就是post和postDelay,以及sendMessage和sendMessageDelayed;延時發送訊息可以實現很多使用者需要的介面延時效果,例如最常用的SplashWindow。
第五, post類型的方法只需要執行個體化一個Runnable介面,且不需要重載Handler. handleMessage方法,比較簡單易用;
第六, sendMessage和sentToTarget需要重載Handler. handleMessage方法,實現對不同訊息的解析。
Handler
Handler是Android訊息處理中最重要的一個類,不管什麼程式設計語言或者架構,叫Handler的類一般都很重要,也都很複雜,當年的Windows控制代碼類更是讓人一頭霧水。
幸好,Android的Handler很容易理解和使用。它就是一個訊息處理的目標類,其主要作用有兩個:1)它安排要執行的訊息和runnable在未來某個時間點執行;2)處理來自不同線程的訊息並執行操作。
使用Handler要注意以下幾點:
1. Handler屬於建立它的線程,並與線程的訊息迴圈關聯,同時與此線程的訊息佇列(MessageQueue)關聯。
2. Handler發送訊息的方法有兩類:post類和sendMessage類,用法見例子;
3. 如果使用post發送訊息,則訊息中包含的Runnable會在解析訊息時執行;
4. 如果使用sendMessage發送訊息,則需要重載Handler. handleMessage方法,實現對不同訊息的解析
Message
Message是訊息類,它有幾個重要的屬性:
public int what:訊息類型
public int arg1:參數1
public int arg2:參數2
public Object obj:對象參數
以上幾個參數使用者可以隨意定製。
值得注意的有幾點:
1. Android使用訊息池模式來提高訊息的效率,因此一般不適用new來建立訊息,而是使用obtain方法來從訊息池中擷取一個Message的執行個體;Handler類也有obtain方法;
2. Message有一個Handler作為Target,一般來說就是擷取該訊息的所線上程的關聯Handler;因此使用sendToTarget方法發送訊息時,將訊息發給它的Target;
MessageQueue
MessageQueue是訊息佇列,一個線程最多擁有一個訊息佇列,這個類一般不會被程式員直接使用。它的入隊方法enqueueMessage用來將一個Message排入佇列,這個方法是安全執行緒的,因此才保證了Android的訊息處理機制是安全執行緒的。
MessageQueue的next方法用來擷取下一個Message,沒有任何訊息時,主線程常常在此方法處等待。
Runnable
Java的介面,它代表一個可執行檔程式碼片段,如下所示:
public interface Runnable { /** * Starts executing the active part of the class' code. This method is * called when a thread is started that has been created with a class which * implements {@code Runnable}. */ public void run();}
Thread
Java的線程類,大家都應該很熟悉了。
第二個例子:在子線程中建立訊息處理迴圈
第一個例子描述了如何在多個線程中發送訊息,並在主線程中統一接收和處理這些訊息;第二個例子則描述如何在子線程中建立一個訊息迴圈,並從主線程發送訊息給子線程,讓子線程處理這些訊息。
第二個例子中有兩個訊息迴圈,兩個Handler,主線程首先向子線程發送一個訊息,子線程的收到訊息後再向主線程發送一個訊息,主線程收到訊息後更新UI。
例子代碼如下:
http://download.csdn.net/detail/logicteamleader/8827401
原始碼如下:
package com.example.wxbloopinthread;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.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;public class MainActivity extends Activity { private static TextView tv = null; //自訂Message類型 public final static int MESSAGE_WXB_1 = 1; //主線程中建立Handler private static Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch(msg.what){ case MESSAGE_WXB_1: tv.setText("主線程發送,子線程接收訊息後回傳,主線程修改UI"); break; } super.handleMessage(msg); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) this.findViewById(R.id.textView1); //建立子線程 new LooperThread().start(); //點擊按鈕向子線程發送訊息 Button btn = (Button) this.findViewById(R.id.btnSendMessage); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { LooperThread.sHandler.sendEmptyMessage(MESSAGE_WXB_1); } }); } //定義子線程 static class LooperThread extends Thread { public static Handler sHandler = null; public void run() { //建立訊息迴圈 Looper.prepare(); sHandler = new Handler() { public void handleMessage(Message msg) { switch(msg.what){ case MESSAGE_WXB_1: mHandler.sendEmptyMessage(MESSAGE_WXB_1); break; } } }; //開啟訊息迴圈 Looper.loop(); } }}
第二個例子使用了另一個重要的類Looper,它是代表Android中的訊息迴圈處理類。
Looper
Looper被用來建立一個線程的訊息迴圈。線程預設情況下是沒有訊息迴圈的,要建立訊息迴圈,必須先調用Looper.prepare,然後在合適的地方調用Looper.loop,這個loop方法就開始迴圈處理本線程接收到的訊息,直到loop迴圈被停止。
在大部分情況下,Looper都是和Handler一起使用,它通過Handler接收和處理訊息。
使用Looper要注意以下幾點:
1. Android的主線程是預設已經建立了Looper對象的,因此不能在主線程中調用Looper.prepare;
2. Looper.prepare和Looper.loop都是靜態方法,調用時要注意,不要使用new來建立一個Looper;
總結
使用Android訊息的方法有以下幾種:
1. 使用Handler和Runnable,即時或延時發送一個訊息;
2. 使用Handler和Message,即時或延時發送一個訊息,需重載Handler. handleMessage方法;
需要注意的規則有以下幾條:
1. 只有建立view的線程能夠更新此view;一般來說,建立UI的是Android主線程,因此只有在主線程中才能更新UI;
2. 處理訊息的類是Handler,它依附於建立自己的線程;如果在主線程中建立Handler mHandler,則向mHandler發送的訊息會在主線程中被解析;如果在子線程中建立Handler sHandler,則向sHandler發送的訊息會在子線程中被解析;
3. Looper被用來建立一個線程的訊息迴圈,要建立訊息迴圈,必須先調用Looper.prepare,然後在合適的地方調用Looper.loop。
具體的體會,還是要多看代碼才行。