美文网首页
Android Handler详解(附面试题)

Android Handler详解(附面试题)

作者: 钢牙仔 | 来源:发表于2020-11-23 02:55 被阅读0次
    Handler模型.png

    可以将Handler模型理解为:生产者—消费者 模型。
    该模型中,生产者在子线程中生产Message,调用Handler对象的sendMessage()等方法,将Message加入到MessageQueue中;Looper.loop()死循环从MessageQueue中取出Message,然后调用Handler对象的handleMessage()方法在主线程中消耗掉Message。

    1.源码分析

    想要弄清楚Handler,得先理解Thread和ThreadLocal

    1.1 Thread和ThreadLocal

    public class Thread implements Runnable {
        ...
        ThreadLocalMap threadLocals = null;
        ...
    }
    

    从Thread源码中得知:

    1. 每一个Thread都有一个ThreadLocalMap类型的成员变量:threadLocals
    2. ThreadLocalMap定义在ThreadLocal中
    public class ThreadLocal<T> {
    
        public ThreadLocal() {
        }
        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
        static class ThreadLocalMap {
            ...
        }
    }
    

    从ThreadLocal源码中得知:

    1. ThreadLocal并不是一个Thread,只不过Thread使用了ThreadLocal中定义的ThreadLocalMap。
    2. 由createMap方法可知,ThreadLocal负责创建当前线程对应Thread对象的ThreadLocalMap
    3. getMap方法获取的是当前线程对应Thread对象的threadLocals属性
    4. set方法是将某一个对象添加到当前线程对应Thread对象的threadLocals中,get方法同理。

    测试ThreadLocal:

    public class MyTest {
        /**
         * 多个线程可以共用一个ThreadLocal
         */
        private static ThreadLocal<Person> mThreadLocal = new ThreadLocal<>();
        public static void main(String[] args) {
            new MyThread("张三").start();
            new MyThread("李四").start();
        }
    
        static class MyThread extends Thread {
            private Person mPerson;
            public MyThread(String name) {
                mPerson = new Person(name);
            }
            @Override
            public void run() {
                super.run();
                // ThreadLocal的set、get方法操作的是当前线程对应Thread对象的threadLocals属性
                mThreadLocal.set(mPerson);
                Person person = mThreadLocal.get();
                System.out.println(Thread.currentThread() + "----" + person.getName());
            }
        }
    
        static class Person {
            private String mName;
            public Person(String name) {
                mName = name;
            }
            public String getName() {
                return mName;
            }
            public void setName(String name) {
                this.mName = name;
            }
        }
    }
    

    测试结果:

    Thread[Thread-0,5,main]----张三
    Thread[Thread-1,5,main]----李四
    

    ThreadLocal总结:
    ThreadLocal提供了线程局部变量,每个线程都可以通过set和get方法来对这个局部变量进行操作,且不会和其他线程的局部变量冲突,实现了线程的数据隔离。

    1.2 Looper、MessageQueue、Message、Handler

    Looper和Handler都持有MessageQueue的引用,那么是谁创建的MessageQueue?
    查看Looper的源码,从Looper的构造函数可知,MessageQueue是由Looper创建的。

    public final class Lopper {
        @UnsupportedAppUsage
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        
        @UnsupportedAppUsage
        final MessageQueue mQueue;
        final Thread mThread;
        
        private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
        }
        
        public static void prepare() {
            prepare(true);
        }
    
        @Deprecated
        public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }
        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));
        }
        public static @Nullable Looper myLooper() {
            return sThreadLocal.get();
        }
        public static void loop() {
            final Looper me = myLooper();
            ...
            me.mInLoop = true;
            final MessageQueue queue = me.mQueue;
            ...
            for (;;) {
                ...
                Message msg = queue.next(); // might block
                ...
                try {
                    msg.target.dispatchMessage(msg);
                }
            }
        }
        public void quit() {
            mQueue.quit(false);
        }
    }
    

    从Looper源码中得知:

    1. Looper的构造函数私有化,不能通过new创建Looper对象,需要使用Looper.prepare()来创建Looper对象
    2. sThreadLocal.set(new Looper(quitAllowed));得知可以在当前线程的其他地方使用sThreadLocal.get()获取Looper对象
    3. loop()方法里面写了一个死循环,不断的调用MessageQueue的next()方法获取Message对象,并调用Handler的dispatchMessage(msg)方法(msg.target指的就是Handler对象)
    public class Handler {
        // Handler的构造函数有很多个,这里只展示其中的两个
        
        public Handler(@Nullable Callback callback, boolean async) {
            ...
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread " + Thread.currentThread()
                            + " that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    
        @UnsupportedAppUsage
        public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
            mLooper = looper;
            mQueue = looper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
        
        // Handler的子类必须实现handleMessage方法
        public void handleMessage(@NonNull Message msg) {
        }
        
        public void dispatchMessage(@NonNull Message msg) {
            ...
            handleMessage(msg);
            ...
        }
        
        /**
         * sendMessage/sendEmptyMessage/sendEmptyMessageDelayed/sendMessageDelayed/...
         * 如上所有的发送消息方法最终都执行的是sendMessageAtTime
         */
        public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
            MessageQueue queue = mQueue;
            if (queue == null) {
                RuntimeException e = new RuntimeException(
                        this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
                return false;
            }
            return enqueueMessage(queue, msg, uptimeMillis);
        }
        
        private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                long uptimeMillis) {
            msg.target = this;
            msg.workSourceUid = ThreadLocalWorkSource.getUid();
    
            if (mAsynchronous) {
                msg.setAsynchronous(true);
            }
            return queue.enqueueMessage(msg, uptimeMillis);
        }
    
    }
    

    从Handler源码可知:

    1. 我们在使用Handler的时候,一般是new一个Handler,然后重写handleMessage方法,该方法是由dispatchMessage来调用,而dispatchMessage是在Looper对象的loop()方法中的死循环中执行。
    2. sendMessage、sendEmptyMessage、sendMessageDelayed等发送消息的方法,实际上调用的是MessageQueue对象的enqueueMessage(msg, uptimeMillis)方法,将Message对象放入MessageQueue中
    public final class MessageQueue { {
        // True if the message queue can be quit.
        @UnsupportedAppUsage
        private final boolean mQuitAllowed;
        
        @UnsupportedAppUsage
        Message mMessages;
        MessageQueue(boolean quitAllowed) {
            mQuitAllowed = quitAllowed;
            mPtr = nativeInit();
        }
        
        @UnsupportedAppUsage
        Message next() {
            // Message是一个单链表结构,有一个属性next指向了后一个Message
        }
        
        boolean enqueueMessage(Message msg, long when) {
            synchronized (this) {
                // 时间参数when表示Message的优先级
                // 1.根据时间参数when来判断当前Message(假设为curr_msg)要插入到哪一个Message(假设为pre_msg)的后面
                // 2.如果pre_msg的next不为空,则用一个临时变量记录:temp_msg = pre_msg.next
                // 3.将pre_msg.next = curr_msg,然后将curr_msg.next = temp_msg
            }
        }
        void quit(boolean safe) {
            if (!mQuitAllowed) {
                throw new IllegalStateException("Main thread not allowed to quit.");
            }
            ...
        }
    }
    

    从MessageQueue的源码可知:
    mQuitAllowed表示MessageQueue是否可以销毁。
    我们在创建Looper时,一般调用的是Looper.prepare(),该方法最终调用的是Looper的构造方法,会将mQuitAllowed设置为true,表示可以销毁。

    public final class ActivityThread extends ClientTransactionHandler {
        public static void main(String[] args) {
            ...
            Looper.prepareMainLooper();
            ...
            Looper.loop();
            ...
        }
    }
    

    从ActivityThread源码可知:
    启动一个app时,会在app的主线程创建一个Looper,而此时调用的是Looper.prepareMainLooper(),会将mQuitAllowed设置为false,表示不可以销毁。

    public final class Message implements Parcelable {
        @UnsupportedAppUsage
        /*package*/ Handler target;
        
        @UnsupportedAppUsage
        /*package*/ Message next;
    
        /** @hide */
        public static final Object sPoolSync = new Object();
        private static Message sPool;
        private static int sPoolSize = 0;
        
        public void setTarget(Handler target) {
            this.target = target;
        }
        
        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();
        }
        public static Message obtain(Message orig) {
            Message m = obtain();
            m.what = orig.what;
            m.arg1 = orig.arg1;
            m.arg2 = orig.arg2;
            m.obj = orig.obj;
            m.replyTo = orig.replyTo;
            m.sendingUid = orig.sendingUid;
            m.workSourceUid = orig.workSourceUid;
            if (orig.data != null) {
                m.data = new Bundle(orig.data);
            }
            m.target = orig.target;
            m.callback = orig.callback;
    
            return m;
        }
    }
    

    从Message源码可知:

    1. 成员变量target是一个Handler对象
    2. 可以通过Message.obtain()来创建Message对象,这种方式可以复用已经创建过的Message,从而避免频繁的创建、销毁Message,达到优化内存和性能的目的。

    1.3 Toast

    不知道大家有没有在子线程中使用过Toast,如果有,那么你可能碰到过在子线程中直接调用Toast的makeText()方法会报错Can't toast on a thread that has not called Looper.prepare()

    public class Toast {
        
        public Toast(Context context) {
            this(context, null);
        }
    
        public Toast(@NonNull Context context, @Nullable Looper looper) {
            ...
            looper = getLooper(looper);
            ...
        }
        
        private Looper getLooper(@Nullable Looper looper) {
            if (looper != null) {
                return looper;
            }
            return checkNotNull(Looper.myLooper(),
                    "Can't toast on a thread that has not called Looper.prepare()");
        }
        public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
            return makeText(context, null, text, duration);
        }
    
        public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
                @NonNull CharSequence text, @Duration int duration) {
            if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
                Toast result = new Toast(context, looper);
                result.mText = text;
                result.mDuration = duration;
                return result;
            } else {
                Toast result = new Toast(context, looper);
                View v = ToastPresenter.getTextToastView(context, text);
                result.mNextView = v;
                result.mDuration = duration;
    
                return result;
            }
        }
    }
    

    从Toast源码可知:

    1. makeText()会调用Toast的构造函数,构造函数中调用getLooper(),此时looper为空,会执行checkNotNull(Looper.myLooper(), "...")方法,通过方法名可以直接得出该方法用于判空,为空则抛出异常。
    2. 从Looper的源码可知,Looper.myLooper()获取的是当前线程的Looper对象,而此时子线程的Looper对象为空,导致了异常。

    子线程中使用Toast的方法:
    第一步,调用Looper.prepare()给子线程创建一个Looper对象。
    第二步,调用Toast.makeText(...).show()向子线程的MessageQueue插入Message
    第三步,调用Looper.loop(),从MessageQueue中取出Message

    1.4 ThreadHandler

    
    

    1.5 同步屏障

    同步Message和异步Message的区别:

    1. 同步(sync)就是一个一个的来,处理完一个再处理下一个;异步(async)就是可以同时处理多个,不需要等待。
    2. 同步Message,就是按照顺序执行的Message,默认情况下,我们通过Handler对象的sendMessage()方法发送的Message就是同步Message。
    3. 异步Message,类似于屏幕点击事件的Message,就是异步Message。

    什么是同步屏障?
    字面意思理解,就是设置了一道屏障,让同步Message不可以通过,从而优先处理异步Message

    那么同步屏障如何设置?我们来看ViewRootImpl的源码:

    public final class ViewRootImpl implements ViewParent,
    View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
        ...
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
            }
        }
    
        void unscheduleTraversals() {
            if (mTraversalScheduled) {
                mTraversalScheduled = false;
                mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
                mChoreographer.removeCallbacks(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            }
        }
        ...
    }
    

    从ViewRootImpl的源码可知:

    1. scheduleTraversals()中调用了MessageQueue对象的postSyncBarrier()方法,设置了同步屏障,设置同步屏障的代码如下:
    public final class MessageQueue {
        @UnsupportedAppUsage
        @TestApi
        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;
            }
        }
    }
    

    如上代码可知,设置同步屏障就是在MessageQueue队列中新增了一个Message对象,并将该Message对象插入到队列的最前面。当我们调用Handler对象的sendMessage()方法时,会将Message对象的target属性设置为Handler对象,而此处postSyncBarrier()方法没有设置Message对象的target,说明target=null

    1. scheduleTraversals()中调用了mChoreographer.postCallback(),追踪源码可知调用的是Handler对象的sendMessageAtTime()发送Message,但是在发送Message之前执行了调用了Message对象的setAsynchronous(true),将Message设置为了异步消息
    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
    msg.arg1 = callbackType;
    msg.setAsynchronous(true);
    mHandler.sendMessageAtTime(msg, dueTime);
    
    1. unscheduleTraversals()中调用了MessageQueue对象的removeSyncBarrier()方法,移除了同步屏障
    2. unscheduleTraversals()中调用了mChoreographer.removeCallbacks(),追踪源码可知调用的是Handler对象的removeMessages(),就是将Message从MessageQueue中移除。

    再来看MessageQueue的next()方法:

        @UnsupportedAppUsage
        Message next() {
            ...
                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) {
                        // Stalled by a barrier.  Find the next asynchronous message in the queue.
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                }
            ...
        }
    

    next()方法中的if语句中,判断了Message对象的target属性是否为空。如果为空,则说明MessageQueue队列中有需要优先处理的异步Message,此时执行do-while循环,去查找队列中的异步Message,找到异步Message之后返回给Looper。判断一个Message是否为异步Message:msg.isAsynchronous()

    同步屏障总结:

    1. 要使用异步Message,需要发送两个Message。一个Message的target=null,用于告诉MessageQueue,当前队列中有异步消息;另一个Message才是真正的消息载体,并且要设置setAsynchronous(true),用于标记Message的异步属性。
    2. Looper的死循环中,会调用MessageQueue的next()方法从消息队列中获取消息,而 MessageQueue的next()方法会优先返回异步消息。

    2.Handler常见面试题

    2.1 一个线程有几个Handler?

    答:一个线程可以有多个Handler,需要时,通过new Handler()的方式创建Handler

    2.2 一个线程有几个Looper,如何保证?

    答:一个线程只有一个Looper,从Looper的源码中可知,Looper的构造私有化了,需要通过Looper.prepare()创建Looper,创建的Looper会使用ThreadLocal存起来,再次调用Looper.prepare()会检查ThreadLocal中是否有Looper,如果有则抛出异常。

    2.3 Handler内存泄漏的原因?

    答:假设在Activity中使用匿名内部类的方式创建了一个Handler:

    public class MainActivity extends Activity {
    
        private TextView mTextView;
    
        Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                // 等价于:MainActivity.this.mTextView.setText("hello")
                mTextView.setText("hello");
                if (msg.what == 100) {
                    // do something...
                }
            }
        };
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            new Thread(){
                @Override
                public void run() {
                    super.run();
                    Message message = new Message();
                    message.what = 100;
                    // 延迟一小时发送
                    mHandler.sendMessageDelayed(message, 1000 * 60 * 60);
                }
            }.start();
        }
    }
    

    结论:匿名内部类默认会持有外部类对象的引用,这也是为什么能直接调用mTextView.setTex()的原因

    1. 从Message的源码可知,Message有一个Handler类型的属性target,说明Message持有一个Handler
    2. 从上述结论可知Handler持有MainActivity对象
    3. 如果Message得不到释放,则Handler也得不到释放,那么MainActivity也得不到释放

    上述代码中,将Message设置为延迟一小时后再发送,如果一小时内需要finish掉MainActivity跳转到其他Activity,则此时会发生内存泄漏,因为GC无法回收MainActivity。
    可以使用静态内部类 + 弱引用的方式解决:

    public class MainActivity extends Activity {
    
        private TextView mTextView;
    
        Handler mHandler;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mHandler = new MyHandler(this);
            new Thread(){
                @Override
                public void run() {
                    super.run();
                    Message message = new Message();
                    message.what = 100;
                    // 延迟一小时发送
                    mHandler.sendMessageDelayed(message, 1000 * 60 * 60);
                }
            }.start();
        }
    
        private static class MyHandler extends Handler {
    
            private WeakReference<MainActivity> mMainReference;
            public MyHandler(MainActivity mainActivity) {
                mMainReference = new WeakReference<MainActivity>(mainActivity);
            }
    
            @Override
            public void handleMessage(Message msg) {
                MainActivity mainActivity = mMainReference.get();
                mainActivity.mTextView.setText("hello");
                if (msg.what == 100) {
                    // do something...
                }
            }
        }
    }
    

    2.4 为何主线程中可以new Handler,在子线程中new Handler要做什么准备?

    答:先调用Looper.prepare(),再调用Looper.loop()

    2.5 子线程中维护的Looper,当MessageQueue中无消息时,该如何处理?

    答:调用Looper对象的quit()方法,最终调用的是MessageQueue的quit()方法

    2.6 不同线程的Handler往MessageQueue中添加Message,Handler内部如何保证线程安全?

    答:

    1. 从Looper的源码可知,MessageQueue对象在Looper的构造函数中创建。即每一个线程对应一个MessageQueue对象
    2. 从MessageQueue的enqueueMessage(...)方法可知,该方法中的synchronized(this)持有的是this对象锁,当前MessageQueue对象其他使用synchronized(this)的地方都会等待

    以上两点,保证了线程安全。

    2.7 使用Message时应该如何创建它?

    答:从Message源码可知,应该使用Message.obtain()来创建Message对象

    2.8 为什么主线程不会因为 Looper.loop() 里的死循环卡死?

    答:

    1. ActivityThread是应用程序的入口,该类中的main函数是整个Java程序的入口,main函数的主要作用就是做消息循环,一旦main函数执行完毕,循环结束,那么应用也就可以退出了。
    2. ActivityThread的main函数中调用了Looper.loop()开启了一个死循环,loop()方法中调用了MessageQueue的next()方法,该方法是一个阻塞方法(这里涉及到Linux中的pipe,不做赘述),如果MessageQueue中没有Message,则会等待。
    3. loop()的阻塞,是指MessageQueue中没有Message,此时释放CPU执行权,等待唤醒。
    4. ANR:Application Not Responding。导致ANR的情况比如主线程中访问网络、操作数据库等耗时操作,此时也会往MessageQueue中发送Message,然后loop()循环中获取到了Message,但是在处理Message时耗时过长,导致短时间内无法响应屏幕点击事件等操作(屏幕被点击也会发送Message到MessageQueue中),然后就出现了ANR。

    相关文章

      网友评论

          本文标题:Android Handler详解(附面试题)

          本文链接:https://www.haomeiwen.com/subject/enyuiktx.html