Android原始碼解析--Looper

傲慢的上校發表於2012-11-04

          前面寫了兩篇原始碼解析了,Handler原始碼解析MessageQueue原始碼解析,其中MessageQueue原始碼解析裡面情況解釋的不是太清晰,隨著以後對程式碼的理解,會有後續內容的新增。

Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.

Most interaction with a message loop is through the Handler class.

This is a typical example of the implementation of a Looper thread, using the separation of prepare() and loop() to create an initial Handler to communicate with the Looper.

類用於為一個執行緒執行一個訊息迴圈。預設情況下執行緒沒有相關聯的訊息迴圈;可以通過線上程裡呼叫looper的prepare()方法建立一個looper,然後就會呼叫loop()處理訊息之道loop被停止。

大多數訊息是通過handler來處理。

下面是一個典型的Looper thread的實現,使用分離的準備()和迴圈()來建立一個Handler與Looper進行通訊。

class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }

先看一下類中的變數:

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

    final MessageQueue mQueue;
    final Thread mThread;
    volatile boolean mRun;

    private Printer mLogging = null;
    private static Looper mMainLooper = null;  // guarded by Looper.class

  1. ThreadLocal主要是用於存放looper,如果沒有呼叫prepare()方法的話,使用sThreadLocal的get方法會返回空。具體原因會在後面程式碼裡具體說明。關於ThreadLocal,在這多說兩句:ThreadLocal很容易讓人望文生義,想當然地認為是一個“本地執行緒”。其實,ThreadLocal並不是一個Thread,而是Thread的區域性變數,也許把它命名為ThreadLocalVariable更容易讓人理解一些。
      當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。
      從執行緒的角度看,目標變數就像是執行緒的本地變數,這也是類名中“Local”所要表達的意思。
  2. 一個MessageQueue,用於存放訊息,詳情請看:MessageQueue原始碼解析
  3. 和這個looper相關的執行緒
  4. looper是否開啟,注意他的關鍵字:volatile:Volatile修飾的成員變數在每次被執行緒訪問時,都強迫從共享記憶體中重讀該成員變數的值。而且,當成員變數發生變化時,強迫執行緒將變化值回寫到共享記憶體。這樣在任何時刻,兩個不同的執行緒總是看到某個成員變數的同一個值。Java語言規範中指出:為了獲得最佳速度,允許執行緒儲存共享成員變數的私有拷貝,而且只當執行緒進入或者離開同步程式碼塊時才與共享成員變數的原始值對比。這樣當多個執行緒同時與某個物件互動時,就必須要注意到要讓執行緒及時的得到共享成員變數的變化。而volatile關鍵字就是提示VM:對於這個成員變數不能儲存它的私有拷貝,而應直接與共享成員變數互動。使用建議:在兩個或者更多的執行緒訪問的成員變數上使用volatile。當要訪問的變數已在synchronized程式碼塊中,或者為常量時,不必使用。由於使用volatile遮蔽掉了VM中必要的程式碼優化,所以在效率上比較低,因此一定在必要時才使用此關鍵字。
  5. 一個printer型別的mLogging。看下printer:一個用於列印資訊的簡單介面。
    Simple interface for printing text, allowing redirection to various targets. Standard implementations are android.util.LogPrinter, android.util.StringBuilderPrinter, and android.util.PrintWriterPrinter.
    

  6. 主執行緒(UI執行緒)looper。(在UI執行緒中系統會自動建立looper,其原理會在下面程式碼中進行解析)         

看完變數,再看構造方法:

private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }

初始化變數。                                       


     /** 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類,首先它是一個靜態類。為當前執行緒初始化一個looper,在正式啟動這個loop的之前,你有機會建立一個handler和這個looper關聯。在呼叫loop方法之前,一定要確認呼叫prepare方法,在最後結束的時候呼叫quit方法。如果這個執行緒已經設過了looper的話就會報錯這說明,一個執行緒只能設一個looper。

   /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare();
        setMainLooper(myLooper());
        myLooper().mQueue.mQuitAllowed = false;
    }

接下來是prepareMainLooper方法,為應用UI執行緒初始化一個looper,這個主looper是由android環境自動建立,所以不需要你呼叫這個方法。方法內部呼叫了prepare方法為執行緒初始化一個looper。最後那句程式碼是把queue的允許退出設為false。

然後呼叫setMainLooper這個方法為mMainLooper賦值

private synchronized static void setMainLooper(Looper looper) {
        mMainLooper = looper;
    }
而myLooper方法:

/**
     * 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();
    }

就是為了獲取前面prepare方法初始化的looper。這個方法返回與當前執行緒想關聯的Looper實體,如果呼叫這個方法的執行緒沒有與looper相關聯,則返回空。

下面一個方法是獲取MainLooper:

 /** Returns the application's main looper, which lives in the main thread of the application.
     */
    public synchronized static Looper getMainLooper() {
        return mMainLooper;
    }

返回在主執行緒裡存在的主looper。

下面是本篇程式碼之重點: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();//獲取當前looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }//如果為空,則拋異常
        MessageQueue queue = me.mQueue;//把當前looper的queue賦值給區域性變數queue
        
        // 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();//確保當前執行緒屬於當前程式,並且記錄真實的token。
        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.退出訊息的標示就是target為空
                    return;
                }

                long wallStart = 0;
                long threadStart = 0;

                // This must be in a local variable, in case a UI event sets the logger 一個區域性變數,為ui事件設定log記錄。
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                    wallStart = SystemClock.currentTimeMicro();
                    threadStart = SystemClock.currentThreadTimeMicro();
                }
                 //handler處理訊息
                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);
                }
                //處理完成後,呼叫Message.recycle()將其放入Message Pool中。 
                msg.recycle();
            }
        }
    }

這個方法,主要講解在註釋中。

/**
     * Control logging of messages as they are processed by this Looper.  If
     * enabled, a log message will be written to <var>printer</var> 
     * at the beginning and ending of each message dispatch, identifying the
     * target Handler and message contents.
     * 
     * @param printer A Printer object that will receive log messages, or
     * null to disable message logging.
     */
    public void setMessageLogging(Printer printer) {
        mLogging = printer;
    }

設定使用looper處理訊息時的log,如果啟用,每條訊息分派開始和結束的日誌訊息將被寫入到Printer類,以確定目標Handler和訊息內容。

 /**
     * Return the {@link MessageQueue} object associated with the current
     * thread.  This must be called from a thread running a Looper, or a
     * NullPointerException will be thrown.
     */
    public static MessageQueue myQueue() {
        return myLooper().mQueue;
    }

返回與當前執行緒相關聯的MessageQueue。當Looper執行時,該方法一定會被呼叫。

    public void quit() {
        Message msg = Message.obtain();
        // NOTE: By enqueueing directly into the message queue, the
        // message is left with a null target.  This is how we know it is
        // a quit message.
        mQueue.enqueueMessage(msg, 0);
    }

退出方法,往訊息佇列中加一個空msg。這就是約定的退出訊息,在處理該訊息時,looper就會退出。

返回當前執行緒和返回當前Messagequeue:

    /**
     * Return the Thread associated with this Looper.
     */
    public Thread getThread() {
        return mThread;
    }

    /** @hide */
    public MessageQueue getQueue() {
        return mQueue;
    }

在往後就是列印列印,異常定義的dump方法:

public void dump(Printer pw, String prefix) {
        pw = PrefixPrinter.create(pw, prefix);
        pw.println(this.toString());
        pw.println("mRun=" + mRun);
        pw.println("mThread=" + mThread);
        pw.println("mQueue=" + ((mQueue != null) ? mQueue : "(null"));
        if (mQueue != null) {
            synchronized (mQueue) {
                long now = SystemClock.uptimeMillis();
                Message msg = mQueue.mMessages;
                int n = 0;
                while (msg != null) {
                    pw.println("  Message " + n + ": " + msg.toString(now));
                    n++;
                    msg = msg.next;
                }
                pw.println("(Total messages: " + n + ")");
            }
        }
    }

最後面還有一個有關log的介面:

/**
     * @hide
     */
    public static interface Profiler {
        void profile(Message message, long wallStart, long wallTime,
                long threadStart, long threadTime);
    }

好,結束,可以做飯,然後重溫最愛的《肖申克的救贖》了。


檢視更多原始碼內容:Android原始碼解析


相關文章