1. 程式人生 > >【Android 開發】: Android 訊息處理機制之一: Handler 與 Message

【Android 開發】: Android 訊息處理機制之一: Handler 與 Message

    最近幾講內容,我們學習了Android中關於多執行緒的一些知識,上一講我們講解了非同步任務 AsyncTask 的操作,Android中還提供了其他的執行緒操作,如Handler Message MessageQueue Looper 等模組,這些就是Android中的訊息處理機制。這部分內容是Android學習過程中的重點和難點。
    現在我們就來學習一下Android的訊息處理,以及剖析一下相關類如Handler和Message類的原始碼,同時使用他們來更新UI主執行緒的操作。因為Android的訊息處理機制內容繁多,我們分為幾部分來學習,大家可以關注這幾講內容,這一講我們重點來學習一下Handler和Message.

一. Handler類介紹

1). 檢視Android官網API Handler類

java.lang.Object
    android.os.Handler
Known Direct Subclasses
AsyncQueryHandler, AsyncQueryHandler.WorkerHandler, HttpAuthHandler, SslErrorHandler

   一個Handler會允許你傳送和處理Message或者Runnable物件關聯到一個執行緒的訊息佇列MessageQueue中,每一個Handler的例項都會關聯一個單一的執行緒和那個執行緒的訊息佇列中。當你建立一個一個新的Handler,它會繫結到你建立的執行緒和這個執行緒訊息佇列中。並且指向好它,它會讓訊息傳遞到關聯好它的訊息佇列中,當它從訊息隊列出隊的時候執行它。這裡他們的如何關聯的不是很懂!

   對於Handler來說有兩種主要的方式: 1. 計劃好訊息和Runnable將來的某一個時間點來執行它 2. 從一個不同的執行緒中執行Handler的入隊操作。分發訊息由下面的幾個方法完成:

   1) post(Runnable),
   2) postAtTime(Runnable, long),
   3) postDelayed(Runnable, long),
   4) sendEmptyMessage(int),
   5) sendMessage(Message),
   6) sendMessageAtTime(Message, long),
   7) sendMessageDelayed(Message, long)

   post方式的方法可以將一個Runable物件排列到訊息佇列中。sendMessage方式的方法可以通過 Handler的handleMessage(Message) 方法攜帶有bundle型別的資料的Message物件到佇列中(需要你實現Handler的子類)。你可以通過上訴兩種方式來出來Handler,你可以允許你的訊息在訊息佇列中準備好就馬上被處理,也可以處理之前指定一些延時讓你實現超時或者基於時間的行為。
   當你的應用程式的程序被建立的時候,它的主執行緒專門用來處理正常執行的主執行緒的訊息佇列,(也就是說UI主執行緒有自己的訊息佇列,所以我們沒必要在UI主執行緒中處理自己的訊息)它關心的是管理頂層的應用物件(activities, broadcast receivers, etc)和他們建立的視窗。你可以建立你自己的執行緒,然後通過Handler與主執行緒溝通。就像上述說的通過post和sendMessage的方式,Runnable和Message會被計劃的執行在Handler的訊息佇列中適時的進行處理。

二. Message類介紹

1). 檢視Android官網API Message類

java.lang.Object
       android.os.Message

定義一個message包含描述資訊和任意的資料物件傳送給Handler。這個物件包含兩個額外的int型別的屬性和一個Object型別的屬性,它可以讓你不需要去做一些強制型別的轉換的操作。如下圖所示:

  1) arg1 和 arg2 都是Message自帶的用來傳遞一些輕量級儲存int型別的資料,比如進度條的資料等。通過這個資料是通過Bundle的方式來轉載的,讀者可以自己查閱原始碼研究。

  2) obj 是Message自帶的Object型別物件,用來傳遞一些物件。相容性最高避免對齊進行型別轉換等。

  3) replyTo 是作為執行緒通訊的時候使用.

  4) what 使用者自定義的訊息碼讓接受者識別訊息種類,int型別。

