如果一個Handler同時收到大量Message會發(fā)生什么?

在之前的面試當中,我被問到了這么一個問題“如果一個Handler收到大量的Message的時候會發(fā)生什么?”最近閑來無事,做了一個Demo實驗了一下,以下是相關的心路歷程。

先說結論

如果一個使用主線程Looper的Handler在一段時間內收到大量的message的時候,消息過多,可能會使得消息處理不及時。帶來的副作用是可能會使得界面的刷新和touch事件的響應延遲

相應的Demo代碼

public class MainActivity extends BaseActivity {

    private Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(@NonNull Message msg) {
            if (msg.what == 1) {
                String time = (String) msg.obj;
                Log.e("Test", time);
            }
        }
    };

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

        final View circle = findViewById(R.id.circle);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("Testttttttt", "Click!!!!");
                ((MiFloatWindowCircle) circle).start();
            }
        });

    }

    @Override
    protected void onResume() {
        super.onResume();
        new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (true) {
                    String msg = "0 + " + System.currentTimeMillis();
                    if (i % 50 == 0) {

                    } else if (i < 2000) {
                        handler.sendMessage(handler.obtainMessage(1, msg));
                    }else {
                        break;
                    }
                    i++;
                }
            }
        }).start();
    }
    
}

心路歷程

在尋找這個原因的時候,我首先需要解決的一個問題就是“為什么這么做不會觸發(fā)ANR?”
首先就需要知道ANR發(fā)生的原理

ANR是什么?為什么不會觸發(fā)ANR

這是需要解決的第一個問題。經過查閱大量的資料后大致可以理解為:
在某個方法開始執(zhí)行之前發(fā)送一個延時任務,如果在延時任務觸發(fā)之前執(zhí)行完成,則取消相應的延時任務。那么這個時候就不會觸發(fā)ANR;如果觸發(fā)了這個延時任務,那么就會產生ANR。
經過查閱相關資料和代碼后發(fā)現(xiàn)。
在AMS的UiHandler當中有這么一段代碼

    final class UiHandler extends Handler {
        public UiHandler() {
            super(com.android.server.UiThread.get().getLooper(), null, true);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case SHOW_NOT_RESPONDING_UI_MSG: {
                mAppErrors.handleShowAnrUi(msg);
                ensureBootCompleted();
            } break;

可以看到,當UiHandler收到SHOW_NOT_RESPONDING_UI_MSG就會觸發(fā)展示“程序無響應”的Dialog
到這個時候,似乎可以解釋為什么在收到大量Msg的時候不會觸發(fā)ANR的原因了——(因為Handler的Msg的處理是一個非常典型的生產者——消費者場景)生產者生成了太多的Msg,導致緩存中觸發(fā)ANR的Msg遲遲得不到處理,從而不會觸發(fā)ANR。但是當使用下面一種測試代碼進行測試的時候,仍然觸發(fā)了ANR

    @Override
    protected void onResume() {
        super.onResume();
        while (true) {
            String msg = "0 + " + System.currentTimeMillis();
            handler.sendMessage(handler.obtainMessage(1, msg));
        }
    }

所以這種解釋似乎就不能成立。
同時經過查看UiHandler所屬的Looper也發(fā)現(xiàn),UiHandler其實并不屬于這個Application中的主線程的Looper,而是使用一個com.android.server.UiThread.get().getLooper()的Looper。
如果ANR延時任務所屬的Looper和我們發(fā)送使用的Looper不是同一個Looper的話,那么這種推測就是不成立的。
同時在查閱資料和實驗后,對于Activity的ANR場景有了進一步的理解——只要用戶不進行輸入操作,其實是不會觸發(fā)ANR的。Activity的ANR是在inputDisptcher在通知inputChannel inputEvent的同時發(fā)送的。所以即使是像上面那樣,在onResume中寫一個死循環(huán),只要在運行的時候不進行輸入的操作。依然不會觸發(fā)ANR。

換一種思路

既然現(xiàn)象是界面無響應,那么就需要看一下View在postInvalidate()的時候做了什么

    /**
     * <p>Cause an invalidate to happen on a subsequent cycle through the event
     * loop. Waits for the specified amount of time.</p>
     *
     * <p>This method can be invoked from outside of the UI thread
     * only when this View is attached to a window.</p>
     *
     * @param delayMilliseconds the duration in milliseconds to delay the
     *         invalidation by
     *
     * @see #invalidate()
     * @see #postInvalidate()
     */
    public void postInvalidateDelayed(long delayMilliseconds) {
        // We try only with the AttachInfo because there's no point in invalidating
        // if we are not attached to our window
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
        }
    }
    public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
    }

