1. 程式人生 > >【Android 開發】: Android 訊息處理機制之四: Android 訊息迴圈 Looper 及其原始碼解析

【Android 開發】: Android 訊息處理機制之四: Android 訊息迴圈 Looper 及其原始碼解析

  上一講我們學習Handler和Message的一些使用方式,我們知道Handler它會發送訊息和處理訊息,並且關聯一個子執行緒,如何傳送訊息入隊和出隊處理訊息等這些都是交給Looper去管理分發的,也就是它是負責整個訊息佇列運轉的一個類,這一講我們就來學習一下Android中的Looper的操作。

一、Looper類介紹

  這個類是用來在一個執行緒中執行一個訊息迴圈(Message),預設情況下執行緒是沒有一個訊息迴圈來關聯它們的,在這個執行緒中呼叫prepare()方法來啟動一個迴圈,然後呼叫loop()就可以處理訊息至到迴圈停止。

  下面就是一個典型的例子實現一個Looper執行緒,使用 prepare()方法 和 loop()來建立一個初始的Handler並且能夠與訊息迴圈(Looper)進行溝通關聯

  【注意】:預設情況下的android新誕生的一個執行緒是沒有開啟一個訊息迴圈(Looper)的,但是主執行緒除外,主執行緒系統會自動為其建立Looper物件,開啟訊息迴圈。

二、程式Demo

1. 佈局檔案定義一個Button和TextView,這裡不貼出來,讀者可以閱讀附件原始碼

2. MainActivity.java

...
public class MainActivity extends Activity {

    private Button btn;
    private TextView txt;
    private MyHandler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       ...
mHandler = new MyHandler(); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub // 啟動執行緒 new Thread(new MyThread()).start(); } }); } public class MyThread implements Runnable { @Override public void run() { // TODO Auto-generated method stub Message msg = Message.obtain(); msg.obj = "AHuier"; mHandler.sendMessage(msg); } } public class MyHandler extends Handler { public MyHandler() { super(); // TODO Auto-generated constructor stub } // Handler中有個傳遞Looper物件的構造方法,這個構造方法比較少用 public MyHandler(Looper looper) { super(looper); // TODO Auto-generated constructor stub } @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); txt.setText("接受子執行緒傳送的訊息 --->" + msg.obj); } } ...  }

3. 程式執行結果

4. 【說明】: 在上面的程式碼中我們並沒有去手動生成Looper物件,主執行緒依然可以完成接受子執行緒訊息並顯示的操作,在這裡我們需要明白為什麼我們之前的例子中雖然沒有建立一個Looper去管理訊息,但是子執行緒中傳送訊息依然能夠被主執行緒接受到,原因是因為我們主執行緒中已經存在了預設的一個Looper物件。

   這裡我們在做一個小測試,我們給其生成一個Looper物件,在onCreate()方法中新增程式碼如下:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initComponent();
        // 在Activity中有一個預設的Looper物件,來處理子執行緒傳送的訊息
        // 這裡我們嘗試的給其生成一個Looper物件,也是可以的
        Looper looper = Looper.myLooper(); //獲得與子執行緒關聯的Looper物件
        mHandler = new MyHandler(looper);
        btn.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                // 啟動執行緒
                new Thread(new MyThread()).start();
            }
        });
    }
程式執行依然會接受到子執行緒傳送的訊息。為什麼會是這樣的呢?我們來檢視一下它們的原始碼

1) 檢視Handler原始碼中的構造方法

    /**
     * Default constructor associates this handler with the queue for the
     * current thread.
     *
     * If there isn't one, this handler won't be able to receive messages.
     */
    public Handler() {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = null;
    }
可以發現在其構造方法中就已經預設為幫其生成一個Looper物件了: mLooper = Looper.myLooper();
同時從Looper中獲取到一個訊息佇列,並且賦值給Handler的本地的mQueque,我們在看一下Handler(Looper looper)這個構造方法如下:
    /**
     * Use the provided queue instead of the default one.
     */
    public Handler(Looper looper) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = null;
    }
同樣也是接受使用者生成的一個Looper物件。所以是底層實現方式都是一模一樣了,從這裡我們也知道了為什麼預設情況下主執行緒都會預設的Looper物件去維護了。

2) 這裡我們需要在看一下為什麼會呼叫 Looper.myLooper();會獲取到一個Looper物件,跟蹤其原始碼如下:

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static Looper myLooper() {
        return sThreadLocal.get();
    }
繼續跟蹤是誰給其sThreadLocal例項化
    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
sThreadLocal 是從一個本地執行緒中獲取Looper型別的本地執行緒ThreadLocal物件,這裡只需要明白ThreadLocal是一個Android提供管理執行緒的一個東西。
     /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
在prepare()方法中,會從sThreadLocal通過get獲取一個本地執行緒的物件,如果是空的話,這個東西中將new出來的Looper物件set到本地執行緒中。檢視ThreadLocal的get和set方法
    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

也就是說它終究是通過set的方式講new出來的Looper物件扔到ThreadLocal中,由它來完成初始化和關聯一個執行緒,如果要得到一個Looper物件就從ThreadLocal中get出來。通過這種方式來關聯和初始化指定執行緒的Looper物件。

5. 在上面的一個Demo中,我們是實現了子執行緒傳送訊息給主執行緒來更新UI的操作和Looper的關係,子執行緒預設情況下是沒有Looper的物件的,下面我就來測試一下主執行緒向子執行緒傳送訊息,由於子執行緒預設沒有Looper,我們就來測試一下這樣實現會發生什麼情況?[注意,這種方式我們一般在實際開發中是很少見的],Demo2如下所示:

1) MainActivity.java 中貼出onCreate()和 MyThread 類裡面的程式碼段,讀者可以閱讀附件中的原始碼

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

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                // 點選按鈕的時候在UI主執行緒中向子執行緒傳送訊息
                Message message = Message.obtain();
                message.obj = "AHuier";
                mHandler.sendMessage(message);
            }
        });
    }
    public class MyThread implements Runnable {
        @Override
        public void run() {
            mHandler = new Handler(){

                @Override
                public void handleMessage(Message msg) {
                    // TODO Auto-generated method stub
                    super.handleMessage(msg);
                    // 由於不能在子執行緒中更新UI,所以我們輸出到控制檯.
                    System.out.println("接受主執行緒中發出來的訊息" + msg.obj);
                }
                
            };
        }
    }
編譯執行發出異常:

從這裡我們可以得出結論在子執行緒中,預設情況下是沒有Looper物件的,所以我們需要根據博文上面的Looper類的說明新增prepare()方法 和 loop()方法來啟動Looper訊息迴圈。修改程式如下2)

2) 在MyThread子執行緒中新增prepare()方法 和 loop()方法完成Looper訊息迴圈的啟動

    public class MyThread implements Runnable {
        @Override
        public void run() {
            Looper.prepare();
            mHandler = new Handler(){

                @Override
                public void handleMessage(Message msg) {
                    // TODO Auto-generated method stub
                    super.handleMessage(msg);
                    // 由於不能在子執行緒中更新UI,所以我們輸出到控制檯.
                    System.out.println("接受主執行緒中發出來的訊息" + msg.obj);
                }
                
            };
            Looper.loop();
        }
    }
程式執行結果:

6. 在這裡為什麼新增完這兩個方法之後就會有Looper訊息迴圈了?我們來檢視一下Looper的相關原始碼

1) prepare() 方法我們在上面已經知道,它會初始化當前的執行緒關聯一個Looper.

2) loop()原始碼如下

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        MessageQueue queue = me.mQueue;
        
        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        
        while (true) {
            Message msg = queue.next(); // might block
            if (msg != null) {
                if (msg.target == null) {
                    // No target is a magic identifier for the quit message.
                    return;
                }

                long wallStart = 0;
                long threadStart = 0;

                // This must be in a local variable, in case a UI event sets the logger
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                    wallStart = SystemClock.currentTimeMicro();
                    threadStart = SystemClock.currentThreadTimeMicro();
                }

                msg.target.dispatchMessage(msg);

                if (logging != null) {
                    long wallTime = SystemClock.currentTimeMicro() - wallStart;
                    long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;

                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                    if (logging instanceof Profiler) {
                        ((Profiler) logging).profile(msg, wallStart, wallTime,
                                threadStart, threadTime);
                    }
                }

                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
                
                msg.recycle();
            }
        }
    }
它首先獲取Looper物件,然後將訊息從Looper中取出,然後賦值給MessageQueue,讓MessageQueue去管理,接著在While(true)這個死迴圈裡面一直在輪轉的取訊息和分發訊息(從Message msg = queue.next();和msg.target.dispatchMessage(msg);)這兩句程式碼讀出。

三、總結與相關原始碼

    通過上述兩個Demo和Looper相關原始碼的分析,我們可以知道Looper作為一個迴圈機制它的作用就是初始化執行緒和將Handler與該執行緒關聯的工作,以及管理,維護整個訊息迴圈的機制。但是具體的傳送訊息還有處理訊息都是靠Handler和Message來完成的。所以在一個新誕生的執行緒中,Looper都會關聯到這個Thread,以及它的MessageQueue和Handler.

    原始碼下載: