Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Handler、Looper消息傳遞機制

Handler、Looper消息傳遞機制

編輯:關於Android編程

一、Handler消息傳遞機制初步認識:

(一)、引入:

子線程沒有辦法對UI界面上的內容進行操作,如果操作,將拋出異常:CalledFromWrongThreadException

為了實現子線程中操作UI界面,Android中引入了Handler消息傳遞機制,目的是打破對主線程的依賴性。

 

什麼是Handler?

handler通俗一點講就是用來在各個線程之間發送數據的處理對象。在任何線程中,只要獲得了另一個線程的handler,則可以通過 handler.sendMessage(message)方法向那個線程發送數據。基於這個機制,我們在處理多線程的時候可以新建一個thread,這個thread擁有UI線程中的一個handler。當thread處理完一些耗時的操作後通過傳遞過來的handler向UI線程發送數據,由UI線程去更新界面。

主線程:運行所有UI組件,它通過一個消息隊列來完成此任務。設備會將用戶的每項操作轉換為消息,並將它們放入正在運行的消息隊列中。主線程位於一個循環中,並處理每條消息。如果任何一個消息用時超過5秒,Android將拋出ANR。所以一個任務用時超過5秒,應該在一個獨立線程中完成它,或者延遲處理它,當主線程空閒下來再返回來處理它。

(二)、常用類:(Handler、Looper、Message、MessageQueue)

  1. Message:消息,其中包含了消息ID,消息處理對象以及處理的數據等,由MessageQueue統一列隊,終由Handler處理。
  2. Handler:處理者,負責Message的發送及處理。使用Handler時,需要實現handleMessage(Message msg)方法來對特定的Message進行處理,例如更新UI等。Handler類的主要作用:(有兩個主要作用)1)、在工作線程中發送消息;2)、在主線程中獲取、並處理消息。
  3. MessageQueue:消息隊列,用來存放Handler發送過來的消息,並按照FIFO規則執行。當然,存放Message並非實際意義的保存,而是將Message串聯起來的,等待Looper的抽取。
  4. Looper:消息泵,不斷地從MessageQueue中抽取Message執行。因此,一個MessageQueue需要一個Looper。
  5. Thread:線程,負責調度整個消息循環,即消息循環的執行場所。

(三)、Handler、Looper、Message、MessageQueue之間的關系:

Handler,Looper和MessageQueue的三角關系

  1. Looper和MessageQueue一一對應,創建一個Looper的同時,會創建一個MessageQueue;
  2. 而Handler與它們的關系,只是簡單的聚集關系,即Handler裡會引用當前線程裡的特定Looper和MessageQueue;
  3. 在一個線程中,只能有一個Looper和MessageQueue,但是可以有多個Handler,而且這些Handler可以共享一個Looper和MessageQueue;
  4. Message被存放在MessageQueue中,一個MessageQueue中可以包含多個Message對象。

【備注:】

Looper對象用來為一個線程開啟一個消息循環,從而操作MessageQueue;

默認情況下,Android創建的線程沒有開啟消息循環Looper,但是主線程例外。

系統自動為主線程創建Looper對象,開啟消息循環;

所以主線程中使用new來創建Handler對象。而子線程中不能直接new來創建Handler對象就會異常。

子線程中創建Handler對象,步驟如下:

Looper.prepare();

Handler handler = new Handler() {

//handlemessage(){}

 

}

Looper.loop();

 

(四)、Handler類中常用方法:

  1. handleMessage() 用在主線程中,構造Handler對象時,重寫handleMessage()方法。該方法根據工作線程返回的消息標識,來分別執行不同的操作。
  2. sendEmptyMessage() 用在工作線程中,發送空消息。
  3. sendMessage() 用在工作線程中,立即發送消息。

(五)、Message消息類中常用屬性:

  1. arg1 用來存放整型數據
  2. arg2 用來存放整型數據
  3. obj 用來存放Object數據
  4. what 用於指定用戶自定義的消息代碼,這樣便於主線程接收後,根據消息代碼不同而執行不同的相應操作。

 

【重點】:使用Message需要注意4點:

1、Message雖然也可以通過new來獲取,但是通常使用Message.obtain()或Handler.obtainMessage()方法來從消息池中獲得空消息對象,以節省資源;

2、如果一個Message只需要攜帶簡單的int型數據,應優先使用arg1和arg2屬性來傳遞數據,這樣比其他方式節省內存;

3、盡可能使用Message.what來標識信息,以便用不同的方式處理Message;

4、如果需要從工作線程返回很多數據信息,可以借助Bundle對象將這些數據集中到一起,然後存放到obj屬性中,再返回到主線程。

 

(六)、示例代碼一:【重點】

