Handler線程通信機制:實戰、原理、性能優化!

Handler的使用


我們知道,Android中,不允許應用程序在子線程中更新UI,UI的處理必須在UI線程中進行,這樣Android定製了一套完善的線程間通信機制——Handler通信機制。Handler作爲Android線程通信方式,高頻率的出現在我們的日常開發工作中,我們常用的場景包括:使用異步線程進行網絡通信、後臺任務處理等,Handler則負責異步線程與UI線程(主線程)之間的交互。

我們來看Handler的使用示例:

    public static final int LOAD_COM = 1;//加載任務的id標誌
    
    private Handler mHandler = new MyHandler(MainActivity.this);

    private static class MyHandler extends Handler{
        private final WeakReference<MainActivity> mActivity;

        private MyHandler(MainActivity activity) {
            this.mActivity = new WeakReference(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {//ui線程中,負責消息返回的處理邏輯
            super.handleMessage(msg);
            switch (msg.what){
                case LOAD_COM:
                    Log.d("TestHandler", msg.obj.toString());
                    MainActivity mainActivity = mActivity.get();
                    if (mainActivity != null){
                        mainActivity.mTextView.setText(msg.obj.toString());
                    }
                    break;
            }
        }
    };
    
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_load:
                new Thread(){
                    @Override
                    public void run() {//後臺線程中執行邏輯
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Message message = Message.obtain();
                        message.what = LOAD_COM;
                        message.obj = "加載完成";
                        mHandler.sendMessage(message);//從後臺線程中,發送消息給UI線程
                    }
                }.start();
                break;
        }
    }
demo邏輯解析:
  1. 在Activity中,創建了一個Handler對象。
  2. 當按鈕start_load點擊時,啓動一個後臺線程,模擬一個後臺加載過程(線程休眠1秒)。
  3. 後臺任務完成後,使用Handler對象的sendMessage方法發送消息(一個Messaage對象)給UI線程。
  4. UI線程中,Handler對象的handleMessage方法負責處理消息的返回。

Demo中的例子是我們在Android開發中經常使用到的方式,Handler非常簡單的幫助我們實現了UI線程和後臺線程之間的通信。那麼Handler是如何做到線程間通信的呢?

接下來我們以源碼來分析Handler的實現機制。

Handler的實現機制&源碼分析


Handler機制介紹

Handler機制是Android通信機制的一個重要組成部分。在Android中,應用程序是消息驅動的,每個應用程序的UI線程(主線程)都會維護一個消息隊列,並且會不斷的從這個消息隊列中取出消息進行處理。Handler機制實現了消息在線程之間的通信。

Handler消息處理機制包括了3個組成部分:消息循環、消息發送、消息處理,這其中涉及到幾個重要類:Handler、Message、Looper和MessageQueue。

一個線程,想要處理Hander發送的消息,必須做好以下幾點準備:

  1. 首先要有一個消息循環,也就是要創建一個Looper對象。
  2. 將Looper對象綁定到當前線程上,也就是必須要調用Looper.prepare()方法。
  3. 開啓Looper的循環,也就是要調用Looper的loop方法。

我們接下來以Handler創建、消息的發送、消息循環、消息處理的順序來展開分析。

Handler對象的創建

要想使用Handler進行通信,首先要創建一個Handler對象。

Handler的構造函數:

    public Handler() {//版本1
        this(null, false);
    }
    public Handler(@Nullable Callback callback) {//版本2
        this(callback, false);
    }
    public Handler(@NonNull Looper looper) {//版本3
        this(looper, null, false);
    }
    public Handler(@NonNull Looper looper, @Nullable Callback callback) {//版本4
        this(looper, callback, false);
    }
    @UnsupportedAppUsage
    public Handler(boolean async) {//版本5。不支持App使用
        this(null, async);
    }
    public Handler(@Nullable Callback callback, boolean async) {//版本6。主要實現邏輯在這裏
        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 " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    @UnsupportedAppUsage
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {//版本7。不支持App直接使用,系統內部使用
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

Handler的構造函數有多個重載版本,但邏輯實現都在版本6和版本7中。在版本7中,只是簡單的屬性賦值,這裏我們來分析版本6的實現。

邏輯解析:
  1. Handler有多個重載版本,但可供我們使用版本的async參數都是默認值false。
  2. 首先根據布爾值FIND_POTENTIAL_LEAKS判斷是否需要進行泄漏的檢測,默認是false。
  3. 將Looper.myLooper()賦值給mLooper對象。
  4. 將looper.mQueue賦值給mQueue對象。
  5. 將參數callback、async賦值給相應屬性。

Looper.myLooper()返回一個Looper對象,我們來看它的實現。

Looper.myLooper()方法

myLooper方法(位置:android.os.Looper.java):

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

這裏直接返回了當前線程的一個線程本地對象(線程本地對象的變量,在每個線程中都是獨立存儲的)。

我們來看sThreadLocal的初始化:

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();//線程的本地對象,存儲當前線程對應的Looper
    
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {//prepare方法只能調用一次
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));//創建Looper對象並綁定到當前線程。
    }
邏輯解析:
  1. sThreadLocal是一個線程本地對象,存儲了當前線程對應的Looper對象。
  2. Looper類對外提供了prepare()靜態方法來初始化Looper,prepare無參方法調用了prepare的有參方法。
  3. prepare方法的參數quitAllowed:表示是否允許MessageQueue退出循環,默認參數是true,表示允許退出。
  4. 每個線程中只能調用一次prepare方法。
  5. prepare方法最終會創建一個Looper對象,並使用線程本地對象sThreadLocal,綁定到當前線程中。

prepare方法負責Looper對象的創建以及將Looper對象綁定到當前線程,所以要想使用Handler機制,Looper.prepare方法必須在使用前調用。

Looper的構造函數:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
構造函數做了2件事:
  1. 初始化MessageQueue對象,MessageQueue負責管理消息隊列。MessageQueue使用一個單鏈表的數據結構來管理消息的添加和刪除。
  2. 獲取當前線程的引用。

小結:

  1. Handler有多個重載版本,但可供我們使用版本的async參數都是默認值false。
  2. Handler機制要想正常使用,必須在初始化時,調用Looper.prepare()方法。
  3. Handler構造函數完成後,我們也就準備好了Looper對象,以及MessageQueue對象。

消息的發送

在Demo中,我們創建完成了Handler對象之後,實現了handleMessage方法來接收後臺線程發來的消息。消息的發送是由Handler對象的sendMessage方法完成的,我們先來分析它的實現。

sendMessage方法:

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

這裏直接調用了sendMessageDelayed方法,第二個參數爲0,表示不作延遲。sendEmptyMessage等發送消息的方法,最終也會調用sendMessageDelayed方法。

