Android的消息機制

Android的消息機制,用Android線程間通信的Message機制,Android中Handler的使用方法——在子線程中更新界面,handler機制

 

Android的消息機制(一)

 

android 有一種叫消息隊列的說法,這裏我們可以這樣理解:假如一個隧道就是一個消息隊列,那麼裏面的每一部汽車就是一個一個消息,這裏我們先忽略掉超車等種種因素,只那麼先進隧道的車將會先出,這個機制跟我們android 的消息機制是一樣的。

一、    角色描述

1.Looper:(相當於隧道) 一個線程可以產生一個Looper 對象,由它來管理此線程裏的Message Queue( 車隊,消息隧道) 。

2.Handler: 你可以構造Handler 對象來與Looper 溝通,以便push 新消息到Message Queue 裏;或者接收Looper( 從Message Queue 取出) 所送來的消息。

3. Message Queue( 消息隊列): 用來存放線程放入的消息。

4 .線程:UI thread 通常就是main thread ,而Android 啓動程序時會替它建立一個Message Queue 。

每一個線程裏可含有一個Looper 對象以及一個MessageQueue 數據結構。在你的應用程序裏,可以定義Handler 的子類別來接收Looper 所送出的消息。

 

在你的Android 程序裏,新誕生一個線程,或執行 (Thread) 時,並不會自動建立其Message Loop 。

Android 裏並沒有Global 的Message Queue 數據結構,例如,不同APK 裏的對象不能透過Massage Queue 來交換訊息(Message) 。

例如:線程A 的Handler 對象可以傳遞消息給別的線程,讓別的線程B 或C 等能送消息來給線程A( 存於A 的Message Queue 裏) 。

線程A 的Message Queue 裏的訊息,只有線程A 所屬的對象可以處理。

使用Looper.myLooper 可以取得當前線程的Looper 對象。

使用mHandler = new EevntHandler(Looper.myLooper()); 可用來構造當前線程的Handler 對象;其中,EevntHandler 是自已實現的Handler 的子類別。

使用mHandler = new EevntHandler(Looper.getMainLooper()); 可誕生用來處理main 線程的Handler 對象;其中,EevntHandler 是自已實現的Handler 的子類別。

 

這樣描述可能太抽像,下面舉幾個實際的例子來說明:

二、    舉例

1.  同線程內不同組件間的消息傳遞

Looper 類用來管理特定線程內對象之間的消息交換(Message Exchange) 。你的應用程序可以產生許多個線程。而一個線程可以有許多個組件,這些組件之間常常需要互相交換訊息。如果有這種需要,您可以替線程構造一個Looper對象,來擔任訊息交換的管理工作。Looper 對象會建立一個MessageQueue 數據結構來存放各對象傳來的消息( 包括UI 事件或System 事件等) 。如下圖:

 

每一個線程裏可含有一個Looper 對象以及一個MessageQueue 數據結構。在你的應用程序裏,可以定義Handler 的子類別來接收Looper 所送出的消息。

同線程不同組件之間的消息傳遞:

public class Activity1 extends Activity implements OnClickListener{

       Button button null ;

       TextView text null ;

       @Override

       protected void onCreate(Bundle savedInstanceState) {

              super .onCreate(savedInstanceState);

              setContentView(R.layout. activity1 );        

              button = (Button)findViewById(R.id. btn );

              button .setOnClickListener( this );

              text = (TextView)findViewById(R.id. content );

       }

       public void onClick(View v) {

              switch (v.getId()) {

              case R.id. btn :

                     Looper looper = Looper.myLooper (); // 取得當前線程裏的looper

                     MyHandler mHandler = new MyHandler(looper); // 構造一個handler 使之可與looper 通信

                     //buton 等組件可以由mHandler 將消息傳給looper 後, 再放入messageQueue 中, 同時mHandler 也可以接受來自looper 消息

                     mHandler.removeMessages(0);

                     String msgStr = " 主線程不同組件通信: 消息來自button" ;

                     Message m = mHandler.obtainMessage(1, 1, 1, msgStr); // 構造要傳遞的消息

                     mHandler.sendMessage(m); // 發送消息: 系統會自動調用handleMessage 方法來處理消息

                     break ;

                }            

       }     

       private class MyHandler extends Handler{             

              public MyHandler(Looper looper){

                     super (looper);

              }

              @Override

              public void handleMessage(Message msg) { // 處理消息

                     text .setText(msg. obj .toString());

              }            

       }

}

 

說明:

此程序啓動時,當前線程( 即主線程, main thread) 已誕生了一個Looper 對象,並且有了一個MessageQueue 數據結構。

    looper = Looper.myLooper (); 

調用Looper 類別的靜態myLooper() 函數,以取得目前線程裏的Looper 對象.

mHandler = new MyHandler (looper);

構造一個MyHandler 對象來與Looper 溝通。Activity 等對象可以藉由MyHandler 對象來將消息傳給Looper ,然後放入MessageQueue 裏;MyHandler 對象也扮演Listener 的角色,可接收Looper 對象所送來的消息。

Message m = mHandler.obtainMessage(1, 1, 1, obj);

先構造一個Message 對象,並將數據存入對象裏。

mHandler.sendMessage(m);

就透過mHandler 對象而將消息m 傳給Looper ,然後放入MessageQueue 裏。

此時,Looper 對象看到MessageQueue 裏有消息m ,就將它廣播出去,mHandler 對象接到此訊息時,會呼叫其handleMessage() 函數來處理,於是輸出"This my message!" 於畫面上,

Android消息處理機制(二)

角色綜述(回顧):

   (1)UI thread 通常就是main thread ,而Android 啓動程序時會替它建立一個MessageQueue 。

(2) 當然需要一個Looper 對象,來管理該MessageQueue 。

(3) 我們可以構造Handler 對象來push 新消息到Message Queue 裏;或者接收Looper( 從Message Queue 取出) 所送來的消息。

(4) 線程A 的Handler 對象可以傳遞給別的線程,讓別的線程B 或C 等能送訊息來給線程A( 存於A 的Message Queue 裏) 。

(5) 線程A 的Message Queue 裏的消息,只有線程A 所屬的對象可以處理。

 

子線程傳遞消息給主線程

public class Activity2 extends Activity implements OnClickListener{

       Button button null ;

       TextView text null ;

       MyHandler mHandler null ;

       Thread thread ;

       @Override

       protected void onCreate(Bundle savedInstanceState) {

              super .onCreate(savedInstanceState);

              setContentView(R.layout. activity1 );        

              button = (Button)findViewById(R.id. btn );

              button .setOnClickListener( this );

              text = (TextView)findViewById(R.id. content );

       }

       public void onClick(View v) {

              switch (v.getId()) {

              case R.id. btn :

                     thread new MyThread();

                     thread .start();

                     break ;

              }            

       }     

       private class MyHandler extends Handler{             

              public MyHandler(Looper looper){

                     super (looper);

              }

              @Override

              public void handleMessage(Message msg) { // 處理消息

                     text .setText(msg. obj .toString());

              }            

       }

       private class MyThread extends Thread{

              @Override

              public void run() {

                     Looper curLooper = Looper.myLooper ();

                     Looper mainLooper = Looper.getMainLooper ();

                     String msg ;

                     if (curLooper== null ){

                            mHandler new MyHandler(mainLooper);

                            msg = "curLooper is null" ;

                     } else {

                            mHandler new MyHandler(curLooper);

                            msg = "This is curLooper" ;

                     }

                     mHandler .removeMessages(0);

                     Message m = mHandler .obtainMessage(1, 1, 1, msg);

                     mHandler .sendMessage(m);

              }            

       }

}

說明:

Android 會自動替主線程建立Message Queue 。在這個子線程裏並沒有建立Message Queue 。所以,myLooper 值爲null ,而mainLooper 則指向主線程裏的Looper 。於是,執行到:

mHandler = new MyHandler (mainLooper);

mHandler 屬於主線程。

   mHandler.sendMessage(m);

就將m 消息存入到主線程的Message Queue 裏。mainLooper 看到Message Queue 裏有訊息,就會作出處理,於是由主線程執行到mHandler 的handleMessage() 來處理消息。

用Android線程間通信的Message機制

在Android 下面也有多線程 的概念,在C/C++中,子線程可以是一個函數 ,一般都是一個帶有循環的函數,來處理某些數據 ,優先線程只是一個複雜的運算過程,所以可能不需要while循環,運算完成,函數結束,線程就銷燬。對於那些需要控制的線程,一般我們都是和互斥鎖相互關聯,從而來控制線程的進度,一般我們創建子線程,一種線程是很常見的,那就是帶有消息循環的線程。
消息循環是一個很有用的線程方式,曾經自己用C在Linux下面實現一個消息循環的機制 ,往消息隊列裏添加數據,然後異步的等待消息的返回。當消息隊列爲空的時候就會掛起線程,等待新的消息的加入。這是一個很通用的機制。
在 Android,這裏的線程分爲有消息循環的線程和沒有消息循環的線程,有消息循環的線程一般都會有一個Looper,這個事android的新概念。我 們的主線程(UI線程)就是一個消息循環的線程。針對這種消息循環的機制,我們引入一個新的機制Handle,我們有消息循環,就要往消息循環裏面發送相 應的消息,自定義 消息一般都會有自己對應的處理,消息的發送和清除,消息的的處理,把這些都封裝在Handle裏面,注意Handle只是針對那些有Looper的線程,不管是UI線程還是子線程,只要你有Looper,我就可以往你的消息隊列裏面添加東西,並做相應的處理。
但是這裏還有一點,就是只要是關於UI相關的東西,就不能放在子線程中,因爲子線程是不能操作UI的,只能進行數據、系統 等其他非UI的操作。
那麼什麼情況下面我們的子線程才能看做是一個有Looper的線程呢?我們如何得到它Looper的句柄呢?
Looper.myLooper();獲得當前的Looper
Looper.getMainLooper () 獲得UI線程的Lopper
我們看看Handle的初始化函數,如果沒有參數,那麼他就默認使用的是當前的Looper,如果有Looper參數,就是用對應的線程的Looper。
如 果一個線程中調用Looper.prepare(),那麼系統就會自動的爲該線程建立一個消息隊列,然後調用 Looper.loop();之後就進入了消息循環,這個之後就可以發消息、取消息、和處理消息。這個如何發送消息和如何處理消息可以再其他的線程中通過 Handle來做,但前提是我們的Hanle知道這個子線程的Looper,但是你如果不是在子線程運行 Looper.myLooper(),一般是得不到子線程的looper的。
public void run() {
            synchronized (mLock) {
                Looper.prepare();
               //do something
            }
            Looper.loop();
        }
所以很多人都是這樣做的:我直接在子線程中新建handle,然後在子線程中發送消息,這樣的話就失去了我們多線程的意義了。
class myThread extends Thread{
             private EHandler mHandler ;
             public void run() {
                 Looper myLooper, mainLooper;
                 myLooper = Looper.myLooper ();
                mainLooper = Looper.getMainLooper ();
                String obj;
                if (myLooper == null ){
                         mHandler = new EHandler(mainLooper);
                         obj = "current thread has no looper!" ;
                }
                else {
                     mHandler = new EHandler(myLooper);
                     obj = "This is from current thread." ;
                }
                mHandler .removeMessages(0);
                Message m = mHandler .obtainMessage(1, 1, 1, obj);
                mHandler .sendMessage(m);
             }
  }