【注意】: 獲得Message的構造方法最好的方式是呼叫Message.obtain() 和 Handler.obtainMessage()方法。以便能夠更好被回收池所回收[這裡讀者可以研究一下obtain()的原始碼即可明白]。而不是直接用 new Message的方式來獲得Message物件。

三. 程式Demo

1. 實現通過 Thread + Handler + Message 的方式下載網路資料。程式結構如下圖所示

2. 在Manifest.xml中新增網路許可權,這裡不再貼出,讀者可以參考上面一講內容

3. 佈局檔案中 activity_main.xml 中定義Button和ImageView控制元件,這裡不再貼出,讀者可以自己下載原始碼檢視

4. MainActivity.java 程式主程式碼

...
/**
 * 通過 Handler + Message 的方式下載網路資料
 * 通過子執行緒run()方法中下載資料,使用Message攜帶資料,然後用Handler傳送訊息並且處理訊息來更新UI.
 * 
 * @author AHuier
 */
public class MainActivity extends Activity {

    private Button btn;
    private ImageView imageView;
    private String imgPath = "http://f.hiphotos.baidu.com/image/w%3D2048/sign=05793c21bba1cd1105b675208d2ac9fc/43a7d933c895d14350ee3c3272f082025aaf0703.jpg";
    private static final int DOWNLOAD_IMG = 1;
    private ProgressDialog dialog = null;
    
    private Handler handler = new Handler() {

        // 處理子執行緒給我們傳送的訊息。
        @Override
        public void handleMessage(android.os.Message msg) {
            byte[] data = (byte[])msg.obj;
            Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
            imageView.setImageBitmap(bitmap);
            if(msg.what == DOWNLOAD_IMG){
                dialog.dismiss();
            }    
        };
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initComponent();
        btn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                new Thread(new MyThread()).start();
                dialog.show();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    private void initComponent() {
        btn = (Button) this.findViewById(R.id.button1);
        imageView = (ImageView) this.findViewById(R.id.imageView1);
        dialog = new ProgressDialog(this);
        dialog.setTitle("提示");
        dialog.setMessage("正在下載,請稍後...");
        dialog.setCancelable(false);
    }
    
    // 使用Handler Message MessageQueue Looper等方式去訪問網路資源的時候,我們必須要開啟一個子執行緒
    public class MyThread implements Runnable{

        // 在run方法中完成網路耗時的操作
        @Override
        public void run() {
            HttpClient httpClient = new DefaultHttpClient();
            HttpGet httpGet = new HttpGet(imgPath);
            HttpResponse httpResponse = null;
            try {
                httpResponse = httpClient.execute(httpGet);
                if(200 == httpResponse.getStatusLine().getStatusCode()){
                    byte[] data = EntityUtils.toByteArray(httpResponse.getEntity());
                    // 這裡的資料data我們必須傳送給UI的主執行緒,所以我們通過Message的方式來做橋樑。
                    Message message = Message.obtain();
                    message.obj = data;
                    message.what = DOWNLOAD_IMG;
                    handler.sendMessage(message);
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
        }
        
    }

}

5. 程式執行結果

四、程式Demo總結

  1. 上述Demo中的Handler寫法我們只是按圖索驥的方式實現比較標準的寫法,如果考慮android記憶體機制的情況下,private Handler的方式定義成為靜態的會更好。在Android原始碼中Handler一般是定義成 protect 許可權的。

2.Handler主要是用來負責傳送訊息和處理訊息的。

  3.基於處理,其實這裡面蘊含著一個訊息佇列的概念,這裡為什麼我們獲得訊息需要用到Obtain()的方式,而不是通過new的方式構建一個訊息,這個問題下一講我們會通過剖析Message中Obtain()的原始碼來討論。

更多關於Android訊息處理機制請點選以下相關連結: