Handler知識(shí)收集整理-2

簡書對(duì)文章長度有限制,緊跟著上一篇文章:

(Handler知識(shí)收集整理-1)

http://www.lxweimin.com/p/4b0bfea99e00

這里接著寫。。。。

4. 面試常見問題

  1. Android中,有哪些是基于Handler來實(shí)現(xiàn)通信的?

答:App的運(yùn)行、更新UI、AsyncTask、Glide、RxJava等

  1. 處理Handler消息,是在哪個(gè)線程?一定是創(chuàng)建Handler的線程么?

答:創(chuàng)建Handler所使用的Looper所在的線程

  1. 消息是如何插入到MessageQueue中的?

答: 是根據(jù)when在MessageQueue中升序排序的,when=開機(jī)到現(xiàn)在的毫秒數(shù)+延時(shí)毫秒數(shù)

  1. 當(dāng)MessageQueue沒有消息時(shí),它的next方法是阻塞的,會(huì)導(dǎo)致App ANR么?

答:不會(huì)導(dǎo)致App的ANR,是Linux的pipe機(jī)制保證的,阻塞時(shí),線程掛起;需要時(shí),喚醒線程

  1. 子線程中可以使用Toast么?

答:可以使用,但是Toast的顯示是基于Handler實(shí)現(xiàn)的,所以需要先創(chuàng)建Looper,然后調(diào)用Looper.loop。

  1. Looper.loop()是死循環(huán),可以停止么?

答:可以停止,Looper提供了quit和quitSafely方法

  1. Handler內(nèi)存泄露怎么解決?

答: 靜態(tài)內(nèi)部類+弱引用 、Handler的removeCallbacksAndMessages等方法移除MessageQueue中的消息

下面逐個(gè)來看看這些問題:

4.1 Android中常見的Handler使用
  1. 保證App的運(yùn)行

App的入口其實(shí)是ActivityThread的main方法:

    public static void main(String[] args) {
        // 創(chuàng)建主線程中的Looper
        Looper.prepareMainLooper();
        
        // 創(chuàng)建ActivityThread對(duì)象
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            // 創(chuàng)建用于通信的Handler
            sMainThreadHandler = thread.getHandler();
        }
        // 執(zhí)行l(wèi)oop方法
        Looper.loop();
    }
    

可以看到這個(gè)main方法中,主要有如下幾步:

  1. 創(chuàng)建了主線程中的Looper,創(chuàng)建Looper時(shí),會(huì)創(chuàng)建一個(gè)MessageQueue用于存放消息。
  2. 創(chuàng)建了ActivityThread對(duì)象,并調(diào)用了它的attach方法,這個(gè)方法就是去創(chuàng)建Application、調(diào)用Application的onCreate方法以及告訴ActivityManagerService現(xiàn)在App啟動(dòng)了。
  3. 創(chuàng)建了用于通信的Handler,它是一個(gè)H對(duì)象。
  4. 調(diào)用Looper.loop方法,開始循環(huán)從主線程中的MessageQueue中取出消息來處理。

回顧下,Handler機(jī)制的原理圖:

Handler流程圖

可以知道,App啟動(dòng)后,因?yàn)長ooper.loop是一個(gè)死循環(huán),導(dǎo)致main方法一直沒有執(zhí)行完,也就是說,我們后續(xù)App中的所有操作,都是發(fā)生在Looper.loop中的

那Handler機(jī)制,是怎么保證App的運(yùn)行的呢?我們來看看ActivityThread中用于通信的Handler的定義:

    private class H extends Handler {
        public static final int LAUNCH_ACTIVITY         = 100;
        public static final int PAUSE_ACTIVITY          = 101;
        public static final int PAUSE_ACTIVITY_FINISHING= 102;
        public static final int STOP_ACTIVITY_SHOW      = 103;
        public static final int STOP_ACTIVITY_HIDE      = 104;
        // 省略部分代碼

        String codeToString(int code) {
            if (DEBUG_MESSAGES) {
                switch (code) {
                    case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
                    case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
                    case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
                    case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
                    case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE";
                    // 省略部分代碼
                }
            }
            return Integer.toString(code);
        }
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case RELAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;
                    handleRelaunchActivity(r);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case PAUSE_ACTIVITY:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
                            (msg.arg1&2) != 0);
                    maybeSnapshot();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case PAUSE_ACTIVITY_FINISHING:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    handlePauseActivity((IBinder)msg.obj, true, (msg.arg1&1) != 0, msg.arg2,
                            (msg.arg1&1) != 0);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case STOP_ACTIVITY_SHOW:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
                    handleStopActivity((IBinder)msg.obj, true, msg.arg2);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                case STOP_ACTIVITY_HIDE:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
                    handleStopActivity((IBinder)msg.obj, false, msg.arg2);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                 // 省略部分代碼
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }


通過代碼可以看到,H繼承于Handler,定義了很多消息的類型,比如啟動(dòng)Activity、停止Activity、顯示W(wǎng)indow、電量過低等,它重寫了handleMessage方法,用于處理這些消息。

那發(fā)消息是在哪里發(fā)的呢?我們以鎖屏調(diào)用Activity的onStop生命周期為例,其實(shí)就是在ApplicationThread中,它是一個(gè)ActivityThread的內(nèi)部類我們拿啟動(dòng)Activity這個(gè)消息舉例,ActivityManagerService會(huì)先通過binder調(diào)用ApplicationThread中的scheduleStopActivity方法(牽涉到進(jìn)程間通信,不懂可以略過),這個(gè)方法是在我們App的Bindler線程池中執(zhí)行的,那看看它是怎么切換到主線程去啟動(dòng)Activity的。

    public final void scheduleStopActivity(IBinder token, boolean showWindow,
                int configChanges) {
           sendMessage(
                showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
                token, 0, configChanges);
        }

我們看到,這里就是調(diào)用sendMessage方法,第一個(gè)參數(shù),不就是上面H中定義的消息類型么?接著看 sendMessage方法,它最后會(huì)調(diào)用到如下這個(gè)多參的構(gòu)造方法:

    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        if (DEBUG_MESSAGES) Slog.v(
            TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
            + ": " + arg1 + " / " + obj);
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }

可以看到,就是使用mH發(fā)送一個(gè)消息,而mH就是ActivityThread中定義的類型為H的成員變量,定義如下:

 final H mH = new H();
 

所以,調(diào)用鎖屏?xí)r,調(diào)用Activity的onStop方法,流程如下:

  1. ActivityManagerService通過binder方式,調(diào)用ApplicationThread的scheduleStopActivity方法
  2. ApplicationThread的scheduleStopActivity方法,通過H把消息加入到主線線程的MessageQueue中
  3. 主線程的Looper遍歷MessageQueue,取到該消息時(shí),調(diào)用H的handleMessage方法進(jìn)行處理
  4. 在H的handleMessage中,調(diào)用Activity的onStop方法

流程圖如下:

鎖屏

那么,我們App運(yùn)行原理就出來了,App的運(yùn)行依賴于Handler機(jī)制,當(dāng)主線程當(dāng)MessageQueue有消息時(shí),主線程的Looper.loop會(huì)不斷從主線程中的MessageQueue中取出消息來處理(比如Activity的onCreate其實(shí)就是屬于對(duì)MessageQueue中取出的一個(gè)消息的處理),這樣就保證了App運(yùn)行

有消息時(shí)

當(dāng)MessageQueue沒有消息時(shí),MessageQueue的next方法會(huì)阻賽,導(dǎo)致當(dāng)前線程掛起,等有消息(一般為系統(tǒng)進(jìn)程通過binder調(diào)用App的ApplicationThread中的方法,注意,方法在binder線程池中執(zhí)行,然后ApplicationThread使用ActivityThread中的H對(duì)象發(fā)送消息,加入消息到主線程的MessageQueue中,當(dāng)發(fā)現(xiàn)主線程被掛起了,則會(huì)喚醒主線程)

所以,當(dāng)沒有任何消息時(shí),我們的App的主線程,是屬于掛起的狀態(tài)。有消息來時(shí)(鎖屏、點(diǎn)擊等),主線程會(huì)被喚醒,所以說,Handler機(jī)制保證了App的運(yùn)行。

沒消息時(shí)
4.2 Handler更新UI

我們知道,如果在子線程直接更新UI會(huì)拋出異常,異常如下:

子線程更新UI拋異常

我們可以使用Handler在子線程中更新UI,常用的方式有如下幾種:

  1. Handler的sendMessage方式
  2. Handler的post方式
  3. Activity的runOnUiThread方法
  4. View的post方式

2.1 Handler的sendMessage方式

    final Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                btn.setText("handler.sendMessage方式");
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 使用sendMessage方式
                Message msg = Message.obtain();
                msg.what = 100;
                handler.sendMessage(msg);
            }
        }).start();

這種方式,就是在子線程中,用主線程中創(chuàng)建的hander調(diào)用sendMessage發(fā)送消息,把Message加入到主線程的MessageQueue中,等主線程的Looper從MessageQueue中取到這個(gè)消息時(shí),會(huì)調(diào)用這個(gè)Message的target的handleMessage方法,這個(gè)target其實(shí)就是我們發(fā)消息用到的handler,也就是調(diào)用了我們重寫的handleMessage方法。

發(fā)消息:Handler.sendMessage(Message)
處理消息:Message.target.handleMessage(其中target就是發(fā)消息的handler)

2.2 Handler的post方法

    final Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                btn.setText("handler.sendMessage方式");
            }
        };


        new Thread(new Runnable() {
            @Override
            public void run() {
                // 使用post
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        btn.setText("handler.post方式");
                    }
                });
            }
        }).start();

在子線程中使用handler的post方法,也是可以更新UI的,post方法需要傳入一個(gè)Runnalbe對(duì)象。我們來看看post方法源碼

    public final boolean post(Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

可以看到,先調(diào)用了getPostMessage構(gòu)建了一個(gè)Message對(duì)象,然后調(diào)用了sendMessageDelayed方法,前面知道,sendMessage也是調(diào)用的這個(gè)方法,所以我們只要關(guān)注怎么構(gòu)建的Message對(duì)象,看getPostMessage方法。

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        // post方法傳入的Runnable對(duì)象作為Message的callback
        m.callback = r;
        return m;
    }

其實(shí)很簡單,就是把post方法傳入的參數(shù)作為Message的callback來創(chuàng)建一個(gè)Message。

我們再來回顧一下從MessageQueue中取出消息來對(duì)消息對(duì)處理,方法是Handler對(duì)dispatchMessage方法

    public void dispatchMessage(Message msg) {
        // callback其實(shí)就是post方法傳入對(duì)Runnable對(duì)象
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            // 不會(huì)執(zhí)行到這里
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

    private static void handleCallback(Message message) {
        // 相當(dāng)于調(diào)用了post方法傳入對(duì)Runnable對(duì)象對(duì)run方法
        message.callback.run();
    }

可以知道,我們使用post發(fā)送消息,就是使用傳入對(duì)Runnable對(duì)象封裝成一個(gè)Message,然后加入到主線程中的MessageQueue,等主線程的Looper取出該消息處理時(shí),因?yàn)镸essage.callback不為空,而調(diào)用其run方法,也就是我們調(diào)用post方法傳入的Runnable對(duì)象的run方法,且不會(huì)調(diào)用Hander的handleMessage方法。

發(fā)送消息:Handler.post(Runnable)
處理消息:Message.callback.run(callback為調(diào)用post方法傳入的Runnable)

2.3 Activity的runOnUiThread方法

    new Thread(new Runnable() {
            @Override
            public void run() {
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        btn.setText("Activity的runOnUiThread方法");
                    }
                });
            }
        }).start();

我們?nèi)绻茉谧泳€程中獲取到Activity對(duì)象,是可以調(diào)用其runOnUiThread方法,來更新UI。我們來看看Activity的runOnUiThread源碼。

    public final void runOnUiThread(Runnable action) {
        // 如果不是UI線程,則調(diào)用Handler的post
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            // 如果是ui線程,則直接回調(diào)Runnable的run方法
            action.run();
        }
    }

如果是UI線程,就直接調(diào)用了傳入的Runnable對(duì)象的run方法,我們主要是看非UI線程的邏輯。

如果不是UI線程,則直接使用mHandler的post方法,看來Activity的runOnUiThread方法也是基于Handler的post方法來實(shí)現(xiàn)的,后面的邏輯就是把傳入的Runnable封裝成Message發(fā)送出去,上面講過了,就不再復(fù)述了。

我們再看看這個(gè)mHandler的定義,其實(shí)就是Activity的成員屬性

    final Handler mHandler = new Handler();

Activity是在主線程創(chuàng)建的,所以這個(gè)Handler也是在主線程中創(chuàng)建的,且持有的Looper為主線程的Looper。那么使用這個(gè)Handler調(diào)用post方法發(fā)出的消息,是加入到主線程的MessageQueue中,這樣就完成了子線程跟主線程的通信。

發(fā)送消息:Activity. runOnUiThread(Runnable)
處理消息:Message.callback.run(callback為runOnUiThread方法傳入的Runnable)

2.4 View的post方法

    new Thread(new Runnable() {
            @Override
            public void run() {
                // 調(diào)用View的post方法
                btn.post(new Runnable() {
                    @Override
                    public void run() {
                        btn.setText("View.post方式");
                    }
                });
            }
        }).start();

我們直接看View的post方法

    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

可以看到這里也是調(diào)用的Handler的post方法,跟Activity. runOnUiThread類似。

發(fā)送消息:View.post(Runnable)
處理消息:Message.callback.run(callback為post方法傳入的Runnable對(duì)象)

總結(jié)一下:

  1. Handler.sendMessage: 把消息加入到主線程的MessageQueue中,主線程中的Looper從MessageQueue中取出消息,調(diào)用Message.target.handleMessage方法
  2. Handler.post: 基于Handler.sendMessage,把消息加入到主線程的MessageQueue中,主線程中的Looper從MessageQueue中取出消息,調(diào)用Message.callback.run方法
  3. Activity.runOnUiThread: 基于Handler.post
  4. View.post: 基于Handler.post

所以,以上子線程更新主線程UI的所有方式,都是依賴于Handler機(jī)制。

  1. AsyncTask

當(dāng)我們想在子線程中做耗時(shí)任務(wù)時(shí),會(huì)考慮使用AsyncTask,我們來舉個(gè)栗子,在子線程中去創(chuàng)建自定義的MyAsyncTask并執(zhí)行它,在doInBackground中去模擬耗時(shí)操作:

    public class AsyncTaskActivity extends AppCompatActivity {
    private static final String TAG = "AsyncTaskActivity";
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_asynctask);
        btn = (Button) findViewById(R.id.btn);
        // 開啟一個(gè)子線程,去執(zhí)行異步任務(wù)
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "run, thread name:"  + Thread.currentThread().getName());
                MyAsyncTask asyncTask = new MyAsyncTask();
                asyncTask.execute();
            }
        }).start();
    }

    // 自定義AsyncTask,并重寫相關(guān)方法,并打印執(zhí)行所在線程
    class MyAsyncTask extends AsyncTask<Void, Integer, Void>{

        @Override
        protected Void doInBackground(Void... voids) {
            Log.e(TAG, "doInBackground, thread name:"  + Thread.currentThread().getName());
            // 模擬耗時(shí)任務(wù)
            for (int i = 0; i < 5; i ++) {
                SystemClock.sleep(1000);
                publishProgress(i);
            }
            return null;
        }

        @Override
        protected void onPreExecute() {
            Log.e(TAG, "onPreExecute, thread name:"  + Thread.currentThread().getName());
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            Log.e(TAG, "onPostExecute, thread name:"  + Thread.currentThread().getName());
            btn.setText("執(zhí)行完了!");
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            Log.e(TAG, "onProgressUpdate, thread name:"  + Thread.currentThread().getName());
        }
    }
}
AsyncTask_log

可以看到,我們創(chuàng)建的子線程名為Thread-4,而AsyncTask的方法所在線程如下:

  1. onPreExecute: Thread-4,其實(shí)就是調(diào)用AsyncTask的execute方法的線程
  2. doInBackground: AsyncTask #1,這其實(shí)是AsyncTask的線程池中的一個(gè)線程
  3. onProgressUpdate: main,即主線程
  4. onPostExecute: main,即主線程

關(guān)于onPreExecute,這很好實(shí)現(xiàn),不需要切換線程,直接回調(diào)就可以;而doInBackground方法的執(zhí)行,可以直接取AsyncTask維持的線程池來執(zhí)行就可以。我們重點(diǎn)關(guān)注onProgressUpdate和onPostExecute方法,是怎么從子線程切換到主線程的。