可以讓其他的線程來控制我們的handle,可以把 private EHandler mHandler ;放在外面,這樣我們的發消息和處理消息都可以在外面來定義,這樣增加程序 代碼 的美觀,結構更加清晰。
對如任何的Handle,裏面必須要重載一個函數
public void handleMessage(Message msg)
這個函數就是我們的消息處理,如何處理,這裏完全取決於你,然後通過 obtainMessage和 sendMessage等來生成和發送消息, removeMessages(0)來清除消息隊列。Google 真是太智慧了,這種框架 的產生,我們寫代碼更加輕鬆了。
有的時候,我們的子線程想去改變UI了,這個時候千萬不要再子線程中去修改,獲得UI線程的Looper,然後發送消息即可。
我們看看Goole Music App的源代碼 。
在MediaPlaybackActivity.java 中,我們可以看一下再OnCreate中的有這樣的兩句:
        mAlbumArtWorker = new Worker("album art worker");
        mAlbumArtHandler = new AlbumArtHandler(mAlbumArtWorker.getLooper());
很 明顯這兩句,是構建了一個子線程。並且這個子線程還是Looper的子線程,這裏很牛逼的使用了 mAlbumArtWorker.getLooper()這個函數,因爲我們知道,我們能夠得到子線程的Looper的途徑只有一個:就是在子線程中調用 Looper.myLooper (),並且這個函數還要在我們perpare之後調用才能得到正確的Looper,但是他這裏用了一個這樣的什麼東東 getLooper,不知道它是如何實現的?
這裏有一個大概的思路,我們在子線程的的prepare之後調用 myLooper ()這個方法,然後保存在一個成員變量中,這個getLooper就返回這個東西,但是這裏會碰到多線程的一個很突出的問題,同步。我們在父線程中調用 mAlbumArtWorker.getLooper(),但是想要這個返回正確的looper就必須要求我們的子線程運行了prepare,但是這個東 西實在子線程運行的,我們如何保證呢?
我們看Google是如何實現的?
   private class Worker implements Runnable {
        private final Object mLock = new Object();
        private Looper mLooper;
        
        /**
         * Creates a worker thread with the given name. The thread
         * then runs a [email=%7B@link]{@link [/email] android.os.Looper}.
         * @param name A name for the new thread
         */
        Worker(String name) {
            Thread t = new Thread(null, this, name);
            t.setPriority(Thread.MIN_PRIORITY);
            t.start();
            synchronized (mLock) {
                while (mLooper == null) {
                    try {
                        mLock.wait();
                    } catch (InterruptedException ex) {
                    }
                }
            }
        }
        
        public Looper getLooper() {
            return mLooper;
        }
        
        public void run() {
            synchronized (mLock) {
                Looper.prepare();
                mLooper = Looper.myLooper();
                mLock.notifyAll();
            }
            Looper.loop();
        }
        
        public void quit() {
            mLooper.quit();
        }
    }
我 們知道,一個線程類的構造函數是在主線程中完成的,所以在我們的 Worker的構造函數中我們創佳一個線程,然後讓這個線程運行,這一這個線程的創建是指定一個 Runnabl,這裏就是我們的Worker本身,在主線程調用 t.start();,這後,我們子線程已經創建,並且開始執行work的run方法。然後下面的代碼很藝術:
synchronized (mLock) {
                while (mLooper == null) {
                    try {
                        mLock.wait();
                    } catch (InterruptedException ex) {
                    }
                }
            }
我們開始等待我們的子線程給mLooper賦值,如果不賦值我們就繼續等,然後我們的子線程在運行run方法之後,在給 mLooper賦值之後,通知worker夠着函數中的wait,然後我們的構造函數才能完成,所以我們說:
mAlbumArtWorker = new Worker("album art worker");
這句本身就是阻塞的,它創建了一個子線程,開啓了子線程,並且等待子線程給mLooper賦值,賦值完成之後,這個函數才返回,這樣才能保證我們的子線程的Looper的獲取 絕對是正確的,這個構思很有創意。值得借鑑

Android中Handler的使用方法——在子線程中更新界面

本文主要介紹Android的Handler的使用方法。Handler可以發送Messsage和Runnable對象到與其相關聯的線程的消息隊列。每個Handler對象與創建它的線程相關聯,並且每個Handler對象只能與一個線程相關聯。

1.    Handler一般有兩種用途:1)執行計劃任務,你可以再預定的實現執行某些任務,可以模擬定時器。2)線程間通信。在Android的應用啓動時,會 創建一個主線程,主線程會創建一個消息隊列來處理各種消息。當你創建子線程時,你可以再你的子線程中拿到父線程中創建的Handler對象,就可以通過該 對象向父線程的消息隊列發送消息了。由於Android要求在UI線程中更新界面,因此,可以通過該方法在其它線程中更新界面。 
◆ 通過Runnable在子線程中更新界面的例子

1.○ 在onCreate中創建Handler 
public class HandlerTestApp extends Activity { 
        Handler mHandler; 
        TextView mText; 
        /** Called when the activity is first created. */ 
       @Override 
       public void onCreate(Bundle savedInstanceState) { 
           super.onCreate(savedInstanceState); 
           setContentView(R.layout.main); 
           mHandler = new Handler();//創建Handler 
           mText = (TextView) findViewById(R.id.text0);//一個TextView 

       } 
     ○ 構建Runnable對象,在runnable中更新界面,此處,我們修改了TextView的文字.此處需要說明的是,Runnable對象可以再主線程中創建,也可以再子線程中創建。我們此處是在子線程中創建的。  
     Runnable mRunnable0 = new Runnable() 
    { 
                @Override 
                public void run() { 
                        mText.setText("This is Update from ohter thread, Mouse DOWN"); 
                } 
    }; 
?    ○ 創建子線程,在線程的run函數中,我們向主線程的消息隊列發送了一個runnable來更新界面。

    private void updateUIByRunnable(){ 
          new Thread()  
         {  
               //Message msg = mHandler.obtainMessage();  
              public void run()  
             { 

                   //mText.setText("This is Update from ohter thread, Mouse DOWN");//這句將拋出異常 
                   mHandler.post(mRunnable0);  
             }  

         }.start();

     }

◆ 用Message在子線程中來更新界面

1.    用Message更新界面與Runnable更新界面類似,只是需要修改幾個地方。
    ○ 實現自己的Handler,對消息進行處理

    private class MyHandler extends Handler 
    { 

        @Override 
        public void handleMessage(Message msg) { 
            super.handleMessage(msg); 
            switch(msg.what) 
            { 
            case UPDATE ://在收到消息時,對界面進行更新 
                mText.setText("This update by message"); 
                break; 
            } 
        } 
    }

    ○ 在新的線程中發送消息     
    private void updateByMessage() 
    { 
        //匿名對象 
         new Thread() 
         { 
                public void run() 
                { 
                    //mText.setText("This is Update from ohter thread, Mouse DOWN");

                    //UPDATE是一個自己定義的整數,代表了消息ID 
                    Message msg = mHandler.obtainMessage(UPDATE); 
                    mHandler.sendMessage(msg); 

                } 
         }.start(); 
    }

 

AsyncTask與handler

AsyncTask實際上就是一個線程池,AsyncTask在代碼上比handler要輕量級別,而實際上要比handler更耗資源,因爲AsyncTask底層是一個線程池!而Handler僅僅就是發送了一個消息隊列,連線程都沒有開。
但是,如果異步任務的數據特別龐大,AsyncTask這種線程池結構的優勢就體現出來了


android的ui線程操作並不是安全的,並且和用戶直接進行界面交互的操作都必須在ui線程中進行纔可以。這種模式叫做單線程模式。

我們在單線程模式下編程一定要注意:不要阻塞ui線程、確保只在ui線程中訪問ui組件

當我們要執行一個複雜耗時的算法並且最終要將計算結果反映到ui上時,我們會發現,我們根本沒辦法同時保證上面的兩點要求;我們肯定會想到開啓一個新的線程,讓這個複雜耗時的任務到後臺去執行,但是執行完畢了呢?我們發現,我們無法再與ui進行交互了。

爲了解決這種情況,android爲我們提供了很多辦法。

1)、handler和message機制:通過顯示的拋出、捕獲消息與ui進行交互;

