深入理解Android中Handler機制


對於一位Android開發者來說,對HandlerLooperMessage三個乖寶貝應該再熟悉不過了,這裏我們先簡單介紹下這三者的關係,之後再用Looper.loop方法做點有意思的事情,加深對運行循環的理解。

一、源碼理解HandlerLooperMessage

通常我們在使用Handler時會在主線程中new出一個Handler來接收消息,我們來看下Handler源碼:

/**
     * Default constructor associates this handler with the {@link Looper} for the
     * current thread.
     *
     * If this thread does not have a looper, this handler won't be able to receive messages
     * so an exception is thrown.
     */
    public Handler() {
        this(null, false);
    }

在源碼註釋中說到默認的構造方法創建Handler,會從當前線程中取出Looper,如果當前線程沒有Looper,這個Handler不能夠接收到消息並會拋出異常。
我們繼續點進去:

public Handler(Callback callback, boolean async) {
        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());
            }
        }
        //獲取Looper
        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 = callback;
        mAsynchronous = async;
    }

 /**
     * 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();//從ThreadLocal中獲取
    }

既然Looper是從ThreadLocal中獲取的,那必然有時機要存進去,我們看下Looper是什麼時候存進去的:

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

    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));
    }

也就是說我們在調用Looper. prepare方法時會創建Looper並存入ThreadLocal中,注意默認quitAllowed參數都爲true,也就是默認創建的Looper都是可以退出的,我們可以點進去看看:


    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
    //進去MessageQueue.java
       MessageQueue(boolean quitAllowed) {
          mQuitAllowed = quitAllowed;
          mPtr = nativeInit();
        }

⚠️注意:MessageQueue的成員變量mQuitAllowed,在調用Looper.quit方法時會進入MessageQueuemQuitAllowed進行判斷,可以簡單看下源碼,後面會再說到:

//MessageQueue.java
void quit(boolean safe) {
        //如果mQuitAllowed爲false,也就是不允許退出時會報出異常
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

看到這裏我們應該是有疑問的,

  1. 第一個疑問:默認我們調用Looper.prepare方法時mQuitAllowed變量都爲true的,那它什麼時候爲false?又是被如何設爲false的?
  1. 第二個疑問:我們在創建Handler時,並沒有往ThreadLocal中存Looper,而卻直接就取出了ThreadLocal中的Looper,那麼這個Looper是什麼時候創建並存入的?

這裏就要說到ActivityThreadmain方法了。Zygote進程孵化出新的應用進程後,會執行ActivityThread類的main方法。在該方法裏會先準備好Looper和消息隊列,並將Looper存入ThreadLocal中,然後調用attach方法將應用進程綁定到ActivityManagerService,然後進入loop循環,不斷地讀取消息隊列裏的消息,並分發消息。

//ActivityThread
 public static void main(String[] args) {
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        Process.setArgV0("<pre-initialized>");
        //創建主線程的阻塞隊列
        Looper.prepareMainLooper();

         // 創建ActivityThread實例
        ActivityThread thread = new ActivityThread();
        //執行初始化
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        //開啓循環
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

我們看下開啓的loop循環吧:

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final 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();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // 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);
            }
            //注意這裏   msg.target爲發送msg的Handler
            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

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

Looper.loop方法內部是個死循環(for(;;))。queue.next();是從阻塞隊列裏取走頭部的Message,當沒有Message時主線程就會阻塞。view繪製,事件分發,activity啓動,activity的生命週期回調等等都是一個個的Message,系統會把這些Message插入到主線程中唯一的queue中,所有的消息都排隊等待主線程的執行。
回過來我們捋一下思路,首先,我們在主線程中創建了Handler,在Handler的構造方法中會判斷是否創建了Looper,由於在ActivityThread.main方法中我們初始化了Looper並將其存入ThreadLocal中,所以可以正常創建Handler。(而如果不是在主線程中創建Handler,則需要在創建之前手動調用Looper.prepare方法。)在Looper的構造方法中創建了MessageQueue消息隊列用於存取Message。然後Handler.sendMessage發送消息,在queue.enqueueMessage(msg, uptimeMillis)方法中將Message存入MessageQueue中,並最終在Loop.loop循環中取出消息調用msg.target.dispatchMessage(msg);也就是發送消息的HandlerdispatchMessage方法處理消息,在dispatchMessage最終調用了handleMessage(msg);方法。這樣我們就可以正常處理髮送到主線程的消息了。

二、用Looper搞事情

  1. 異步任務時阻塞線程,讓程序按需要順序執行
  1. 判斷主線程是否阻塞
  2. 防止程序異常崩潰

1. 異步任務時阻塞線程,讓程序按需要順序執行
在處理異步任務的時候,通常我們會傳入回調來處理請求成功或者失敗的邏輯,而我們通過Looper處理消息機制也可以讓其順序執行,不使用回調。我們來看下吧:

    String a = "1";
    public void click(View v){
        new Thread(new Runnable() {
            @Override
            public void run() {
                //模擬耗時操作
                SystemClock.sleep(2000);
                a = "22";
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        mHandler.getLooper().quit();
                    }
                });
            }
        }).start();

        try{
            Looper.loop();
        }catch (Exception e){
        }
       Toast.makeText(getApplicationContext(),a,Toast.LENGTH_LONG).show();
    }

當點擊按鈕的時候我們開啓線程處理耗時操作,之後調用Looper.loop();方法處理消息循環,也就是說主線程又開始不斷的讀取queue中的Message並執行。這樣當執行mHandler.getLooper().quit();時會調用MessageQueuequit方法:

 void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        ...
}

這個就到了之前我們分析的變量mQuitAllowed,主線程不允許退出,這裏會拋出異常,而最終這段代碼是在Looper.loop方法中獲取消息調用msg.target.dispatchMessage執行的,我們將Looper.loop的異常給捕獲住了,從而之後代碼繼續執行,彈出Toast。

2. 判斷主線程是否阻塞
一般來說,Loop.loop方法中會不斷取出Message,調用其綁定的Handler在UI線程進行執行主線程刷新操作。

            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            //注意這裏   msg.target爲發送msg的Handler
            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

也就是這裏,基本上可以說msg.target.dispatchMessage(msg);我們可以根據這行代碼的執行時間來判斷UI線程是否有耗時操作。

msg.target.dispatchMessage(msg);前後,分別有logging判斷並打印>>>>> Dispatching to<<<<< Finished to的log,我們可以設置logging並打印相應時間,基本就可以判斷消耗時間。

          Looper.getMainLooper().setMessageLogging(new Printer() {
            private static final String START = ">>>>> Dispatching";
            private static final String END = "<<<<< Finished";

            @Override
            public void println(String x) {
                if (x.startsWith(START)) {
                    //開始
                }
                if (x.startsWith(END)) {
                   //結束
                }
            }
        });

3. 防止程序異常崩潰
既然主線程異常事件最終都是在Looper.loop調用中發生的,那我們在Looper.loop方法中將異常捕獲住,那主線程的異常也就不會導致程序異常了:

 private Handler mHandler = new Handler();
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_test);

        mHandler.post(new Runnable() {
            @Override
            public void run() {
               while (true){
                   try{
                       Looper.loop();
                   }catch (Exception e){
                   }
               }
            }
        });
    }
    public void click2(View v){
        int a = 1/0;//除數爲0  運行時報錯
    }

主線程的所有異常都會從我們手動調用的Looper.loop處拋出,一旦拋出就會被try{}catch捕獲,這樣主線程就不會崩潰了。此原理的開源項目:Cockroach,有興趣可以看下具體實現。

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