我們從AsyncTask的源碼中可以看到這樣一個(gè)內(nèi)部類

        private static class InternalHandler extends Handler {
        public InternalHandler() {
            // 使用主線程的Looper去創(chuàng)建Handler
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // finish中主要調(diào)用onPostExecute
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    // 調(diào)用onProgressUpdate
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

從handleMessage方法看到,這里切換到主線程,也是使用的Handler機(jī)制來實(shí)現(xiàn)的,但是為什么我們不管在任何線程創(chuàng)建的AsyncTask去執(zhí)行,最后都可以在主線程去回調(diào)這兩個(gè)方法呢?主要是創(chuàng)建InternalHandler時(shí),使用的是主線程的Looper,這樣使用這個(gè)InternalHandler發(fā)送消息時(shí),消息就會(huì)加入到主線程Looper對(duì)應(yīng)的MessageQueue中,所以主線程Looper取出消息處理時(shí),InternalHandler的handleMessage方法就是在主線程中回調(diào)的了。

所以AsyncTask其實(shí)就是基于線程池+Handler機(jī)制來實(shí)現(xiàn)的。

  1. 其他使用Handler的地方

  2. RxJava: 子線程切換到主線程執(zhí)行觀察者的回調(diào)方法(RxJava我不熟悉)

  3. Glide:圖片準(zhǔn)備好以后的回顯

  4. LocalBroadcastManager: 傳統(tǒng)的廣播是基于binder實(shí)現(xiàn)的,本地廣播LocalBroadcastManager是基于Handler實(shí)現(xiàn)的

其實(shí)還有很多使用到handler機(jī)制的地方,就不一一舉例了,反正記住,Handler機(jī)制很重要。

4.3 處理Handler消息,是在哪個(gè)線程?一定是創(chuàng)建Handler的線程么?

以前總覺得,處理消息的線程,就是創(chuàng)建Handler的線程,但是上一篇文章的分析,我們知道這樣說其實(shí)是不準(zhǔn)確的(因?yàn)槲覀儎?chuàng)建Handler通常使用的默認(rèn)Looper)。

處理消息的線程,其實(shí)是發(fā)送handler所持有的Looper所在的線程。

其實(shí)原理很好理解,我們知道Handler的原理如圖

Handler流程圖

所以,消息的處理分發(fā)所在線程完全取決于消息所在MessageQueue的線程,如果想要在某個(gè)線程中處理消息,只要做到把消息加入到那個(gè)線程所對(duì)應(yīng)的MessageQueue中。

就像上面講到AsyncTask的例子,就算我們在子線程創(chuàng)建了AsyncTask(即在子線程創(chuàng)建了用于通信的Handler),但只要我們創(chuàng)建Handler的時(shí)候,通過Looper.getMainLooper()傳入主線程的Looper ,那么消息就加入到了主線程所對(duì)應(yīng)的MessageQueue中,消息就是在主線程中處理的。

如下,我們在子線程中創(chuàng)建一個(gè)handler,然后在主線程發(fā)送消息,因?yàn)閯?chuàng)建handler使用的是子線程中的Looper,所以消息是在主線程中處理的。代碼如下:

    public class ThreadActivity extends AppCompatActivity {
    private static final String TAG = "ThreadActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_refresh);
        btn = (Button) findViewById(R.id.btn);
        // 開啟一個(gè)子線程,去創(chuàng)建Handler
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
                Looper.prepare();
                mHandler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.e(TAG, "handleMessage, thread name: " + Thread.currentThread().getName());
                    }
                };
                Looper.loop();
            }
        }).start();

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 發(fā)送一個(gè)消息
                mHandler.sendEmptyMessage(100);
            }
        });
    }

}

log輸出,可以看出,處理消息是在子線程中:

log

按照之前的說法,如果我們想在主線程中處理消息,只要把消息加入到主線程的MessageQueue中,所以我們可以創(chuàng)建Looper時(shí),傳入主線程的Looper,代碼如下:

    public class ThreadActivity extends AppCompatActivity {
    private static final String TAG = "ThreadActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_refresh);
        btn = (Button) findViewById(R.id.btn);
        // 開啟一個(gè)子線程,去創(chuàng)建Handler
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
                Looper.prepare();
                // 創(chuàng)建Handler傳入主線程的Looper
                mHandler = new Handler(Looper.getMainLooper()){
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.e(TAG, "handleMessage, thread name: " + Thread.currentThread().getName());
                    }
                };
                Looper.loop();
            }
        }).start();

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 發(fā)送一個(gè)消息
                mHandler.sendEmptyMessage(100);
            }
        });
    }
}

可以看到,創(chuàng)建Handler使用了主線程的Looper后,的確消息是在主線程中處理的了:


log
4.4 是如何插入到MessageQueue中?

我們之前說,所有handler.post和handler.sendMessage都會(huì)調(diào)用到Handler的sendMessageDelayed方法,方法如下:

    public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

這里邏輯就很簡單了,直接調(diào)用了sendMessageAtTime方法,第一個(gè)參數(shù)為Message,第二個(gè)參數(shù)為SystemClock.uptimeMillis() + delayMillis,其中delayMillis為延時(shí)的時(shí)間,單位為毫秒,SystemClock.uptimeMillis() 為開機(jī)到現(xiàn)在的時(shí)間(不包括休眠時(shí)間),單位為毫秒。第二個(gè)參數(shù)主要是決定該Message在MessageQueue的順序,比如現(xiàn)在開機(jī)時(shí)間為100s,發(fā)送一個(gè)延時(shí)20s的消息,則兩者之和為120s; 過了5秒,又發(fā)了一個(gè)延時(shí)5s的消息,則兩者只喝為105+5 = 110s。

sendMessageAtTime最后會(huì)調(diào)用到MessageQueue的enqueueMessage方法,我們來看看這個(gè)方法:

    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            //  when = 開機(jī)到目前的時(shí)間 + 延時(shí)時(shí)間
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            // 如果當(dāng)前消息隊(duì)列為空或者當(dāng)前when小于隊(duì)頭when
            // 則把消息插入到隊(duì)頭
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                // 死循環(huán),根據(jù)when尋找Message插入到MessageQueue合適的位置
                for (;;) {
                    prev = p;
                    p = p.next;
                    // MessageQueue中依次往后找到第一個(gè)Message.when大于當(dāng)前Message.when的Message
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                // 把當(dāng)前Message插入到MessageQueue的合適位置
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                // 如果需要喚醒,則調(diào)用nativeWake去喚醒處理線程
                nativeWake(mPtr);
            }
        }
        return true;
    }

從上面的代碼,可以很容易看出,Message在MessageQueue中是根據(jù)when從小到大來排隊(duì)的,when是開機(jī)到現(xiàn)在的時(shí)間+延時(shí)時(shí)間。

比如,我們假設(shè)開機(jī)時(shí)間為100s,此時(shí)MessageQueue沒有消息,這時(shí)候發(fā)一個(gè)延時(shí)20s的消息,即when為120000,則MessageQueue中的消息情況如圖:

MessageQueue開始

過了5s,我們又發(fā)了一個(gè)延時(shí)10s的消息,則when為115000,此時(shí)MessageQueue如圖:

MessageQueue開始過了5s

又過了5s,我們發(fā)了一個(gè)不延時(shí)的消息,即when為110000,此時(shí)MessageQueue如圖:

MessageQueue又過了5s

所以,Message在MessageQueue中是根據(jù)when從小到大來排隊(duì)的,when是開機(jī)到現(xiàn)在的時(shí)間+延時(shí)時(shí)間。

4.5 MessageQueue的next會(huì)造成App的ANR么

我們知道Activity如果5s的事件都不能相應(yīng)用戶的請求,則會(huì)ANR。我們在來回顧下Looper.loop方法:

public static void loop() {
        final Looper me = myLooper();
        // 取到當(dāng)前線程的MessageQueue
        final MessageQueue queue = me.mQueue;
        
        // 死循環(huán)
        for (;;) {
            // 調(diào)用MessageQueue.next,從隊(duì)列中取出消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            
            // 對(duì)消息對(duì)分發(fā)
            msg.target.dispatchMessage(msg);

          // 省略無關(guān)代碼
        }
    }

在前面講過Looper.loop方法維持了App的運(yùn)行,它是里面使用了一個(gè)死循環(huán),我們App平常的操作(Activity的生命周期、點(diǎn)擊事件等)都是屬于調(diào)用了 msg.target.dispatchMessage(msg)對(duì)消息的處理。但是如果MessageQueue中沒有消息時(shí),MessageQueue的next方法會(huì)阻塞,那它會(huì)導(dǎo)致App對(duì)ANR么?

我們來看看MessageQueue的next方法

Message next() {

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        // 死循環(huán)
        for (;;) {
            // 阻塞MessageQueue
            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) {
                        // 如果是延時(shí)消息,則算出需要阻塞的時(shí)間nextPollTimeoutMillis
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 如果不是延時(shí)顯示,則直接把消息返回,以供處理
                        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 {
                    // 沒有消息時(shí),設(shè)置nextPollTimeoutMillis為-1,阻塞MessageQueue
                    nextPollTimeoutMillis = -1;
                }
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 一般都會(huì)滿足if條件,然后mBlocked設(shè)置為true,繼續(xù)continue
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
                //  省略無關(guān)代碼
            }

            pendingIdleHandlerCount = 0;

            nextPollTimeoutMillis = 0;
        }
    }

next方法中,首先設(shè)置一個(gè)死循環(huán),然后調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis)方法,它是一個(gè)native方法,用于阻塞MessageQueue,主要是關(guān)注它的第二個(gè)參數(shù)nextPollTimeoutMillis,有如下三種可能:

  1. 如果nextPollTimeoutMillis=-1,一直阻塞不會(huì)超時(shí)。
  2. 如果nextPollTimeoutMillis=0,不會(huì)阻塞,立即返回。
  3. 如果nextPollTimeoutMillis>0,最長阻塞nextPollTimeoutMillis毫秒(超時(shí)),如果其間有程序喚醒會(huì)立即返回。

我們先繼續(xù)往下看,開始nextPollTimeoutMillis為0,也就是不會(huì)阻塞,則繼續(xù)往下,這時(shí)候有三種情況

  1. 延時(shí)消息,則直接算出目前需要延時(shí)的時(shí)間nextPollTimeoutMillis,注意,這時(shí)候并沒有把消息返回,而是繼續(xù)往下,設(shè)置mBlocked為true,表示消息隊(duì)列已阻塞,并continue執(zhí)行for循環(huán)體,再次執(zhí)行nativePollOnce方法,這時(shí)候nextPollTimeoutMillis>0,則會(huì)導(dǎo)致MessageQueue休眠nextPollTimeoutMillis毫秒,接著應(yīng)該會(huì)走到情況2.
  2. 不是延時(shí)消息,則設(shè)置mBlocked為false,表示消息隊(duì)列沒有阻塞,直接把消息返回,且把消息出隊(duì)。
  3. 如果消息為空,則調(diào)用位置nextPollTimeoutMillis為-1,繼續(xù)往下,設(shè)置mBlocked為true,表示消息隊(duì)列已阻塞,并continue繼續(xù)for循環(huán),這時(shí)候調(diào)用nativePollOnce會(huì)一直阻塞,且不會(huì)超時(shí)。

所以,當(dāng)消息隊(duì)列為空時(shí),其實(shí)是調(diào)用本地方法nativePollOnce,且第二個(gè)參數(shù)為-1,它會(huì)導(dǎo)致當(dāng)前線程阻塞,且不會(huì)超時(shí),所以不會(huì)出現(xiàn)ANR的情況,其實(shí)這是由Linux的管道機(jī)制(pipe)來保證的,當(dāng)線程阻塞時(shí),對(duì)CPU等資源等消耗時(shí)極低的,具體的原理可以自行查閱。

那線程阻塞以后,什么時(shí)候才能再喚醒呢?記得之前我們說消息加入MessageQueue的邏輯么?我們再來回顧一下enqueueMessage的流程:

boolean enqueueMessage(Message msg, long when) {

        synchronized (this) {

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                // 如果MessageQueue為空
                needWake = mBlocked;
            } else {
                // 如果MessageQueue中有消息
                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; // invariant: p == prev.next
                prev.next = msg;
            }

            // 如果需要喚醒當(dāng)前線程,則調(diào)用nativeWake方法
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

其實(shí)就是使用Handler發(fā)送消息,把消息加入到MessageQueue時(shí),會(huì)判斷當(dāng)前MessageQueue是否阻塞,如果阻塞了,則需要調(diào)用nativeWake方法去喚醒線程,而這個(gè)阻塞是在前面提到的MessageQueue的next方法中,MessageQueue沒有消息或者消息為延時(shí)消息時(shí)設(shè)置的。
所以MessageQueue的next方法可能會(huì)阻塞線程,但不會(huì)造成ANR。

  1. 當(dāng)MessageQueue沒有消息時(shí),next方法中調(diào)用nativePollOnce導(dǎo)致線程阻塞,直到有新消息加入MesssageQueue時(shí)調(diào)用nativeWake來喚醒線程
  2. 當(dāng)MessageQueue有消息時(shí)且隊(duì)頭消息為延時(shí)消息時(shí),next方法調(diào)用nativePollOnce導(dǎo)致線程阻塞nextPollTimeoutMillis的時(shí)間,中途有新消息加入MessageQueue時(shí)調(diào)用nativeWake可以喚醒線程,也可以等nextPollTimeoutMillis后自動(dòng)喚醒線程
4.6 子線程使用Toast

有時(shí)候,我們需要在子線程中直接彈出Toast來提示一些信息,代碼如下:

public class ToastActivity extends AppCompatActivity {
    private static final String TAG = "ToastActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_toast);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 子線程中彈出toast
                Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
            }
        }).start();
    }
}

運(yùn)行程序,會(huì)發(fā)現(xiàn)程序會(huì)崩潰。是因?yàn)樽泳€程不能更新UI么?其實(shí)不是不這樣的。

  1. Toast是屬于系統(tǒng)Window,不受子線程更新UI限制。
  2. onCreate方法中,子線程可能可以更新UI,因?yàn)樽泳€程不能更新UI的檢測是在ViewRootImpl的checkThread完成的,而onCreate方法中,ViewRootImpl還沒有創(chuàng)建,所以不會(huì)去檢測。

既然不是這兩方面的原因,我們來看看報(bào)錯(cuò)的log吧


log

這跟我們在子線程中直接使用handler好像報(bào)的錯(cuò)誤類似,那我們也跟使用hendler的套路一樣,先調(diào)用Looper.prepare然后再調(diào)用Looper.loop呢?代碼如下:

new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                // 子線程中彈出toast
                Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }).start();

發(fā)現(xiàn)就可以了,可以看出Toast也是需要使用Handler,我們來看看Toast的實(shí)現(xiàn),直接看Toast中的一個(gè)內(nèi)部類TN,它是一個(gè)IBinder實(shí)現(xiàn)類,我們來看它的定義:

private static class TN extends ITransientNotification.Stub {
       final Runnable mShow = new Runnable() {
            @Override
            public void run() {
                // 調(diào)用handleShow,處理顯示邏輯
                handleShow();
            }
        }
        final Handler mHandler = new Handler();    

        // 省略無關(guān)代碼

        WindowManager mWM;

        TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");
            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        }

        /**
         * schedule handleShow into the right thread
         */
        @Override
        public void show() {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            // 這里是Binder線程池,用handler切換到有Looper的線程
            mHandler.post(mShow);
        }
        
        // Toast的真實(shí)顯示
        public void handleShow() {
            if (mView != mNextView) {
                mView = mNextView;

                // 省略無關(guān)代碼

                // 把View加入到Window上
                mWM.addView(mView, mParams);
            }
        }
        
        //省略無關(guān)代碼
}

可以看出,Toast的顯示,使用了Binder通信,其實(shí)就是WindowManagerService會(huì)拿到TN對(duì)象,調(diào)用其show方法,但是這是Binder線程池中執(zhí)行的,所以使用handler切換到調(diào)用Toast的show方法所在的線程去執(zhí)行,這里使用的就是handler.post,所以就需要調(diào)用Toast.show方法所在線程有Looper。最后調(diào)用的就是handleShow方法,把View加載到Window上。

總結(jié)一下Handler:

  1. Toast是系統(tǒng)Window來實(shí)現(xiàn)的
  2. Toast的顯示使用了IPC
  3. Toast的顯示使用了Handler機(jī)制
  4. 子線程可以使用Toast,不過需要使用Handler的套路
4.7 Looper.loop可以停止么?

前面我們知道,loop方法中是一個(gè)死循環(huán),又因?yàn)榇a是順序執(zhí)行的,所以它之后的代碼是得不到執(zhí)行的,如下:

public class LooperActivity extends AppCompatActivity {
    private static final String TAG = "LooperActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_looper);
        btn = (Button) findViewById(R.id.btn);
        // 開啟一個(gè)子線程,去執(zhí)行異步任務(wù)
        new Thread(new Runnable() {
            @Override
            public void run() {
               
                Looper.prepare();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);

                    }
                };
                 Log.e(TAG, "Looper.loop之前" );
                // Looper.loop方法是一個(gè)死循環(huán)
                Looper.loop();
                // 得不到執(zhí)行
                Log.e(TAG, "Looper.loop之后" );
            }
        }).start();
    }
}

log如圖,只會(huì)打印loop方法之前的,loop之后的代碼得不到執(zhí)行:


log

這樣我們就要考慮一個(gè)問題了,并不是所有線程都需要像主線程一樣一直運(yùn)行下去,有些線程希望做完耗時(shí)任務(wù)后能回收,但是因?yàn)長ooper.loop方法,導(dǎo)致線程只是阻塞,隨時(shí)有被喚醒的可能,不能釋放。那有什么辦法能停止loop方法么?

其實(shí)Looper提供了quit和quitSafely方法來停止Looper,我們先來看看quit的用法,在點(diǎn)擊事件中調(diào)用了Looper的quit方法,修改后的代碼如下

public class LooperActivity extends AppCompatActivity {
    private static final String TAG = "LooperActivity";
    private Button btn;
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_looper);
        btn = (Button) findViewById(R.id.btn);
        // 開啟一個(gè)子線程,去執(zhí)行異步任務(wù)
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);

                    }
                };
                Log.e(TAG, "Looper.loop之前" );
                // Looper.loop方法是一個(gè)死循環(huán)
                Looper.loop();
                // 得不到執(zhí)行
                Log.e(TAG, "Looper.loop之后" );
            }
        }).start();
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 調(diào)用Looper的quit方法,停止Looper
                mHandler.getLooper().quit();
            }
        });
    }
}

開始和之前一樣,Looper.loop后的方法不會(huì)得到執(zhí)行,我們點(diǎn)擊按鈕后,Looper會(huì)停止,Looper.loop之后的代碼也可以得到執(zhí)行,log如下:


log

我們來看看Looper的quit和quitSafely的源碼:

    public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }

我們發(fā)現(xiàn),兩個(gè)方法都是調(diào)用了MessageQueue的quit方法,只是傳入的參數(shù)不同,我們來看看MessageQueue的quit方法:

    void quit(boolean safe) {
        synchronized (this) {
            if (mQuitting) {
                return;
            }

            // MessageQueue正在停止,用于next方法退出死循環(huán)
            mQuitting = true;

            if (safe) {
                // 刪除MessageQueue中的延時(shí)消息
                removeAllFutureMessagesLocked();
            } else {
                // 刪除MessageQueue中的所有消息
                removeAllMessagesLocked();
            }

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

首先把mQuitting設(shè)置為true,主要用于MessageQueue的next方法退出死循環(huán),然后通過safe去判斷邏輯邏輯,這里就可以看出Looper的quit和quitSafely的區(qū)別了

  1. quit: 刪除MesageQueue中所有消息
  2. quitSafely: 刪除MessageQueue中的延時(shí)消息

我們繼續(xù)看mQuitting對(duì)MessageQueue的next方法的影響,回到next方法,我們只看關(guān)鍵性代碼:

    Message next() {
      
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

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

                // 判斷mQuitting
                if (mQuitting) {
                    dispose();
                    return null;
                }

            }

        }
    }

直接看最后的部分,對(duì)mQuitting做判斷,我們之前在MessageQueue的quit方法中,會(huì)把這個(gè)屬性設(shè)置為true,其實(shí)就是會(huì)影響到這里。滿足條件以后,調(diào)用了dispose方法,并返回了null。

我們先來看dispose方法

private void dispose() {
        if (mPtr != 0) {
            nativeDestroy(mPtr);
            mPtr = 0;
        }
    }

其實(shí)就是調(diào)用了nativeDestroy方法,它是一個(gè)native方法,用于在底層停止MessageQueue。

這里只是停止了MessageQueue的next中的死循環(huán),Looper.loop方法中的死循環(huán)還是沒有退出,我們繼續(xù)看Looper.loop方法。

    public static void loop() {
        final Looper me = myLooper();
       
        final MessageQueue queue = me.mQueue;

        for (;;) {
            // 當(dāng)mQuitting為true,queue.next方法返回了null
            Message msg = queue.next(); // might block
            if (msg == null) {
                // 直接return,退出loop的死循環(huán)
                return;
            }
            // 省略無關(guān)代碼
        }
    }

前面我們知道,當(dāng)調(diào)用了Looper的quit或者quitSafely時(shí),會(huì)設(shè)置當(dāng)前線程的MessageQueue的 mQuitting為true,然后導(dǎo)致了MessageQueue的next返回了null,然后直接return了,退出了loop中的死循環(huán),這樣就完成了停止Looper的邏輯。

4.8 Handler的內(nèi)存泄漏

我們通常會(huì)使用如下的方式去使用handler來通信

public class HandlerActivity extends AppCompatActivity {
    private static final String TAG = "HandlerActivity";
    private Handler mHandler;
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);

        // 匿名內(nèi)部類
        mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                // 處理消息
            }
        };

        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 發(fā)送延時(shí)100s的消息
                mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 使用leakcanary做內(nèi)存泄漏檢測
        RefWatcher refWatcher = MyApplication.getRefWatcher(this);
        if (refWatcher != null) {
            refWatcher.watch(this);
        }
    }
}

但是會(huì)有一個(gè)問題,我們進(jìn)入這個(gè)頁面然后點(diǎn)擊按鈕,發(fā)送一個(gè)延時(shí)100s的消息,再退出這個(gè)Activity,這時(shí)候可能導(dǎo)致內(nèi)存泄漏。

