Handler详解

这篇是以前Looper、Handler、Message以及MessageQueue之间的关系后续版本,将更加详细讲解一下以前没提到的细节内容。java

先说一下这篇文章主要要解决的几个点:bash

  1. 为什么Activity中直接new Hander()调用不报错;
  2. 为什么以前文章提到的MessageQueue内部是一个死循环,可是UI并不卡顿;
  3. Handler.postDelay()实现的原理是什么;
  4. 子线程使用Handler的注意点;
  5. 一些其余的知识点;

为什么Activity中直接new Hander()调用不报错

以前文章提到过要在线程(包括UI线程)中使用Handler,就须要Looper.prepare()以及Looper.loop()相关操做,可是为何Activity中能够直接使用Handler呢?这个问题在以前文章中提到过,是由于系统已经帮咱们作了Looper相关的准备操做,咱们来看一下具体实现。app

Android进程启动过程当中会建立一个ActivityThread对象,并调用其中的main(),用以接收系统的各类操做。这个ActivityThread对象就是咱们常说的UI线程,其实并非一个线程。在main()中咱们就能看到Looper初始化相关的操做。异步

public static void main(String[] args) {

        ...
        
        Looper.prepareMainLooper();
        
        ...
        
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        
        ...
        
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
复制代码

这里咱们只看到了MainLooper初始化操做,并无发现它跟Activity有何关联,接着往下看。ide

ActivityThread有一个成员变量mH,是一个H类型的对象,这个H是是Handler的子类,内部用于处理4大组件的启动、生命周期相关方法以及其余一些系统的回调。固然系统的启动过程太过冗长,这里不作细致的分析,如今只须要知道mH这个Handler对象。oop

final H mH = new H();
复制代码

就是这个mH接收了来自ActivityManagerService传递过来的启动Activity的消息,并调用了ActivityThread中的performLaunchActivity()来建立一个Activity并调用相关生命周期方法()post

/**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }        
        ....
        
        return activity;
    }
复制代码

这里咱们能够知道,Activity运行在mH内部。再看最上面ActivityThread的建立,能够看出mH中调用的方法也是在MainLooper中执行,Activity在MainLooper中执行,因此咱们并不须要先初始化Looper再使用Handler。ui

为什么以前文章提到的MessageQueue内部是一个死循环,可是UI并不卡顿

首先要明白一个问题,UI卡顿实际上是MessageQueue中某些方法,好比onCreate()、onResume()等生命周期的回调执行过长,致使UI绘制某些帧丢失甚至ANR。MessageQueue虽然是个死循环,可是当消息队列为空时,只是阻塞消息队列,当时并不占用cpu资源,因此并不会引发UI的卡顿。至于为何MessageQueue能够作到没有消息时阻塞队列,有消息时能够唤起继续工做的,读者能够查阅Handler机制在Native层中的实现,主要是Linux中的epoll。this

固然你还有疑问为何要将MessageQueue写成死循环,这是由于UI线程须要一直接收系统,以及其余组件发送过来的事件,中间可能会有空闲的状况,为了保证MessageQueue不退出,就只能写成死循环了。spa

Handler.postDelay()实现的原理是什么

前面的文章咱们能够知道,无论Handler经过什么形式发送消息,最后都会调用到enqueueMessage()。这个方法内部调用了MessageQueue的enqueueMessage()。

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
        //除了系统的同步控制,全部的msg都必需要有target来处理msg
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
            //循环队列正在退出,直接将msg回收
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
            
            //这里开始正常的msg入队操做,标记msg的使用状态,处理时间等
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                //若是当前队列为空,
                //或者msg须要当即处理,
                //或者当前消息队列中头消息处理的时间大于当前消息,
                //都是将头消息置为当前消息,
                //同时若是当前队列阻塞,则唤起消息队列进入循环
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                //进入这个分支,是将msg按执行时间前后插入消息链表中
                //正常状况下咱们不须要唤醒队列,除非当前消息对列正在
                //同步消息控制,同时当前消息为第一条异步消息
                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;
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
复制代码

上面的方法咱们能够看到,处理了几种状况都在注释里作了说明,下面再来看看消息具体是如何取出来的

Message next() {
        //ptr = 0 当前队列正在退出
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        
        //这个nextPollTimeoutMillis用来控制消息队列是否要阻塞,以及阻塞多久
        //nextPollTimeoutMillis = 0 表示不阻塞
        //nextPollTimeoutMillis = -1 表示一直阻塞,直到被唤醒
        //nextPollTimeoutMillis > 0 表示阻塞这么长时间以后唤醒
        int nextPollTimeoutMillis = 0; 
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            
            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) {
                    //当前消息队列被同步控制,找到第一条异步的消息
                    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;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    //消息队列为空,直接进入阻塞状态,等待唤醒
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                //首次循环的时候执行设置的IdleHandler
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                
                ....

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
复制代码

上面消息的enqueueMessage()以及next()取出操做能够很清晰的看到Handler.postDelay()的实现原理,就是消息按执行时间入队,并延迟处理。

子线程使用Handler的注意点

子线程中须要咱们手动准备Looper,而后很方便的进行线程间通讯,可是在消息处理完成以后要手动将Looper退出,以避免形成内存泄漏。 从SDK 23 开始系统提供一个HandlerThread供咱们方便的在子线程中使用Handler;

其余

Handler形成的内存泄漏

当咱们在Activity内部直接建立一个非静态的Handler成员变量时,IDE就会提醒咱们,这样使用Handler可能会形成内存泄漏,咱们来分析一下缘由在哪里。

  1. Handler全部发送的Message都有一个target指向发送的Handler,用于后续处理回调
  2. 非静态内部类隐式的持有外部类的一个引用
  3. 匿名内部类也隐式的持有外部类的一个引用

好,根据上面3点,咱们来看一下Activity的引用:

public class MainActivity extends AppCompatActivity {
    private Handler mHandler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mHandler = new Handler();
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //do something
            }
        }, 10 * 60 * 1000);
    }
}
复制代码

当这个runnable消息非发送出去以后,mHandler被msg持有,并安排到10分钟以后执行,同时MainActivity也一直被mHandler和runnable持用。 当MainActivity退出时,由于Looper是UI线程的Looper,咱们没法退出,msg还在消息队列中还没有执行,会阻止MainActivity被系统回收,就形成了内存泄漏。

避免这种状况方法有不少种这里提2种经常使用的方式:

  1. 定义静态Handler对象,并传人UI线程Looper

    static Handler sHandler = new Handler(Looper.getMainLooper());
    复制代码
  2. 将咱们须要的Handler定义成静态内部类,同时使用弱引用保存对Activity的引用

    public static class MyHandler extends Handler {
    
        private final WeakReference<Activity> mActivityWf;
    
        public MyHandler(WeakReference<Activity> activityWf) {
            mActivityWf = activityWf;
        }
    }
    复制代码

    上面提到的同步控制

消息队列的同步控制是经过postSyncBarrier()这个方法来实现, 说是Barrier,其实就是在队列的头部插入一条特殊的msg

public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    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; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } } 复制代码

代码其实也挺简单的,就是在消息队列适当的位置插入一条没有target的msg,next()出去消息时,若是发现个同步控制消息,则它以后的同步消息就暂时不出列。

默认状况下,咱们构建的Handler发送的都是同步消息,除非在Handler构建的时候指定发送的全部消息类型都为异步消息。

固然通常咱们也不须要同步控制,虽然当前方法是public修饰的,可是hide,系统并无打算将此方法开放给咱们使用。

咱们再来看一下,系统哪里用到了这个同步控制。

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
复制代码

这是ViewRootImpl中的一个方法,这个方法执行以后,系统就开始进行页面的绘制流程,咱们看到这里调用postSyncBarrier(),这么作的目的就是要让UI绘制尽快完成,以避免形成卡顿。

相关文章
相关标签/搜索