private Handler handler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text_main_info = (TextView) findViewById(R.id.text_main_info);
        pDialog = new ProgressDialog(MainActivity.this);
        pDialog.setMessage("Loading...");
        image_main = (ImageView) findViewById(R.id.image_main);

        // 主線程中的handler對象會處理工作線程中發送的Message。根據Message的不同編號進行相應的操作。
        handler = new Handler() {
                public void handleMessage(android.os.Message msg) {
                        // 工作線程中要發送的信息全都被放到了Message對象中,也就是上面的參數msg中。要進行操作就要先取出msg中傳遞的數據。
                        switch (msg.what) {
                        case 0:
                                // 工作線程發送what為0的信息代表線程開啟了。主線程中相應的顯示一個進度對話框
                                pDialog.show();
                                break;
                        case 1:
                                // 工作線程發送what為1的信息代表要線程已經將需要的數據加載完畢。本案例中就需要將該數據獲取到,顯示到指定ImageView控件中即可。
                                image_main.setImageBitmap((Bitmap) msg.obj);
                                break;
                        case 2:
                                // 工作線程發送what為2的信息代表工作線程結束。本案例中,主線程只需要將進度對話框取消即可。
                                pDialog.dismiss();
                                break;
                        }
                }
        };

        new Thread(new Runnable() {
                @Override
                public void run() {
                        // 當工作線程剛開始啟動時,希望顯示進度對話框,此時讓handler發送一個空信息即可。
                        // 當發送這個信息後,主線程會回調handler對象中的handleMessage()方法。handleMessage()方法中
                        // 會根據message的what種類來執行不同的操作。
                        handler.sendEmptyMessage(0);

                        // 工作線程執行訪問網絡,加載網絡圖片的任務。
                        byte[] data = HttpClientHelper.loadByteFromURL(urlString);
                        // 工作線程將網絡訪問獲取的字節數組生成Bitmap位圖。
                        Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,
                                        data.length);
                        // 工作線程將要發送給主線程的信息都放到一個Message信息對象中。
                        // 而Message對象的構建建議使用obtain()方法生成,而不建議用new來生成。
                        Message msgMessage = Message.obtain();
                        // 將需要傳遞到主線程的數據放到Message對象的obj屬性中,以便於傳遞到主線程。
                        msgMessage.obj = bitmap;
                        // Message對象的what屬性是為了區別信息種類,而方便主線程中根據這些類別做相應的操作。
                        msgMessage.what = 1;
                        // handler對象攜帶著Message中的數據返回到主線程
                        handler.sendMessage(msgMessage);

                        // handler再發出一個空信息,目的是告訴主線程工作線程的任務執行完畢。一般主線程會接收到這個消息後,
                        // 將進度對話框關閉
                        handler.sendEmptyMessage(2);
                }
        }).start();
}

 

 

 

 

 

(七)、示例代碼二:圖片定時切換:

1、思路:利用多線程,子線程每隔2秒發送一個消息給主線程,主線程中Handler接收消息,並更新ImageView中的圖片。這樣就實現了循環切換的動態效果。

 

handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                case 0:
                        image_main_pic.setImageResource(imageId[position++]);
                        if (position >= imageId.length) {
                                position = 0;
                        }
                        break;
                default:
                        break;
                }
        }
};

// 第一種解決辦法:利用Thread和Thread的sleep
// new Thread(new Runnable() {
// @Override
// public void run() {
// while (flag) {
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// handler.sendEmptyMessage(0);
// }
// }
// }).start();

// 第二種解決辦法:利用Timer定時器和定時器的schedule()方法。
//schedule()方法中有三個參數:
/*第一個:表示定時任務TimerTask。 TimerTask 類實現了Runnable接口,所以要new  TimerTask(),一定要實現run()方法。
第二個:表示第一次執行前的等待延遲時間;
第三個:表示兩次定時任務執行之間的間隔時間。*/
new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
                handler.sendEmptyMessage(0);
                //sendEmptyMessage()方法等同於以下幾句話。所以。如果只發送一個what,就可以使用sendEmptyMessage()。這樣更簡單。
                //Message message = Message.obtain();
                // Message message2 = handler.obtainMessage();
                //message.what = 0;
                //handler.sendMessage(message);
        }
}, 1, 1500);

 

 

 

(八)、示例代碼三:打地鼠:

 

handler = new Handler() {

@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

switch (msg.what) {

case 0:

image_main_mouse.setVisibility(View.VISIBLE);

// 獲取0-8之間的隨機數[0,8),半閉合區間。目的是隨機獲取給定的8個坐標位置。

// 獲取隨機數有兩種辦法:

// 方法一:

//Math.random()*positionArr.length,注意偽隨機數是個半閉合區間。即隨機數不可能為positionArr.length

// 方法二:

//new Random().nextInt(positionArr.length);

position = (int) (Math.random() * positionArr.length);

image_main_mouse.setX(positionArr[position][0]);

image_main_mouse.setY(positionArr[position][1]);

break;

default:

break;

}

}

};

 

image_main_mouse = (ImageView) findViewById(R.id.image_main_mouse);

 

new Thread(new Runnable() {

@Override

public void run() {

while (flag) {

try {

// 獲取0-500之間的隨機數,再加上500,目的是讓老鼠出現的間隙時間也隨機,最短出現間隙為500毫秒,最長為999毫秒。

Thread.sleep(new Random().nextInt(500) + 500);

} catch (InterruptedException e) {

e.printStackTrace();

}

handler.sendEmptyMessage(0);

}

}

}).start();

 