根本原因是因?yàn)槲覀儎?chuàng)建的匿名內(nèi)部類Handler對(duì)象持有了外部類Activity的對(duì)象,我們知道,當(dāng)使用handler發(fā)送消息時(shí),會(huì)把handler作為Message的target保存到MessageQueue,由于延時(shí)了100s,所以這個(gè)Message暫時(shí)沒有得到處理,這時(shí)候它們的引用關(guān)系為MessageQueue持有了Message,Message持有了Handler,Handler持有了Activity,如下圖所示


Handler內(nèi)存泄漏

當(dāng)退出這個(gè)Activity時(shí),因?yàn)镠andler還持有Activity,所以gc時(shí)不能回收該Activity,導(dǎo)致了內(nèi)存泄漏,使用LeakCanary檢測,效果如下圖所示:

LeakCanary-泄漏圖

當(dāng)然,過了100s,延時(shí)消息得到了處理,Activity對(duì)象屬于不可達(dá)的狀態(tài)時(shí),會(huì)被回收。

那怎么來解決Handler泄漏呢?主要有如下兩種方式:

  1. 靜態(tài)內(nèi)部類+弱引用
  2. 移除MessageQueue中的消息
4.8.1 靜態(tài)內(nèi)部類+弱引用

我們知道,靜態(tài)內(nèi)部類是不會(huì)引用外部類的對(duì)象的,但是既然靜態(tài)內(nèi)部類對(duì)象沒有持有外部類的對(duì)象,那么我們怎么去調(diào)用外部類Activity的方法呢?答案是使用弱引用。代碼如下:

public class HandlerActivity extends AppCompatActivity {
    private static final String TAG = "HandlerActivity";
    private Handler mHandler;
    private Button btn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        
        // 創(chuàng)建Handler對(duì)象,把Activity對(duì)象傳入
        mHandler = new MyHandler(HandlerActivity.this);

        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 發(fā)送延時(shí)100s的消息
                mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
            }
        });
    }

    // 靜態(tài)內(nèi)部類
    static class MyHandler extends Handler {
        private WeakReference<Activity> activityWeakReference;
        public  MyHandler(Activity activity) {
            activityWeakReference = new WeakReference<Activity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 處理消息
            if (activityWeakReference != null) {
                Activity activity = activityWeakReference.get();
                // 拿到activity對(duì)象以后,調(diào)用activity的方法
                if (activity != null) {

                }
            }
        }
    }
}

首先,我們自定義了一個(gè)靜態(tài)內(nèi)部類MyHandler,然后創(chuàng)建MyHandler對(duì)象時(shí)傳入當(dāng)前Activity的對(duì)象,供Hander以弱應(yīng)用的方式持有,這個(gè)時(shí)候Activity就被強(qiáng)引用和弱引用兩種方式引用了,我們繼續(xù)發(fā)起一個(gè)延時(shí)100s的消息,然后退出當(dāng)前Activity,這個(gè)時(shí)候Activity的強(qiáng)引用就不存在了,只存在弱引用,gc運(yùn)行時(shí)會(huì)回收掉只有弱引用的Activity,這樣就不會(huì)造成內(nèi)存泄漏了。

但這個(gè)延時(shí)消息還是存在于MessageQueue中,得到這個(gè)Message被取出時(shí),還是會(huì)進(jìn)行分發(fā)處理,只是這時(shí)候Activity被回收掉了,activity為null,不能再繼續(xù)調(diào)用Activity的方法了。所以,其實(shí)這是Activity可以被回收了,而Handler、Message都不能被回收。

至于為什么使用弱引用而沒有使用軟引用,其實(shí)很簡單,對(duì)比下兩者回收前提條件就清楚了

  1. 弱引用(WeakReference): gc運(yùn)行時(shí),無論內(nèi)存是否充足,只有弱引用的對(duì)象就會(huì)被回收
  2. 軟引用(SoftReference): gc運(yùn)行時(shí),只有內(nèi)存不足時(shí),只有軟引用的對(duì)象就會(huì)被回收

很明顯,當(dāng)我們Activity退出時(shí),我們希望不管內(nèi)存是否足夠,都應(yīng)該回收Activity對(duì)象,所以使用弱引用合適。

4.8.2 移除MessageQueue中的消息

我們知道,內(nèi)存泄漏的源頭是MessageQueue持有的Message持有了Handler持有了Activity,那我們在合適的地方把Message從MessageQueue中移除,不就可以解決內(nèi)存泄漏了么?

Handler為我們提供了removeCallbacksAndMessages等方法用于移除消息,比如,在Activity的onDestroy中調(diào)用Handler的removeCallbacksAndMessages,代碼如下:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 移除MessageQueue中target為該mHandler的Message
        mHandler.removeCallbacksAndMessages(null);
    }

其實(shí)就是在Activity的onDestroy方法中調(diào)用mHandler.removeCallbacksAndMessages(null),這樣就移除了MessageQueue中target為該mHandler的Message,因?yàn)镸essageQueue沒有引用該Handler發(fā)送的Message了,所以當(dāng)Activity退出時(shí),Message、Handler、Activity都是可回收的了,這樣就能解決內(nèi)存泄漏的問題了。

5. 結(jié)尾

Handler的知識(shí)不多,但細(xì)節(jié)特別多,一旦久一點(diǎn)沒看就會(huì)忘記。
所以,不管是別人寫的還是自己寫的,先把相關(guān)知識(shí)記下來,下次忘記了回來再看一下就行了。

6. 參考文章

  1. http://www.lxweimin.com/p/592fb6bb69fa
  2. http://www.lxweimin.com/p/9631eebadd5c
  3. http://www.lxweimin.com/p/67eb02c8bdce
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容