經過一路追蹤以后追蹤到,View在調用postInvalidate()以后會發(fā)送一條msg到ViewRootHandler中,
而這個ViewRootHandler在初始化的時候沒有傳遞它需要使用哪個Looper來進行消息的處理。
根據Android的基礎知識可以得知,View只在主線程進行操作。所以ViewRootHandler的構造方法中的Looper.myLooper()最后得到的是MainThread中的Looper。
到這里似乎可以解釋為什么在主線程短時間內大量發(fā)送msg的時候可能會導致界面卡頓——是因為相關界面刷新的msg排隊相對靠后,無法第一時間進行處理。

但是在使用Demo進行測試的時候除了界面無法刷新這個問題,還有另一個問題——onClick事件也無法進行響應。

有了上面這個思路,這個問題的原因找起來就相對順利多了。
首先,在View體系下,第一個接收到TouchEvent的一定是RootView的dispatchTouchEvent()。所以我們需要知道,是誰調用的dispatchTouchEvent()即可。
經過查找,發(fā)現(xiàn)在ViewRootImpl有上文提到的inputChannel,然后在相關代碼中發(fā)現(xiàn)了這么一段代碼

                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());

Looper.myLooper()?ViewRootImpl應該也是同屬于View體系下的,所以在這里調用這個方法應該返回的是MainThread的Looper,繼續(xù)跟蹤下去發(fā)現(xiàn)

    /**
     * Creates an input event receiver bound to the specified input channel.
     *
     * @param inputChannel The input channel.
     * @param looper The looper to use when invoking callbacks.
     */
    public InputEventReceiver(InputChannel inputChannel, Looper looper) {
        if (inputChannel == null) {
            throw new IllegalArgumentException("inputChannel must not be null");
        }
        if (looper == null) {
            throw new IllegalArgumentException("looper must not be null");
        }

        mInputChannel = inputChannel;
        mMessageQueue = looper.getQueue();
        mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
                inputChannel, mMessageQueue);

        mCloseGuard.open("dispose");
    }

到這里就進入了native方法了,暫時就不能繼續(xù)跟蹤下去了。
但是它既然把主線程的Looper傳進去了,那么就說明點擊事件也與主線程的Looper有關。所以就可以解釋為什么可能會導致主線程的點擊事件可能會出現(xiàn)延遲。同繪制事件的理由一樣。

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

相關閱讀更多精彩內容

  • 資料來源:http://www.itdecent.cn/p/916b2f8e1456 Activity Acti...
    百度不清閱讀 504評論 0 0
  • 久違的晴天,家長會。 家長大會開好到教室時,離放學已經沒多少時間了。班主任說已經安排了三個家長分享經驗。 放學鈴聲...
    飄雪兒5閱讀 7,819評論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉變要...
    余生動聽閱讀 10,843評論 0 11
  • 可愛進取,孤獨成精。努力飛翔,天堂翱翔。戰(zhàn)爭美好,孤獨進取。膽大飛翔,成就輝煌。努力進取,遙望,和諧家園??蓯塾巫?..
    趙原野閱讀 3,502評論 1 1
  • 在妖界我有個名頭叫胡百曉,無論是何事,只要找到胡百曉即可有解決的辦法。因為是只狐貍大家以訛傳訛叫我“傾城百曉”,...
    貓九0110閱讀 3,715評論 7 3

友情鏈接更多精彩內容