Framework 源碼解析知識梳理(4) - 從源碼角度談?wù)?Handler 的應(yīng)用

一、前言

其實(shí),這篇文章本來應(yīng)該叫做"Handler源碼解析"的,但是想想寫Handler源碼的文章太多了,還是緩一緩,先寫些不一樣的,今天,我們從源碼的角度來總結(jié)一下應(yīng)用Handler的一些場景的原理。

二、Handler 的應(yīng)用

2.1 線程間的通信

在平時(shí)的開發(fā)當(dāng)中,Handler最常見的用法就是用于線程之間的通信,特別是當(dāng)我們在子線程中去處理耗時(shí)的任務(wù),當(dāng)任務(wù)完成之后,我們希望將結(jié)果發(fā)送到主線程中進(jìn)行處理,那么就會使用到Handler,基本的思想如下圖所示:


當(dāng)我們在子線程中通過Handler放入在主線程中的循環(huán)隊(duì)列時(shí),那么主線程就會收到消息,之后,我們就可以在主線程中進(jìn)行后續(xù)的處理,例如下面這樣:

public class MainActivity extends AppCompatActivity {

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Log.d("Handler", "HandleMessageThreadId=" + Thread.currentThread().getId());
        }
    };

    private void startThread() {
        new Thread() {
            @Override
            public void run() {
                super.run();
                Log.d("Handler", "ThreadId=" + Thread.currentThread().getId());
                mHandler.sendEmptyMessage(0);
            }
        }.start();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("Handler", "MainThreadId=" + Thread.currentThread().getId());
        startThread();
    }
    
}

打印出上面的線程Id,可以看到最后handleMessage收到消息的時(shí)候是在主線程當(dāng)中:

2.2 實(shí)現(xiàn)延時(shí)操作

除了線程間的通信之外,我們還可以通過Handler提供的sendXXXDelay方法,實(shí)現(xiàn)延時(shí)操作,延時(shí)操作有兩種目的:

  • 一種是希望讓不那么緊急的任務(wù)延后執(zhí)行,例如在應(yīng)用啟動過程中,我們在onCreate方法中的任務(wù)不是那個(gè)緊急,那么可以通過Handler發(fā)送一個(gè)延時(shí)消息出去,讓它不占用主線程去渲染布局的資源,從而提高應(yīng)用的啟動速度。
  • 另一種就是防抖動操作,我們收到一個(gè)命令,并不是馬上執(zhí)行它,而是通過HandlersendxxxDelay方法延時(shí),如果在這段事件內(nèi)又有一個(gè)相同的命令來到了,那么就把之前的消息移除,再放入一個(gè)新的延時(shí)消息。
    例如下面的例子,當(dāng)我們點(diǎn)擊一個(gè)按鈕之后,我們不立刻執(zhí)行任務(wù),而是一段時(shí)間之后仍然沒有收到第二次點(diǎn)擊事件采取執(zhí)行任務(wù):
    private Handler mDelayHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.d("Handler", "handleMessage");
        }

    };

    private void performClickDelay() {
        Log.d("Handler", "performClickDelay");
        mDelayHandler.removeMessages(1);
        mDelayHandler.sendEmptyMessageDelayed(1, 500);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTv = (TextView) findViewById(R.id.tv);
        mTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                performClickDelay();
            }
        });
    }

我們連續(xù)多次點(diǎn)擊按鈕之后,只收到了一次消息:


2.3 使用HandlerThread在異步線程執(zhí)行耗時(shí)操作

由于Looper其實(shí)是線程的一個(gè)私有變量(ThreadLocal),主線程可以有Looper,子線程同樣也可以有Looper,只不過從子線程中的Looper中的MessageQueue中取出消息之后,是在子線程當(dāng)中處理的,那么我們就可以通過它來執(zhí)行異步操作,其基本思想如下圖所示:


HandlerThread就是基于這一思想來實(shí)現(xiàn)的,關(guān)于HandlerThread的內(nèi)部實(shí)現(xiàn),可以參考之前的這篇文章 Android 異步任務(wù)知識梳理(2) - HandlerThread 源碼解析,它的本質(zhì)就是當(dāng)異步線程啟動之后,會初始化這個(gè)線程私有的Looper,因此,當(dāng)我們通過HandlerThread中的getLooper()方法獲得這個(gè)Looper之后,在通過這個(gè)Looper來創(chuàng)建一個(gè)Handler,那么這個(gè)HandlerhandleMessage回調(diào)時(shí)所在的線程就是這個(gè)異步線程。

下面是一個(gè)簡單的事例:

    private void useHandlerThread() {
        final HandlerThread handlerThread = new HandlerThread("handlerThread");
        System.out.println("MainThreadId=" + Thread.currentThread().getId() + ",handlerThreadId=" + handlerThread.getId());
        handlerThread.start();
        MyHandler handler = new MyHandler(handlerThread.getLooper());
        handler.sendEmptyMessage(0);
    }

    private class MyHandler extends Handler {

        MyHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            System.out.println("handleMessageThreadId=" + Thread.currentThread().getId());
        }
    }

我們打印出handleMessage的執(zhí)行時(shí)的線程ID,可以看到它就是HandlerThread的線程ID,因此我們就可以通過發(fā)送消息的方式,在異步線程執(zhí)行一些耗時(shí)的操作:

2.4 使用AsyncQueryHandlerContentProvider進(jìn)行操作

如果你的本地?cái)?shù)據(jù)使用ContentProvider封裝的,那么對這些數(shù)據(jù)的增刪改查操作,為了不影響主線程,應(yīng)該在子線程中進(jìn)行操作,而AsyncQueryHandler就是系統(tǒng)為我們提供的很方便的工具類,它通過Handler + HandlerThread的方式,實(shí)現(xiàn)了異步的增刪改查。

它是在HandlerThread的基礎(chǔ)之上擴(kuò)展出來的,其基本思想如下圖所示,這一框架就保證了發(fā)起命令和接收回調(diào)是在同一個(gè)線程,而任務(wù)的執(zhí)行則是在另一個(gè)線程:


具體的實(shí)現(xiàn)原理可以參考這篇文章 Android 異步任務(wù)知識梳理(3) - AsyncQueryHandler 源碼解析

當(dāng)然AsyncQueryHandler限制了只能操作通過ContentProvider封裝的數(shù)據(jù),我們可以參考它的思想,進(jìn)行擴(kuò)展,實(shí)現(xiàn)對于數(shù)據(jù)庫的增刪改查。

2.5 使用Handler機(jī)制檢測應(yīng)用中的卡頓問題

對于每個(gè)應(yīng)用程序來說,它的入口函數(shù)為ActivityThread中的main()方法:

   public static void main(String[] args) {
        //...
        Looper.prepareMainLooper();
        //...
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

而在main()函數(shù)的最后,則構(gòu)建一個(gè)在主線程中的循環(huán)隊(duì)列,之后應(yīng)用程序收到的事件之后,還主線程就會被喚醒,進(jìn)行事件的處理,假如這一處理的事件過長,那么就會發(fā)生ANR,因此,我們就可以通過計(jì)算主線程中對于單次消息的處理時(shí)間,從而間隔地判斷是否存在卡頓問題。
那么要怎么知道主線程中單次消息的處理時(shí)間呢,我們查看Looper的源碼:

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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

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

對于消息的處理對應(yīng)這句:

msg.target.dispatchMessage(msg);

而在這句話的前后,則調(diào)用了Printer類打印,那么我們就可以通過這兩次打印的之間的時(shí)長來獲得單次消息的處理時(shí)間。

public class MainLoopMonitor {

    private static final String MSG_START = ">>>>> Dispatching";
    private static final String MSG_END = "<<<<< Finished";
    private static final int TIME = 1000;
    private Handler mExecuteHandler;
    private Runnable mExecuteRunnable;

    private static class Holder {
        private static final MainLoopMonitor INSTANCE = new MainLoopMonitor();
    }

    public static MainLoopMonitor getInstance() {
        return Holder.INSTANCE;
    }

    private MainLoopMonitor() {
        HandlerThread monitorThread = new HandlerThread("LooperMonitor");
        monitorThread.start();
        mExecuteHandler = new Handler(monitorThread.getLooper());
        mExecuteRunnable = new ExecutorRunnable();
    }

    public void startMonitor() {
        Looper.getMainLooper().setMessageLogging(new Printer() {
            @Override
            public void println(String x) {
                if (x.startsWith(MSG_START)) {
                    mExecuteHandler.postDelayed(mExecuteRunnable, TIME);
                } else if (x.startsWith(MSG_END)) {
                    mExecuteHandler.removeCallbacks(mExecuteRunnable);
                }
            }
        });
    }
    
    private class ExecutorRunnable implements Runnable {

        @Override
        public void run() {
            StringBuilder sb = new StringBuilder();
            StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
            for (StackTraceElement s : stackTrace) {
                sb.append(s).append("\n");
            }
            System.out.println("MainLooperMonitor:" + sb.toString());
        }
    }
}

假如我們像下面這樣,在主線程中進(jìn)行了耗時(shí)的操作:

public void anrButton(View view) {
        for (int i = 0; i < (1 << 30); i++) {
            int b = 6;
        }
}

那么就會打印出下面的堆棧信息:


三、Handler 使用注意事項(xiàng)

在介紹完Handler的這些用法,我們再來總結(jié)一下使用Handler是比較容易犯的一些錯(cuò)誤。

3.1 內(nèi)存泄漏

一個(gè)比較常見的錯(cuò)誤就是將定義Handler的子類時(shí)將它作為Activity的內(nèi)部類,而由于內(nèi)部類會默認(rèn)持有外部類的引用,因此,如果這個(gè)內(nèi)部類的實(shí)例在Activity試圖被回收的時(shí)候,沒有被銷毀掉,那么就會導(dǎo)致Activity無法被回收,從而引起內(nèi)存泄漏。

Handler實(shí)例無法被銷毀掉最常見的情況就是,我們通過它發(fā)送了一個(gè)延時(shí)消息出去,此時(shí)這個(gè)消息會被放入到該線程所對應(yīng)的Looper中的MessageQueue當(dāng)中,而該消息為了能在得到執(zhí)行之后,回調(diào)到對應(yīng)的Handler,因此它會持有這個(gè)Handler的實(shí)例。

從引用鏈的角度來看就是下面的情況:


這個(gè)大家見得很多,就不多舉例子了。

3.2 在子線程中實(shí)例化 new Handler()

在子線程中new Handler()有下面兩個(gè)需要注意的點(diǎn):

(1) 在 new Handler() 之前調(diào)用 Looper.prepare() 方法

另外一個(gè)錯(cuò)誤就是我們在子線程當(dāng)中,直接調(diào)用new Handler(),那么這時(shí)候會拋出異常,例如下面這樣:

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

            @Override
            public void run() {
                mHandler = new MyHandler();
            }

        }.start();
    }