sendMessageDelayed方法:

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {//矯正延遲時間
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

這裏調用了sendMessageAtTime方法,參數1:msg;參數2:SystemClock.uptimeMillis() + delayMillis,其中SystemClock.uptimeMillis()表示從開機到現在的毫秒數。

這裏將延遲時間delayMillis,轉換爲了絕對時間,傳遞給了sendMessageAtTime方法。

sendMessageAtTime方法:

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
邏輯解析:
  • 這裏有2個參數,msg表示要發送的消息,uptimeMillis表示要處理消息的時間(這裏是絕對時間)。
  • 獲取消息隊列,消息隊列不允許未初始化。
  • 調用enqueueMessage方法,讓消息進入消息隊列中。

enqueueMessage方法:

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
邏輯解析:
  • 將當前對象賦給msg的target屬性。
  • 將調用者的uid賦給msg的workSourceUid屬性。
  • 判斷是否是異步消息,如果是異步,則調用setAsynchronous方法。我們在Handler初始化中得知,我們通常的應用使用的都是默認同步的,最後調用queue.enqueueMessage。

MessageQueue的enqueueMessage方法:

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {//msg.target不能爲null。
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {//msg不能是正在使用中
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {//同步處理
            if (mQuitting) {//消息循環正在退出
                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.markInUse();//消息標記爲正在使用
            msg.when = when;
            Message p = mMessages;//表示隊列頭的消息
            boolean needWake;//該變量表示是否需要喚醒線程執行消息處理
            if (p == null || when == 0 || when < p.when) {//如果隊列頭消息爲空,或者當前消息是插隊的消息(time值爲0時),或者當前消息的執行時間比隊列頭的要早。這裏會優先執行當前消息。
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;//表示是否需要阻止喚醒loop循環
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();//如果隊列頭的消息是異步的,並且target爲空,並且當前線程處理是阻塞的,則表示需要喚醒。
                Message prev;
                for (;;) {//這裏將當前消息插入到隊列中的合適位置
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {//如果消息爲空或者未到執行時間,則退出循環,找到了合適的位置了
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {//如果需要喚醒屬性是true並且隊列頭消息是異步的,則不需要喚醒。
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;//插入當前消息
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {//如果需要喚醒,則調用nativeWake方法
                nativeWake(mPtr);
            }
        }
        return true;
    }
邏輯解析:
  1. 消息的target爲空,或者msg正在處理,則拋出error錯誤。
  2. 接下來進行同步塊的處理。
  3. 當消息循環正在退出時,將當前消息回收,並退出。
  4. 對msg進行處理,設置msg的when,將msg狀態置位正在使用。
  5. 將隊列頭消息取出。
  6. 如果隊列頭消息爲空,或者當前消息是插隊的消息(time值爲0時),或者當前消息的執行時間比隊列頭的要早,將消息插入到隊列頭。
  7. 如果消息不需要立即執行,則需要插入到消息隊列中。
  8. for循環中,將當前消息插入到隊列中的合適位置。
  9. 如果需要喚醒線程,則調用nativeWake方法(nativeWake是一個native方法)。

小結:

  1. 我們使用Handler對象的sendMessage()等方法進行消息的發送。
  2. 最終消息會發送到MessageQueue的enqueueMessage方法中。
  3. 如果消息不需要立即執行,則把消息添加到MessageQueue的消息隊列中的合適位置。消息隊列的順序是按執行時間(絕對時間)順序排列的。
  4. 如果消息需要立即執行,則將消息添加到隊列頭,並賦值給mMessages(隊列頭指針)。
  5. 判斷線程是否需要喚醒,如果需要,則調用nativeWake(mPtr)執行喚醒。

消息循環

在消息的發送部分,消息發送後,會將消息添加到消息隊列中,如果滿足立即執行條件,則喚醒線程執行。

那麼到了這裏,你也許會問,消息的處理在哪進行的呢?將消息添加到消息隊列之後,接下來怎麼處理呢?

不要着急,消息的處理邏輯不在Handler裏面,而是在Looper的loop方法中進行處理的,Looper和MessageQueue負責我們的消息循環部分。

Looper的loop方法:

消息循環是在調用了Looper.loop方法之後開始的,我們來看:

    public static void loop() {
        final Looper me = myLooper();//獲取當前線程的Looper對象
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;//獲取MessageQueue對象

        // 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();//清空遠程調用端的uid和pid,用當前本地進程的uid和pid替代

        ……

        for (;;) {
            Message msg = queue.next(); // 從消息隊列中獲取消息
            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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
                msg.target.dispatchMessage(msg);//執行消息處理,將消息發送給Handler對象進行處理。這裏的target,是當前消息的Handler對象
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", 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();
        }
    }
邏輯解析:
  1. 獲取當前線程的Looper對象。
  2. 清空遠程調用端的uid和pid。
  3. 消息處理是在一個無限循環的for循環中處理的。
  4. 在for循環中,首先從消息隊列中獲取消息。
  5. 若消息爲null,則表示消息循環隊列已退出,則結束循環。
  6. 否則,執行性能相關的日誌輸出,我們通過logging可以實現message處理的性能檢測,我們將在後續文章中分析它的實現。
  7. 調用msg.target.dispatchMessage(msg)執行消息處理,將消息發送給Handler對象進行處理。這裏的target,是當前消息的Handler對象。
  8. 最後將消息對象進行回收處理。

loop方法調用Handler對象的dispatchMessage進行消息的處理,消息處理部分我們稍後分析,我們先來看消息獲取的queue.next()方法。

MessageQueue的next方法

MessageQueue是一個消息隊列,內部是使用單向鏈表來實現的,Message對象的next屬性保存列表中的下一個,MessageQueue對象的mMessages屬性是鏈表頭,也就是當前要處理的消息。

MessageQueue的next方法:

    @UnsupportedAppUsage
    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // 閒置任務的數量
        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) {//表示添加了一個消息屏障。這個屏障之後的所有同步消息都不會被執行,即使時間已經到了也不會執行。
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());//該循環會忽略同步消息,查找到異步第一個異步消息
                }
                if (msg != null) {
                    if (now < msg.when) {//沒到執行時間,則設置休眠時間
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {//有消息需要處理,則返回將要處理的消息
                        // Got a message.
                        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 {//表示隊列中已經沒有消息需要處理了,可以進行無限休眠
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {//如果消息循環已退出,則進行資源銷燬
                    dispose();
                    return null;
                }

                ……
                //進行閒置時間的任務處理。

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

邏輯解析:

MessageQueue的next方法負責了從消息隊列中取出消息,如果沒有要執行的消息,則進行休眠,直到有消息需要執行爲止。

  1. for循環內首先調用nativePollOnce(ptr, nextPollTimeoutMillis),這是一個native方法,通過Native層的MessageQueue實現休眠。

    nextPollTimeoutMillis表示休眠時間:

    • nextPollTimeoutMillis == -1,一直休眠不會超時。
    • nextPollTimeoutMillis == 0,不會休眠。
    • nextPollTimeoutMillis > 0,最長休眠時間不超過nextPollTimeoutMillis毫秒,如果期間有程序喚醒會立即返回。
  2. 如果msg.target爲null,則表示添加了一個消息屏障。這個屏障之後的所有同步消息都不會被執行,即使時間已經到了也不會執行。

  3. 如果存在消息屏障,則該循環會忽略同步消息,查找到異步第一個異步消息,並進行處理。

  4. 如果消息存在,並且當前消息沒到執行時間,則設置休眠時間。否則消息需要處理,則返回將要處理的消息。

  5. 如果隊列中已經沒有消息需要處理了,可以進行無限休眠,等待喚醒,而喚醒操作是調用native方法nativeWake()來實現的。

  6. 如果當前隊列中沒有任務需要處理,則可以利用空閒資源處理一些非緊急任務(閒置任務),以執行一些非緊急的任務。

消息處理

我們來分析消息的處理,消息處理邏輯是在Looper中的loop方法調用Handler的dispatchMessage方法中進行的。

Handler的dispatchMessage方法:

    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

如果Message對象的callback屬性存在,則執行callback的run方法,否則,調用handleMessage進行消息處理。

Handler的handleMessage方法

    public void handleMessage(@NonNull Message msg) {
    }

Handler的handleMessage方法是一個空方法,需要我們使用時,在子類中實現,就像demo中一樣。

UI線程Handler消息機制的初始化


經過Handler的原理分析,我們瞭解到Handler機制在初始化階段,需要調用Looper的prepare方法進行Looper初始化操作,並且需要調用Lopper.loop()開啓消息循環。

但是我們在UI線程中使用時,並沒有進行這些初始化操作,那麼UI線程中,Handler機制是如何進行初始化的呢?

在Looper的方法中,我們發現有一個叫prepareMainLooper()的方法,貌似是執行UI線程Handler初始化的地方?

prepareMainLooper()方法:

    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

這裏直接調用了prepare()方法,並且參數爲false,表示不允許消息隊列循環退出;然後進行了線程校驗,如果是非ui線程則會報錯;最後將Looper對象賦值給sMainLooper。

那麼該方法是在什麼地方?什麼時候調用的呢?

ActivityThread的main方法

我們來看源碼:

    public static void main(String[] args) {
        ……

        Looper.prepareMainLooper();

        ……
        Looper.loop();

        ……
    }

ActivityThread的main方法是在一個進程被創建的時候調用的,也即是說,進程創建過程中,會執行Handler的相關初始化工作。

這裏調用了Looper.prepareMainLooper()方法,初始化了Looper,並關聯了UI線程。
調用了Looper.loop()開啓了Looper的循環。

Handler的性能問題


在使用Handler的時候,我們通常會因爲使用時的不小心,造成一些內存泄漏等方面的性能問題。

下面來進行原因分析及如何來避免。

Handler的內存泄漏

原因

Handler的內存泄漏問題,通常是因爲對象存活的生命週期不一致問題導致的,具體原因如下:

  1. Handler持有了外部Activity的引用。
  2. 在Activity中啓動了一個後臺線程,執行耗時任務,後臺線程持有Handler的引用,當任務執行時間很長時。
  3. Activity在耗時任務執行期間結束執行了,這時,因爲後臺線程中持有了Handler對象,Handler對象持有Activity的引用,所以就會造成Activity在GC時,回收不掉的問題。
  4. 造成了內存泄漏。

思路

內存泄漏的原因是因爲我們在執行異步任務時,持有了Activity的引用,從而導致內存泄漏問題。

那麼如果我們的Handler不持有外部類的引用不就可以了嗎?那麼我們如何做到不持有外部Activity的引用呢?

解決方法一

解決方法參考本章開頭的Demo:

  1. Handler一定要實現一個自定義的靜態內部類,這裏爲什麼是靜態內部類呢?因爲非靜態內部類會持有外部類的引用,而靜態內部類是不持有外部類引用的。
  2. Handler的handleMessage方法中,如果需要用到Activity的屬性或對象,這時我們不得不持有Activity的引用,怎麼辦呢?那就是像上面我們的Demo一樣,使用一個弱引用來保存Activity,原理就是,弱引用在GC時,當內存緊張時會進行回收。
  3. Activity在Handler中使用時,一定要判斷是否爲null(是否已經回收)。

解決方法二

如果我們在Activity進行退出時,把相應的異步消息都進行註銷,這樣Handler都成爲可回收狀態後,我們的Activity自然也就是可回收的了,也就不會內存泄漏了。

  1. 我們可以在Activity的onDestory中調用Handler對象的removeCallbacksAndMessages()方法,參數一定要傳null。
  2. 注意,需要每一個Handler對象都要執行該方法。

Handler的removeCallbacksAndMessages()方法

    public final void removeCallbacksAndMessages(@Nullable Object token) {
        mQueue.removeCallbacksAndMessages(this, token);
    }

這裏調用了MessageQueue的removeCallbacksAndMessages方法。

MessageQueue的removeCallbacksAndMessages方法

    void removeCallbacksAndMessages(Handler h, Object object) {
        if (h == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;

            // Remove all messages at front.
            while (p != null && p.target == h
                    && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();
                p = n;
            }

            // Remove all messages after front.
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }

該方法會清除掉MessageQueue隊列中的所有消息,並且對Message進行回收。

Message的recycleUnchecked方法

    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;//移除了handler的引用
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

該方法對Message對象進行資源回收。

總結


  1. Android中的線程通信機制使用了Handler來實現的。
  2. Android機制的初始化,需要調用Looper.prepare()方法進行Looper的創建及關聯到當前線程,然後調用Looper.loop()方法實現消息的循環。
  3. 消息的發送是通過Handler的sendMessage()或post()等方法執行的,然後調用了MessageQueue的enqueueMessage()方法來把消息添加到消息隊列中,如果需要喚醒操作,則調用native方法nativeWake進行喚醒。
  4. MessageQueue是消息隊列,內部採用單鏈表的數據結構來存儲消息列表,鏈表的頭是mMessages對象。
  5. MessageQueue實現了消息隊列的添加、消息的獲取、消息循環的休眠、消息循環的喚醒等。
  6. 消息循環的啓動是通過Looper.loop()來實現的,它啓動了一個無限循環來進行消息的處理。
  7. Looper.loop()每次循環都會調用MessageQueue的next()方法來實現消息的獲取,當然如果當前沒有消息需要執行,則會在next方法中調用native方法nativePollOnce()進行休眠。
  8. MessageQueue的next()方法中實現了消息屏障機制,可以通過添加消息屏障來阻塞消息隊列中同步消息的執行。
  9. MessageQueue的next()方法中也實現了空閒任務執行機制,當線程空閒時,我們可以執行一些非緊急任務。
  10. 我們可以調用Looper.quit()或Looper.quitSafely()方法來退出消息循環,但是UI線程不可退出消息循環。
  11. UI線程是在ActivityThread的main方法中執行的Handler機制的初始化工作,調用了Looper.prepareMainLooper()方法進行初始化和Looper.loop()方法開啓消息循環。
  12. Handler使用不當常常會導致內存泄漏等性能問題。Handler內存泄漏的原因通常是因爲引用的持有及生命週期不一致導致的。
  13. 我們可以通過弱引用+靜態內部類的方式來解決Handler內存泄漏的問題,或者在Activity結束時,調用Handler的removeCallbacksAndMessages(null)方法來回收Handler及消息。
  14. 另外,Message對象過多的創建也會導致內存過大,GC頻繁等問題,我們可以通過Message類提供的obtain()方法來獲取Message對象,obtain內部使用了一個對象池來減少新對象的創建。

通過上面的分析,我們已經對如何使用Handler消息機制和它的原理有了很清晰的瞭解。但是,在Looper循環的時候,loop()方法中的無限循環是如何做到不佔用系統資源的呢?它的休眠和喚醒又是什麼原理實現的呢?我們將在下一章進行分析~

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