天天看點

錦囊篇|一文深入Handler

錦囊篇|一文深入Handler

前言

在日常開發中,我們勢必會使用到子線程和UI線程的通信,而起着橋梁作用的就是我們常用的​

​Handler​

​。但是他的内部是怎麼運作的?運作的過程中存在什麼問題?需要我們注意,本文将會詳細講解。

解析Handler

錦囊篇|一文深入Handler

從圖中我們就可以知道了,整個​

​Handler​

​工作組成的包括了​

​Handler​

​、​

​Looper​

​、​

​MessageQueue​

​、​

​Message​

​這四個部分。

MessageQueue和Message分别隻是一個隊列和消息實體類,自然不再多說。而Handler和Looper的具體是怎樣的呢?

在我的模拟Handler項目中,已經比較清晰的闡述了整個架構的工作流程,接下裡就是結合SDK代碼的一份解析了。

整個Handler往簡單了來說其實就幹了兩件事情:

  • 發送消息
  • 處理消息

發送消息

涉及到的三個函數​

​sendMessage()​

​、​

​enqueueMessage()​

​、​

​Looper.prepareMainLooper()​

​。

所有事情的起源要從​

​Looper.prepareMainLooper()​

​開始講起。這個函數處于​

​ActivityThread​

​中,沒有了解過這個類的讀者們需要知道,java程式設計一定是有一個主入口的,但是我們在整個Android程式設計中,從來沒有涉及過​

​main()​

​這個函數,是因為它已經包含在了​

​ActivityThread​

​這個類中,而它已經經過了複雜的封裝。

接下來看下這個​

​Looper.prepareMainLooper ()​

​函數。

public static void prepareMainLooper() {
        prepare(false); // 1
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper(); // 2
        }
    }
// 上述注釋1對應的函數
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));
    }
// 上述注釋2對應的函數
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
      

兩小段代碼,裡面用到了一個變量​

​sThreadLocal​

​,這個變量是使用​

​static final​

​修飾的,意味這全局唯一。從他主動抛出的異常我們也可以看出​

​Looper​

​這個對象也是一個唯一的變量。這是我們需要掌握的第一個知識點。

接下來是關于​

​sendMessage()​

​函數 這個函數其實是一個泛稱,他并不單單指​

​sendMessage()​

​,他還可以是​

​sendMessageAtTime()​

​、​

​sendMessageDelayed()​

​,他們都幹了一件事情——傳遞消息。通過一直向下探索,你就能知道他們最後調用的都是​

​enqueueMessage()​

​這個函數,也就是把消息放進了消息隊列中。

錦囊篇|一文深入Handler

當然要注意到一個事情,這裡我們使用了synchronized的關鍵詞,其實就是為了保障我們的資料同步。

沒有很多的操作,就是我們熟悉的連結清單操作。這裡沒有做展示,有興趣的朋友進到源碼往下翻一點,馬上就能看到了。

就這樣很簡單,并且很成功的讓我們的消息進入了消息隊列。

處理消息

接收完消息,我們要幹嘛?我們為什麼要發消息,因為我們要處理啊。

這裡我們要遇到的函數有:​

​Looper.loop()​

​、​

​dispatchMessage()​

​、​

​handleMessage()​

​。用過Handler的讀者們都應該知道我們是需要重寫​

​handleMessage()​

​這個函數的,用于對不同的消息作出響應,是以就不再多介紹。是以第一個講的就是​

​Looper.loop()​

​這個函數。

錦囊篇|一文深入Handler

一共兩段代碼,也是至關重要的一部分。在這個代碼中兩個至關重要的點:

(1)首先是問題是這麼一個死循環的函數,怎麼就沒引發ANR呢????

(2)通過​

​dispatchMessage()​

​如何分發這個消息的?target是什麼?

先是第一個問題的解答。網上的解答多種多樣。但是最關鍵的點其實是這樣的,​

​ANR​

​是圍繞​

​loop()​

​這個函數展開的,而​

​ANR​

​的出現也就是​

​loop()​

​的消息沒有得到及時的消費。

第二個問題。先說​

​target​

​這個爆紅的變量是什麼。​

​msg.target​

​也就是說這是​

​Message​

​的一個元素,搜尋​

​Message​

​就能找到如下圖示。

錦囊篇|一文深入Handler

原來​

​target​

​就是一個​

​Handler​

​,而這個​

​Handler​

​就是我們對應的主動建立​

​Handler​

​。然後就是​

​dispatchMessage()​

