[Android源碼]l理解handler機制

Android中的異步消息處理機制,就是對核心類handler、looper類和Message類的應用。如果我們想要把一些耗時的操作(比如網絡請求),放在worker線程裏面去做,就需要對這個handler機制有一個深入的瞭解。本文將從源碼的角度來分析handler內部是如何把消息發送出去,並且在完成時通知UI線程去進行相應的操作的。

源碼分析

Thread,Looper,MessageQueen,Message和handler的邏輯關係:

這裏寫圖片描述

說明:

  • 任何一個Thread最多隻能擁有一個Looper
  • 普通的Thread(除去UI線程,因爲UI線程在程序啓動時候自己已經調用了相應的.prepare())要想要變成LooperThread必須要經過Looper.prepare()和Loop.loop();
  • Handler負責向所屬的線程裏面的Looper裏面添加Message(消息入隊)或者處理消息(消息出隊)
  • Handler的歸屬問題,一個Handler屬於創建他的那個線程。一個線程可以創建多個handler

從Looper說起

私有的構造函數

除了這是一個final的類以外,私有的構造函數,也禁止了從外部來 new 一個Looper(),是這樣的:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

但是查看了Looper的整個源碼,我們會發現,只有這裏:

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

是new了一個Looper,所以就解釋了爲什麼一定要先調用Looper.prepare()才能變成有個LooperThread

成員變量

// sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;
  • ThreadLocal:通過它我們可以得到與當前Thread相綁定的Looper是哪個,並且還可以通過它來綁定一個Looper到當前的Thread
    sThreadLocal.set(new Looper(quitAllowed));
    return sThreadLocal.get();
  • sMainLooper:與UI線程綁定的Looper
  • mQueue:如邏輯關係圖中所示,指Looper裏面的那個MessageQueue
  • mThread:當前線程,通過getThread()可以返回他的值

核心方法loop()

public static void loop() {
        final Looper me = myLooper();
        //返回當前線程裏面的Looper
        final MessageQueue queue = me.mQueue;
        //拿到Looper裏面的MessageQueue
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        //沒看懂但是不影響理解消息處理機制
        for (;;) {
        //判斷條件爲空,所有是一個無限循環
            Message msg = queue.next(); // might block
            //Message裏面的next()方法,將下一個消息出隊,
            //並進行處理,當隊列沒有消息時候就阻塞在這裏
            msg.target.dispatchMessage(msg);
            //對消息進行處理,非常重要!!
            //msg.target返回的是Handler對象,message.target的
            //初始化是在Handler中enqueueMessage()入隊操作時候
            msg.recycle();
            //釋放處理掉的消息所佔用的資源
        }
    }

Loop中的一個for循環將MessageQueue驅動着動起來,如果隊列裏面有消息就拿出來進行處理,如果沒有就一直阻塞並檢查着。

核心類Handler

構造函數

構造函數一共有7個,調用關係如下:
這裏寫圖片描述
通過構造函數的源碼我們可以知道,在構造handler時,我們可以傳入一個callback參數,也可以選擇不傳入這個參數

  • 如果傳入callback的時候,因爲存在 mCallback = callback; 而mCallback是一個本地的接口,所以我們就必須去實現這個接口裏面的handleMessage,同時我們也要去重寫空的成員函數handleMessage來接受messages
public interface Callback {
        public boolean handleMessage(Message msg);
    }
