Android Handler那些事兒,消息屏障?IdelHandler? ANR?

Handler 是Android SDK中用來處理異步消息的核心類,子線程能夠經過handler來通知主線程進行ui更新。java

備註:本文源碼截圖 基於Android sdk 28api

Handler機制 消息發送主要流程如圖性能優化

消息發送流程圖.jpg

一,Handler機制

1,MessageQueue建立

應用程序啓動後,zygote fork一個應用進程後,和普通java程序同樣,程序會首先執行ActivityThread中的main函數。在main函數中,程序首先會建立Looper對象並綁定到主線程中,而後開啓loop循環。(ps:主線程loop循環不能退出)bash

public static void main(String[] args) {
        ......
        Looper.prepareMainLooper();
        ......
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
複製代碼

在prepareMainLooper方法中,最終會建立Looper,MessageQueue對象 以及建立native層MessageQueue對象。app

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
複製代碼
MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
複製代碼

2,消息發送

使用Handler.sendMessageXXX或這 postDedayXXX發送消息後,最終會調用到SendMessageAtTime方法中。異步

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        ......
        return enqueueMessage(queue, msg, uptimeMillis);
}
複製代碼

而後調用MessageQueue.enqueueMessage將消息存到消息隊列中。async

boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
          //將新消息經過 when大小排序,存到消息隊列中。
          //消息隊列其實是一個單鏈表,when最小,即表示最早觸發的消息,會放在鏈表頭部
          ......
          if (needWake) {
              nativeWake(mPtr);
           }
        }
        return true;
    }
複製代碼

存入消息後,而後經過調用native方法 喚醒主線程進行消息處理。ide

3,消息循環

當應用程序啓動,作完一些必要工做以後,便會開啓Loop循環,除非系統異常,不然該循環不會中止。loop循環中,主要作兩件事,第一,從消息隊列中取消息。第二,進行消息分發處理。函數

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

        for (;;) {
            ......
            Message msg = queue.next(); // might block
            ......
            msg.target.dispatchMessage(msg);
            ......
    }
複製代碼
Message next() {
        for (;;) {
            ......
            nativePollOnce(ptr, nextPollTimeoutMillis);
              ......
              //從隊列中取出當前須要處理的消息並返回。
             //若當前消息隊列不爲空,則將nextPollTimeoutMillis賦值爲下一次消息將要觸發的時間。
            //當前消息隊列爲空,則將nextPollTimeoutMillis賦值爲-1,無限進入阻塞狀態,
            //直到下一條消息進入消息隊列時, 喚醒。
              
               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) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
              } else {
                   nextPollTimeoutMillis = -1;
             }
     
            nextPollTimeoutMillis = 0;
            ......
        }
    }
複製代碼

MessageQueue.next() 方法 經過調用 native方法 nativePollOnce(ptr, nextPollTimeoutMillis)實現無消息處理時,進入阻塞的功能。 當nextPollTimeoutMillis 值爲0時,該方法會馬上返回; 當nextPollTimeoutMillis 值爲-1時,該方法會無限阻塞,直到被喚醒; 當nextPollTimeoutMillis 值大於0時,該方法會將該值設置爲超時時間,阻塞到達必定時間後,返回;oop

4,消息分發

在loop循環中 ,經過調用 msg.target.dispatchMessage(msg) 進行消息的分發處理

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

        for (;;) {
            Message msg = queue.next(); // might block
            ......
            msg.target.dispatchMessage(msg);
            ......
    }