2)、Activity.runOnUiThread(Runnable):如果當前線程爲ui線程,則立即執行;否則,將參數中的線程操作放入到ui線程的事件隊列中,等待執行。

3)、View.post(Runnable):將操作放入到message隊列中,如果放入成功,該操作將會在ui線程中執行,並返回true,否則返回false

4)、View.postDelayed(Runnable, long)跟第三條基本一樣,只不過添加了一個延遲時間。

5)、android1.5以後爲我們提供了一個工具類來搞定這個問題AsyncTask.

AsyncTask是抽象類,定義了三種泛型類型 Params,Progress,Result。

Params 啓動任務執行的輸入參數,比如HTTP請求的URL

Progress 後臺任務執行的百分比。

Result 後臺執行任務最終返回的結果,比如String

用程序調用,開發者需要做的就是實現這些方法。

1) 子類化AsyncTask

2) 實現AsyncTask中定義的下面一個或幾個方法

onPreExecute(),該方法將在執行實際的後臺操作前被UI thread調用。可以在該方法中做一些準備工作,如在界面上顯示一個進度條。

doInBackground(Params…),將在onPreExecute 方法執行後馬上執行,該方法運行在後臺線程中。這裏將主要負責執行那些很耗時的後臺計算工作。可以調用 publishProgress方法來更新實時的任務進度。該方法是抽象方法,子類必須實現。

onProgressUpdate(Progress…),在publishProgress方法被調用後,UI thread將調用這個方法從而在界面上展示任務的進展情況,例如通過一個進度條進行展示。

onPostExecute(Result),在doInBackground 執行完成後,onPostExecute 方法將被UI thread調用,後臺的計算結果將通過該方法傳遞到UI thread.

爲了正確的使用AsyncTask類,以下是幾條必須遵守的準則:

1) Task的實例必須在UI thread中創建

2) execute方法必須在UI thread中調用

3) 不要手動的調用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)這幾個方法

4) 該task只能被執行一次,否則多次調用時將會出現異常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<span style="font-size: 14px;">    package cn.com.chenzheng_java; 
        
    import android.os.AsyncTask; 
    /**
     
     * @author chenzheng_java
     * @description 異步任務AcyncTask示例
     *    
     */ 
    public class MyAsyncTask extends AsyncTask<String, Integer, Object> { 
        
     /**
     * 該方法由ui線程進行調用,用戶可以在這裏盡情的訪問ui組件。
     * 很多時候,我們會在這裏顯示一個進度條啥的,以示後臺正在
     * 執行某項功能。
     */ 
     @Override 
     protected void onPreExecute() { 
     super.onPreExecute(); 
     
        
     /**
     * 該方法由後臺進程進行調用,進行主要的耗時的那些計算。
     * 該方法在onPreExecute方法之後進行調用。當然在執行過程中
     * 我們可以每隔多少秒就調用一次publishProgress方法,更新
     * 進度信息
     */ 
     @Override 
     protected Object doInBackground(String... params) { 
     return null
     
        
        
     /**
     * doInBackground中調用了publishProgress之後,ui線程就會
     * 調用該方法。你可以在這裏動態的改變進度條的進度,讓用戶知道
     * 當前的進度。
     */ 
     @Override 
     protected void onProgressUpdate(Integer... values) { 
     super.onProgressUpdate(values); 
     
        
     /**
     * 當doInBackground執行完畢之後,由ui線程調用。可以在這裏
     * 返回我們計算的最終結果給用戶。
     */ 
     @Override 
     protected void onPostExecute(Object result) { 
     super.onPostExecute(result); 
     
    
</span>


轉自:http://www.cnblogs.com/-OYK/archive/2011/08/03/2126657.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章