/**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
  • 如果不傳入callback這個參數,那麼也需要我們去重寫空的成員函數handleMessage:
/**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

通過暴露這兩個handlerMessage來實現對消息的處理操作。

通過handler向綁定的線程裏面的Looper裏面的MessageQueue發送消息

發送消息有兩種途徑post系列和sendMessage系列,來張圖說明下調用關係:
這裏寫圖片描述
我們可以看到最終都是調用了enqueueMessage方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        //重要代碼,爲Message設置target,方便在looper裏面
        //拿到這個Message時候,找到對應的target(handler對象)
        //從而調用裏面的處理方法(dispatchMessage方法)
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
        //把這個消息加入到當前handler所屬的線程裏面的looper中的
        //messageQueue裏面去
    }

說明:兩種方式Post和sendMesssage的不同點是,採用post時,傳入的是一個runnable對象,再轉化成message對象;採用sendMessage時,傳入的直接就是Message對象。

對Message的處理(消費)dispatchMessage函數

當我們從Looper中loop方法的for(;;)裏面拿到一個消息時候會回調Message.target.dispatchMessage方法,看下源碼:

/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

需要認真分析一下

  • 首先檢查Message自身的callback(實質是一個Runnable對象)是否爲空,我們會看到只有調用post—->getPostMessage—->m.callback = runnable時候進行了初始化,所以可以通過post傳入Runnable來實現對消息的消費目的。
private static void handleCallback(Message message) {
        message.callback.run();
    }

通過執行這個callback(Runnable)來達到消費Message的目的

  • 如果Message自身的callback爲空,繼續檢查handler的成員變量mCallback(本身是一個接口類型):
final Callback mCallback;
public interface Callback {
        public boolean handleMessage(Message msg);
    }

並且只有當handleMessage返回false的時候,纔會繼續向下執行handler的成員函數handleMessage

  • handleMessage(msg)是handler的一個空的成員函數,需要重寫來實現對消息的消費。
    小總結:我們可以在三個地方對消息進行消費,如果我分別在Message裏面寫了callback,實現了handler的接口Callback,同時又重寫了成員函數handlerMessage,那麼他們的優先級是 Message.callback>Callback>handlerMessage

Message類

一個Message就是一個要處理的事件,常用到的有以下幾點:
* 這是一個final類,所以可以通過Message.obtain來獲得一個空的消息
* 可以攜帶數據 有兩個 int arg1 arg2 可以攜帶少量的數據,並且效率也比較高,還有一個成員變量object可以攜帶任何數據
* Message.what 用來標誌Message的標識符,可以自己賦值,來區別Message
* Handler target 可以得到或者設置Message所綁定的handler
* Runnable callback 可以接收一個Runnable來對消息進行消費


到此,對handler分析就結束了,再提示一下handler的常用場景,就像例子裏面一樣,在UI線程創建handler,完成綁定,並通過最低優先級的方法—重寫了空的成員函數handleMessage來消費這個事件,然後再新開一個Thread,在run方法裏面通過已經創建的handler.post 或者handler.sendmessage 加入消息隊列就可以了。
(其實我們也可以做到在thread1裏面做網絡請求,傳到thread2,再傳到thread3…..最後傳到UI線程,只不過android都是依託UI線程來做的,這樣實際效果更好一些。)

我們放一個例子:—->直觀的使用handler

(如果不想看例子的直接可以跳過了跳過)
* 想要達到的效果:
這裏寫圖片描述
這裏寫圖片描述
* 主要代碼:
* MainActivity.java

public class MainActivity extends Activity {
    Button  bt;
    static ImageView iv;
    static TextView tv;
    final String address= "http://www.yydm.com/yfile/allimg/140708/1PI3L60-0.jpg";
    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         bt = (Button) findViewById(R.id.btbt);
         tv= (TextView) findViewById(R.id.tv);
         iv= (ImageView) findViewById(R.id.iv);
       final Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    //7.把位圖對象顯示至imageview
                    case 1:
                        tv.setText("一隻大龍貓!");
                        iv.setImageBitmap((Bitmap) msg.obj);
                        break;
                    case 2:
                        Toast.makeText(MainActivity.this,"請求失敗!",Toast.LENGTH_SHORT).show();
                        break;
                }
            }};

        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Thread(){
                    @Override
                    public void run() {
                        try {
                            //1.下載圖片確定網址,將網址封裝成url對象
                            URL url = new URL(address);

                            //2.獲取客戶端與服務器連接對象,此時還沒有建立連接
                            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

                            //3.對連接對象進行初始化

                            conn.setRequestMethod("GET");//連接方式
                            conn.setConnectTimeout(3000);//連接超時
                            conn.setReadTimeout(3000);//讀取超時

                            //4.發送請求,與服務器建立連接
                            conn.connect();

                            //響應碼=200,說明請求成功
                            if (conn.getResponseCode()==200){

                                //5.獲取服務器響應頭裏的流,流中的數據就是請求端的數據
                                InputStream is = conn.getInputStream();
                                Log.d("InputStream", String.valueOf((is.toString()==null)));
                                //6.讀取數據流裏的數據,構造成位圖對象
                                Bitmap bitmap = BitmapFactory.decodeStream(is);
                                Log.d("bitmap", String.valueOf(bitmap==null));
                                Message msg=handler.obtainMessage();
                                //消息對象可以攜帶數據
                                Log.d("Message", String.valueOf(msg==null));
                                msg.obj=bitmap;
                                Log.d("msg.obj", String.valueOf(msg.obj==null));
                                msg.what=1;
                                //把消息發送至主線程的消息隊列
                                handler.sendMessage(msg);
                            }else {
                                Message msg = handler.obtainMessage();
                                msg.what=2;
                                handler.sendMessage(msg);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }}.start();
            }
        });
    }
}
  • xml文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

    <Button
        android:id="@+id/btbt"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="click"
        android:layout_gravity="center"
        android:gravity="center"></Button>
    <TextView
        android:id="@+id/tv"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Hello World, MyActivity"
        />
    <ImageView
        android:id="@+id/iv"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"></ImageView>>
</LinearLayout>

當然了,網絡請求,得想着在manifest裏面獲取到權限:

<uses-permission android:name="android.permission.INTERNET"></uses-permission>
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission>
    <uses-permission android:name="android.permission.INSTALL_PACKAGES"></uses-permission>

這樣就可以讓demo跑起來了

注:demo參考

參考:
[0]http://blog.csdn.net/iispring/article/details/47180325#t0
[1]http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html
[2]http://www.jianshu.com/p/36a978b6cacc
[3]http://www.jianshu.com/p/86ea1c817fff
[4]http://www.cnblogs.com/JczmDeveloper/p/4403129.html
[5]http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章