image_main_mouse.setOnTouchListener(new View.OnTouchListener() {

@Override

public boolean onTouch(View v, MotionEvent event) {

image_main_mouse.setVisibility(View.GONE);

return false;

}

});

 

 

【備注:】

在案例《打地鼠》中使用到了橫豎屏幕切換,請參考以下代碼:

關於Android中Activity的橫豎屏切換問題可以通過AndroidManifest.xml文件中的Activity來配置:

android:screenOrientation=["unspecified" | "user" | "behind" |"landscape" | "portrait" | "sensor" | "nonsensor"]
screenOrientation 用來指定Activity的在設備上顯示的方向,每個值代表如下含義:
"unspecified" 默認值 由系統來判斷顯示方向.判定的策略是和設備相關的,所以不同的設備會有不同的顯示方向. "landscape" 橫屏顯示(寬比高要長) "portrait" 豎屏顯示(高比寬要長) "user" 用戶當前首選的方向 "behind" 和該Activity下面的那個Activity的方向一致(在Activity堆棧中的) "sensor" 有物理的感應器來決定。如果用戶旋轉設備這屏幕會橫豎屏切換。 "nosensor" 忽略物理感應器,這樣就不會隨著用戶旋轉設備而更改了 ( "unspecified"設置除外 )。

 

二、Handler、Looper源碼分析:

(一)、Handler的概念:

  1. Handler是用於發送和處理消息和一個線程的MessageQueue相關聯的Runable對象。
  2. 每個Handler實例關聯到一個單一線程和線程的messagequeue。
  3. 當您創建一個Handler,從你創建它的時候開始,它就綁定到創建它的線程以及對應的消息隊列,handler將發送消息到消息隊列,並處理從消息隊列中取出的消息。

Handler的主要用途有兩個:

(1)、在將來的某個時刻執行消息或一個runnable;

(2)、為運行在不同線程中的多個任務排隊。

 

主要依靠以下方法來完成消息調度:

  • post(Runnable)、
  • postAtTime(Runnable, long)、
  • postDelayed(Runnable, long)、
  • sendEmptyMessage(int)、
  • sendMessage(Message)、
  • sendMessageAtTime(Message)、
  • sendMessageDelayed(Message, long)

【備注:】

  • post方法是當到Runable對象到達就被插入到消息隊列;
  • sendMessage方法允許你把一個包含有信息的Message插入消息隊列,它會在Handler的handlerMessage(Message)方法中執行(該方法要求在Handler的子類中實現)。
  • 當Handler post或者send消息的時候,可以在消息隊列准備好的時候立刻執行,或者指定一個延遲處理或絕對時間對它進行處理,後兩個是實現了timeout、ticks或者其他timing-based的行為。
  • 當你的應用創建一個進程時,其主線程(UI線程)會運行一個消息隊列,負責管理優先級最高的應用程序對象(Activity、廣播接收器等)和任何他們創建的windows。你也可以創建自己的線程,通過handler與主線程進行通信,在新創建的線程中handler通過調用post或sendMessage方法,將傳入的Runnable或者Message插入到消息隊列中,並且在適當的時候得到處理。

 

 

(二)、Handler的用法:

當你實例化一個Handler的時候可以使用Callback接口來避免寫自定義的Handler子類。這裡的機制類似與Thread與runable接口的關系。

在Handler裡面,子類要處理消息的話必須重寫handleMessage()這個方法,因為在handler裡面它是個空方法:

 

 

(三)、源碼分析:

A、Handler.java:(3個屬性,9個方法)

3個屬性:

  • finalMessageQueue mQueue;
  • finalLooper mLooper;
  • finalCallback mCallback;

 

9個方法:

  • public booleanhandleMessage(Message msg);
  • public final MessageobtainMessage()
  • public final booleansendMessage(Message msg)
  • public final booleansendEmptyMessage(int what)
  • public final booleanpost(Runnable r)
  • public final boolean postAtTime(Runnable r, long uptimeMillis)
  • public void dispatchMessage(Message msg)
  • public boolean sendMessageAtTime(Message msg, long uptimeMillis)
  • public final boolean sendMessageDelayed(Message msg, long delayMillis)

 

B、Looper.JAVA:(4個屬性,4個方法)

每個ThreadLocal中只能有一個Looper,也就是說一個Thread中只有一個Looper


4個屬性:

  • static finalThreadLocalsThreadLocal= new ThreadLocal();
  • finalMessageQueue mQueue;
  • finalThread mThread;
  • private staticLoopermMainLooper= null;

4個方法:

  • public static void prepare()
  • public static void prepareMainLooper()
  • public static void loop()
  • public static Looper myLooper()

 

C、Message.java:(8個屬性,5個方法)

8個屬性:

  • public intwhat;
  • public intarg1;
  • public intarg2;
  • public Objectobj;
  • Handlertarget;
  • MessagesPool;
  • intsPoolSize;
  • intMAX_POOL_SIZE=10;

5個方法:

  • public static Messageobtain()
  • public voidrecycle()
  • public voidsetTarget(Handler target)
  • public HandlergetTarget()
  • public voidsendToTarget()
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved