Handler詳解

這篇是以前Looper、Handler、Message以及MessageQueue之間的關係後續版本,將更加詳細講解一下以前沒提到的細節內容。java

先說一下這篇文章主要要解決的幾個點:bash

  1. 爲什麼Activity中直接new Hander()調用不報錯;
  2. 爲什麼以前文章提到的MessageQueue內部是一個死循環,可是UI並不卡頓;
  3. Handler.postDelay()實現的原理是什麼;
  4. 子線程使用Handler的注意點;
  5. 一些其餘的知識點;

爲什麼Activity中直接new Hander()調用不報錯

以前文章提到過要在線程(包括UI線程)中使用Handler,就須要Looper.prepare()以及Looper.loop()相關操做,可是爲何Activity中能夠直接使用Handler呢?這個問題在以前文章中提到過,是由於系統已經幫咱們作了Looper相關的準備操做,咱們來看一下具體實現。app

Android進程啓動過程當中會建立一個ActivityThread對象,並調用其中的main(),用以接收系統的各類操做。這個ActivityThread對象就是咱們常說的UI線程,其實並非一個線程。在main()中咱們就能看到Looper初始化相關的操做。異步

public static void main(String[] args) {

        ...
        
        Looper.prepareMainLooper();
        
        ...
        
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

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

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
複製代碼

這裏咱們只看到了MainLooper初始化操做,並無發現它跟Activity有何關聯,接着往下看。ide

ActivityThread有一個成員變量mH,是一個H類型的對象,這個H是是Handler的子類,內部用於處理4大組件的啓動、生命週期相關方法以及其餘一些系統的回調。固然系統的啓動過程太過冗長,這裏不作細緻的分析,如今只須要知道mH這個Handler對象。oop

final H mH = new H();
複製代碼

就是這個mH接收了來自ActivityManagerService傳遞過來的啓動Activity的消息,並調用了ActivityThread中的performLaunchActivity()來建立一個Activity並調用相關生命週期方法()post

/**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }        
        ....
        
        return activity;
    }
複製代碼

這裏咱們能夠知道,Activity運行在mH內部。再看最上面ActivityThread的建立,能夠看出mH中調用的方法也是在MainLooper中執行,Activity在MainLooper中執行,因此咱們並不須要先初始化Looper再使用Handler。ui

爲什麼以前文章提到的MessageQueue內部是一個死循環,可是UI並不卡頓

首先要明白一個問題,UI卡頓實際上是MessageQueue中某些方法,好比onCreate()、onResume()等生命週期的回調執行過長,致使UI繪製某些幀丟失甚至ANR。MessageQueue雖然是個死循環,可是當消息隊列爲空時,只是阻塞消息隊列,當時並不佔用cpu資源,因此並不會引發UI的卡頓。至於爲何MessageQueue能夠作到沒有消息時阻塞隊列,有消息時能夠喚起繼續工做的,讀者能夠查閱Handler機制在Native層中的實現,主要是Linux中的epoll。this

固然你還有疑問爲何要將MessageQueue寫成死循環,這是由於UI線程須要一直接收系統,以及其餘組件發送過來的事件,中間可能會有空閒的狀況,爲了保證MessageQueue不退出,就只能寫成死循環了。spa

Handler.postDelay()實現的原理是什麼

前面的文章咱們能夠知道,無論Handler經過什麼形式發送消息,最後都會調用到enqueueMessage()。這個方法內部調用了MessageQueue的enqueueMessage()。

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
        //除了系統的同步控制,全部的msg都必需要有target來處理msg
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
            //循環隊列正在退出,直接將msg回收
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
            
            //這裏開始正常的msg入隊操做,標記msg的使用狀態,處理時間等
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                //若是當前隊列爲空,
                //或者msg須要當即處理,
                //或者當前消息隊列中頭消息處理的時間大於當前消息,
                //都是將頭消息置爲當前消息,
                //同時若是當前隊列阻塞,則喚起消息隊列進入循環
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                //進入這個分支,是將msg按執行時間前後插入消息鏈表中
                //正常狀況下咱們不須要喚醒隊列,除非當前消息對列正在
                //同步消息控制,同時當前消息爲第一條異步消息
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p;
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
複製代碼

上面的方法咱們能夠看到,處理了幾種狀況都在註釋裏作了說明,下面再來看看消息具體是如何取出來的

Message next() {
        //ptr = 0 當前隊列正在退出
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        
        //這個nextPollTimeoutMillis用來控制消息隊列是否要阻塞,以及阻塞多久
        //nextPollTimeoutMillis = 0 表示不阻塞
        //nextPollTimeoutMillis = -1 表示一直阻塞,直到被喚醒
        //nextPollTimeoutMillis > 0 表示阻塞這麼長時間以後喚醒
        int nextPollTimeoutMillis = 0; 
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    //當前消息隊列被同步控制,找到第一條異步的消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        //第一條異步消息還沒有到執行時間,計算須要阻塞的時間
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //找到了符合條件的消息,直接返回
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    //消息隊列爲空,直接進入阻塞狀態,等待喚醒
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                //首次循環的時候執行設置的IdleHandler
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                
                ....

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
複製代碼

上面消息的enqueueMessage()以及next()取出操做能夠很清晰的看到Handler.postDelay()的實現原理,就是消息按執行時間入隊,並延遲處理。

子線程使用Handler的注意點

子線程中須要咱們手動準備Looper,而後很方便的進行線程間通訊,可是在消息處理完成以後要手動將Looper退出,以避免形成內存泄漏。 從SDK 23 開始系統提供一個HandlerThread供咱們方便的在子線程中使用Handler;

其餘

Handler形成的內存泄漏

當咱們在Activity內部直接建立一個非靜態的Handler成員變量時,IDE就會提醒咱們,這樣使用Handler可能會形成內存泄漏,咱們來分析一下緣由在哪裏。

  1. Handler全部發送的Message都有一個target指向發送的Handler,用於後續處理回調
  2. 非靜態內部類隱式的持有外部類的一個引用
  3. 匿名內部類也隱式的持有外部類的一個引用

好,根據上面3點,咱們來看一下Activity的引用:

public class MainActivity extends AppCompatActivity {
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mHandler = new Handler();
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //do something
            }
        }, 10 * 60 * 1000);
    }
}
複製代碼

當這個runnable消息非發送出去以後,mHandler被msg持有,並安排到10分鐘以後執行,同時MainActivity也一直被mHandler和runnable持用。 當MainActivity退出時,由於Looper是UI線程的Looper,咱們沒法退出,msg還在消息隊列中還沒有執行,會阻止MainActivity被系統回收,就形成了內存泄漏。

避免這種狀況方法有不少種這裏提2種經常使用的方式:

  1. 定義靜態Handler對象,並傳人UI線程Looper

    static Handler sHandler = new Handler(Looper.getMainLooper());
    複製代碼
  2. 將咱們須要的Handler定義成靜態內部類,同時使用弱引用保存對Activity的引用

    public static class MyHandler extends Handler {
    
        private final WeakReference<Activity> mActivityWf;
    
        public MyHandler(WeakReference<Activity> activityWf) {
            mActivityWf = activityWf;
        }
    }
    複製代碼

    上面提到的同步控制

消息隊列的同步控制是經過postSyncBarrier()這個方法來實現, 說是Barrier,其實就是在隊列的頭部插入一條特殊的msg

public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } } 複製代碼

代碼其實也挺簡單的,就是在消息隊列適當的位置插入一條沒有target的msg,next()出去消息時,若是發現個同步控制消息,則它以後的同步消息就暫時不出列。

默認狀況下,咱們構建的Handler發送的都是同步消息,除非在Handler構建的時候指定發送的全部消息類型都爲異步消息。

固然通常咱們也不須要同步控制,雖然當前方法是public修飾的,可是hide,系統並無打算將此方法開放給咱們使用。

咱們再來看一下,系統哪裏用到了這個同步控制。

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
複製代碼

這是ViewRootImpl中的一個方法,這個方法執行以後,系統就開始進行頁面的繪製流程,咱們看到這裏調用postSyncBarrier(),這麼作的目的就是要讓UI繪製儘快完成,以避免形成卡頓。

相關文章
相關標籤/搜索