複製代碼
/**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
複製代碼

二,IdelHandler是什麼,有啥用?

1,簡介

使用當前線程的MessageQueue.addIdleHandler方法能夠在消息隊列中添加一個IdelHandler。

MessageQueue messageQueue = Looper.myQueue();
        messageQueue.addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                return false;
            }
        });
複製代碼

當MessageQueue 阻塞時,即當前線程空閒時,會回調IdleHandler中的方法;

注:a,添加IdelHandler時,消息隊列不爲空,當消息處理完或者剩下消息還沒到觸發時間,會回調方法 b,當添加IdelHandler時,消息隊列爲空,則當時不會觸發回調

當IdelHandler接口返回false時,表示該IdelHandler只執行一次,

Message next() {
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            nativePollOnce(ptr, nextPollTimeoutMillis);
            ......
            synchronized (this) {
             ......
            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);
                    }
                }
            }
        }
    }
複製代碼

2,經常使用場景

a,延遲執行

例如,當啓動Activity時,須要延時執行一些操做,以避免啓動過慢,咱們經常使用如下方式延遲執行任務,可是在延遲時間上卻很差控制。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //do something
            }
        },1000);
    }
複製代碼

其實,這時候使用IdelHandler 會更優雅

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

        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
               //do something
                return false;
            }
        });
    }
複製代碼

b,批量任務,任務密集,且只關注最終結果

例如,在開發一個IM類型的界面時,一般狀況下,每次收到一個IM消息時,都會刷新一次界面,可是當短期內, 收到多條消息時,就會刷新屢次界面,容易形成卡頓,影響性能,此時就可使用一個工做線程監聽IM消息,在經過添加IdelHandler的方式通知界面刷新,避免短期內屢次刷新界面狀況的發生。

三,消息屏障是啥?

在Android的消息機制中,其實有三種消息: 普通消息、異步消息及消息屏障。

消息屏障也是一種消息,可是它的target爲 null。能夠經過MessageQueue中的postSyncBarrier方法發送一個消息屏障(該方法爲私有,須要反射調用)。

private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; //按照時間順序將消息插入到消息隊列中 ...... return token; } } 複製代碼

在消息循環中,若是第一條消息就是屏障消息,就日後遍歷,看看有沒有異步消息: 若是沒有,則無限休眠,等待被喚醒 若是有,就看離這個消息被觸發時間還有多久,設置一個超時時間,繼續休眠

異步消息和普通消息同樣,只不過它被設置setAsynchronous 爲true。有了這個標誌位,消息機制會對它有些特別的處理,咱們稍後說。

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
    private void createAsyncMessage(){
        Message msg = Message.obtain();
        msg.setAsynchronous(true);
    }
複製代碼

因此消息屏障和異步消息的做用很明顯,在設置消息屏障後,異步消息具備優先處理的權利。

這時候咱們回顧將消息添加到消息隊列中時,能夠發現,其實並非每一次添加消息時,都會喚醒線程。 當該消息插入到隊列頭時,會喚醒該線程; 當該消息沒有插入到隊列頭,但隊列頭是屏障,且該消息是隊列中 靠前的一個異步消息,則會喚醒線程,執行該消息;

調用MessageQueue.removeSyncBarrier 方法能夠移除指定的消息屏障

public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            ......
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }
複製代碼

四,ANR是什麼?有啥關係?

ANR 即 Application Not Response, 是系統進程對應用行爲的一種監控,若是應用程序沒有在規定時間內完成任務的話,就會引發ANR。

ANR類型

Service Timeout: 前臺服務20s, 後臺服務200s

BroadcastQueue Timeout: 前臺廣播 10s,後臺廣播60s

ContentPrivider Timeout: 10s

InputDispatching Timeout: 5s

好比,在啓動一個服務時, AMS端經過應用進程的Binder對象建立Service, 在scheduleCreateService()方法中 會調用到當前service的onCreate()生命週期函數;

private final void realStartServiceLocked(...){
    ...
    bumpServiceExecutingLocked(r, execInFg, "create");
    ...
    app.thread.scheduleCreateService(...);
    ...
    serviceDoneExecutingLocked(...)
}
複製代碼

bumpServiceExecutingLocked()方法內部實際上會調用到scheduleServiceTimeoutLocked()方法,發送一個ActivityManagerService.SERVICE_TIMEOUT_MSG類型消息到AMS工做線程中。

void scheduleServiceTimeoutLocked(ProcessRecord proc) {
        Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG);
        msg.obj = proc;
        mAm.mHandler.sendMessageDelayed(msg,
                  proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
    }
複製代碼

消息的延時時間,若是是前臺服務,延時20s, 若是是後臺服務,延時200s;

// How long we wait for a service to finish executing.
    static final int SERVICE_TIMEOUT = 20*1000;
    // How long we wait for a service to finish executing.
    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
複製代碼

若是Service的建立 工做在 上訴消息的延時時間內完成,則會移除該消息,

private void serviceDoneExecutingLocked(...){
     ...
     mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
     ...
}
複製代碼

不然,在Handler正常收到這個消息後,就會進行服務超時處理,即彈出ANR對話框。

public void handleMessage(Message msg) {
            case SERVICE_TIMEOUT_MSG: {
                mServices.serviceTimeout((ProcessRecord)msg.obj);
            }
複製代碼

五,性能優化

複雜狀況下,可能會頻繁調用sendMessage 往消息隊列中,添加消息,致使消息積壓,形成卡頓,

1,重複消息過濾

頻繁發送同類型消息時,有可能隊列中以前的消息尚未處理,又發了一條相同類型的消息,更新以前的數據,這時候,能夠採用移除前一個消息的方法,優化消息隊列。

private void sendTypeMsg(){
        Message msg = Message.obtain();
        msg.what = MSG_TYPE;
        handler.removeMessages(MSG_TYPE);
        handler.sendMessage(msg);
    }
複製代碼

2,互斥消息取消

在發送消息時,優先將消息隊列中還未處理的信息已通過時的消息 移除,優化隊列

3,隊列優化-複用消息

建立消息時,優先採用以前回收的消息,避免重複建立對象,引發GC

/**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
複製代碼

完~ (若是錯誤或不足,望指出, 你們共同進步)

相關文章
相關標籤/搜索