拋出的異常為:


原因是在Handler的構(gòu)造函數(shù)中,會去檢查當(dāng)前線程的Looper是否已經(jīng)被初始化:

    public Handler(Callback callback, boolean async) {
        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;
    }

如果沒有,那么就會拋出上面代碼當(dāng)中的異常,正確的做法,是需要保證我們在new Handler之前,要保證當(dāng)前線程的Looper對象已經(jīng)被初始化,也就是Looper當(dāng)中的下面這個(gè)方法被調(diào)用:

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

(2) 如果希望通過該 Looper 接收消息那么要調(diào)用 Looper.loop() 方法,并且需要放在最后一句調(diào)用

假如我們只調(diào)用了prepare()方法,僅僅只是初始化了一個(gè)線程的私有的變量,此時(shí)是無法通過這個(gè)Looper構(gòu)建的Handler來接收消息的,例如下面這樣:

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

            @Override
            public void run() {
                Looper.prepare();
                mHandler = new MyHandler();
            }
            
        }.start();
    }

    public void anrButton(View view) {
        mHandler.sendEmptyMessage(0);
    }

    private static class MyHandler extends Handler {

        @Override
        public void handleMessage(Message msg) {
            Log.e("TAG", "handleMessageId=" + Thread.currentThread().getId());
        }
    }

此時(shí)我們應(yīng)當(dāng)調(diào)用loop方法讓這個(gè)循環(huán)隊(duì)列開始工作,并且該調(diào)用一定要位于最后一句,因?yàn)橐坏┱{(diào)用了loop方法,那么它就會進(jìn)入等待 -> 收到消息 -> 喚醒 -> 處理消息 -> 等待的循環(huán)過程,而這一過程只有等到loop方法返回的時(shí)候才會結(jié)束,因此,我們初始化Handler的語句要放在loop方法之前,類似于下面這樣:

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

            @Override
            public void run() {
                Looper.prepare();
                mHandler = new MyHandler();
                //最后一句再調(diào)用
                Looper.loop();
            }

        }.start();
    }

四、小結(jié)

Handler的機(jī)制似乎已經(jīng)成為面試必問的題目,如果大家能在回答完內(nèi)部的實(shí)現(xiàn)原理,再根據(jù)這些實(shí)現(xiàn)原理引申出上面的幾個(gè)應(yīng)用和注意事項(xiàng),可以加分不少哦~


更多文章,歡迎訪問我的 Android 知識梳理系列:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容