​函數了。

/**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }      

想來這就很清楚了,他也就是将事件分發給了​

​handleMessage()​

​處理,而​

​handleMessage()​

​又是我們自己來專門寫的。​

​msg.callback​

​是一個​

​Runnable​

​對象。

害,原來就是這樣啊。。

MessageQueue中消息如果為空,該咋辦???

其實他在​

​MessageQueue​

​的​

​next()​

​方法中已經有了對應的解決方案了。

Message next() {
        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) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 。。。。。。
                // 判斷隊列内部是否還有消息
                if (msg != null) {
                    // 。。。。。
                } else {
                    // No more messages.
                    // 不存在更多的資訊資料了
                    nextPollTimeoutMillis = -1;
                }

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

                // 兩種情況調用到IdleHandler
                // 1.消息隊列為空;2.下一條消息在将來才執行
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 如果idleHandler都不存在了,那隻能讓Looper阻塞
                // 繼續循環執行
                if (pendingIdleHandlerCount <= 0) {
                    // 出現了一個用于阻塞的判斷
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 執行這些IdleHandler,那IdleHandler是幹嘛的?
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }
      

上面我們講述了MessageQueue中的next()想必已經為我們指點迷津了,這裡再進行一個梳理。

  1. MessageQueue中存在資料時,就是正常的找到對應的Handler進行處理。
  2. 如果MessageQueue沒有資料了,這時候分為兩種情況:
  1. 尋找mIdleHandlers,也就是一些類似與系統服務了,進行處理。
  2. 連mIdleHandlers都沒有了,就進入阻塞狀态,等待新的任務進入将其喚醒。

既然知道了​

​IdleHandler​

​的存在,就看看他的具體運作方式是如何的,而最主要的就是他的接口方法​

​queueIdle()​

​。

// 在ActivityThread中存在
// 使用了aidl調用了底層服務
// 也就是說在你沒事幹的時候,他也會自己給自己安排事情做
private class Idler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            ActivityClientRecord a = mNewActivities;
            boolean stopProfiling = false;
            if (mBoundApplication != null && mProfiler.profileFd != null
                    && mProfiler.autoStopProfiler) {
                stopProfiling = true;
            }
            if (a != null) {
                mNewActivities = null;
                IActivityTaskManager am = ActivityTaskManager.getService();
                ActivityClientRecord prev;
                do {
                    if (localLOGV) Slog.v(
                        TAG, "Reporting idle of " + a +
                        " finished=" +
                        (a.activity != null && a.activity.mFinished));
                    if (a.activity != null && !a.activity.mFinished) {
                        try {
                            am.activityIdle(a.token, a.createdConfig, stopProfiling);
                            a.createdConfig = null;
                        } catch (RemoteException ex) {
                            throw ex.rethrowFromSystemServer();
                        }
                    }
                    prev = a;
                    a = a.nextIdle;
                    prev.nextIdle = null;
                } while (a != null);
            }
            if (stopProfiling) {
                mProfiler.stopProfiling();
            }
            applyPendingProcessState();
            return false;
        }
    }
    
final class GcIdler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        doGcIfNeeded();//做 GC 操作,VMRuntime.getRuntime().requestConcurrentGC();
        purgePendingResources();//清除懸而未決的資源
        return false;
    }
}

final class PurgeIdler implements MessageQueue.IdleHandler {
    @Override
    public boolean queueIdle() {
        purgePendingResources();//清除懸而未決的資源
        return false;
    }
}      

其他講實話,可能就是處理一系列沒有優先級是那種并需要立刻處理的事情。

HandlerThread

作為一個Android 已封裝好的輕量級異步類,其他他已經給我們做出給了突破,幫我們做到了系統的那一套消息傳遞機制,也幫我們省去自己造一個Thread+Handler的模式。

廢了這麼多話,其實講實話啊,他就是我上面已經說過的Thread+Handler的模式,但是有一個事情要突破啊,Looper咋辦?先放一下,讓我們先看看他的使用方法吧。

使用方法

// 建立Handler執行個體
HandlerThread handlerThread  = new HandlerThread("MainActivity");
// 線程啟動
handlerThread.start();
// 基于HandlerThread建構的Looper
Handler handler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                Log.e(TAG, msg.what+"");
            }
        };
// 使用handler發送消息
handler.sendEmptyMessage(1);
// 退出Lopper的死循環
handlerThread.quit();      

思考

咦,哪裡來的Looper呢???

那隻好先讓我們先看看HandlerThread的家庭裡有哪些成員了。

// 一個繼承自Thread的類
public class HandlerThread extends Thread {
    int mPriority; // 優先級
    int mTid = -1;
    Looper mLooper; // Looper
    private @Nullable Handler mHandler; // Handler
}      

在上文中我們已經講到過了,​

​Looper​

​​指的是在​

​ActivityThread​

​​中定義的,也就是一個全局型的​

​Looper​

​,并且他的初始化是在main()這個主入口進入時就已經初始化完畢的。

那我們這兒的Looper是在哪裡進行初始化的呢?

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare(); // 1 --》
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }      

在HandlerThread中我們能夠看到這樣的一段代碼,從注釋1我們慢慢深入,其實最後回到了我們文章最開始的一段代碼。

// 上述注釋1對應的函數
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)); // 2 -》
    }
// 對目前的線程和MessageQueue進行了存儲    
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }      

溫習一下,就是根據目前的Thread,來建立對應的Looper,并把他放到了ThreadLocal中進行存儲。

總結

Q1:Handler的記憶體洩漏執行個體。

Handler handler;
@Overrideprotected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);
         handler = new Handler(new Handler.Callback() {            @Override            public boolean handleMessage(@NonNull Message msg) {                startActivity(new Intent(MainActivity.this, HandlerTestActivity.class));                return false;            }        });
        new Thread(new Runnable() {            @Override            public void run() {                // 中斷3秒                SystemClock.sleep(3000);                handler.sendEmptyMessage(0);            }        }).start();    }
@Overrideprotected void onDestroy() {        super.onDestroy();        Log.e("onDestroy", "已銷毀");        handler.removeCallbacksAndMessages(0); // 2        handler = null; // 1    }      

如果不加注釋1和注釋2,這就是一段會記憶體洩露的代碼,看了代碼,應該也清楚邏輯十分簡單,就是一個跳轉。推出程式後,你仍然會看到跳轉,這就是​

​Handler​

​的記憶體洩漏。

Q2: 為什麼Handler不能在子線程建立?

這個問題其實有點問題,對于修改過底層的華為的作業系統并不存在這樣的問題,但是正常的​

​Android​

​原生系統就不行了,當然這針對的是無參構造函數,如果你通過傳入一個​

​Looper​

​來解決,像這樣​

​handler = Handler(Looper.getMainLooper(), Callback())​

​,也是沒問題的。

代碼如下

Handler handler;

@Override
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

         handler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(@NonNull Message msg) {
                startActivity(new Intent(MainActivity.this, HandlerTestActivity.class));
                return false;
            }
        });

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 中斷3秒
                SystemClock.sleep(3000);
                handler.sendEmptyMessage(0);
            }
        }).start();
    }

@Override
protected void onDestroy() {
        super.onDestroy();
        Log.e("onDestroy", "已銷毀");
        handler.removeCallbacksAndMessages(0); // 2
        handler = null; // 1
    }
      
錦囊篇|一文深入Handler

通過對報錯溯源,我們就能發現這樣一個問題。

錦囊篇|一文深入Handler

他拿不到​

​Looper​

​,因為他不是UI線程。其實這就是問題所在,我們上文講過​

​sThreadLocal​

​這個變量,他通過一個​

​get()​

​函數擷取了​

​Looper​

​。但是這裡存在一個問題,這個​

​get()​

​,他擷取的是什麼。是以我們也就進去看看好了。

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }      

原來,他的取法就是從一個​

​Map​

​中進行擷取的,而​

​key​

​,就是目前線程,是以當我們在子線程中建立​

​Handler​

​的時候,我們是拿不到​

​Looper​

​的,因為​

​key​

​并不對應。這個時候我們同樣明白了​

​Looper​

​也是一個唯一的,因為他不會為我們建立出來的一個子線程再添加一個​

​Looper​

​,而是共用。

當然你要知道使用ThreadLocalMap是一個通過消耗空間來換取時間效率的方案。

Q3:為什麼Handler構造方法裡面的Looper不是new出來的?

這個問題的性質和問題2有點類似了,唯一性。​

​Looper​

​作為一個事件處理的重要組成部分,想來我們已經看到了,就像多道程式設計技術一樣,這是一個不受控制的過程,我們需要瘋狂的思考安全性,同步性等問題。這也是唯一性的好處,是以事件統一處理,處理起來也就有序。至少在我們的平時使用中已經證明了這是一個可取的方法。

錦囊篇|一文深入Handler