Handler详解

1,266 阅读8分钟

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

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

  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相关的准备操作,我们来看一下具体实现。

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有何关联,接着往下看。

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

final H mH = new H();

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

  /**  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。

为何之前文章提到的MessageQueue内部是一个死循环,但是UI并不卡顿

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

当然你还有疑问为什么要将MessageQueue写成死循环,这是因为UI线程需要一直接收系统,以及其他组件发送过来的事件,中间可能会有空闲的情况,为了保证MessageQueue不退出,就只能写成死循环了。

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绘制尽快完成,以免造成卡顿。