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();
    }
复制代码

完~ (若是错误或不足,望指出, 你们共同进步)

相关文章
相关标签/搜索