美文网首页Android技术知识Android开发Android开发
关于Handler,看看面试官都问了我哪些?这些你都知道吗?

关于Handler,看看面试官都问了我哪些?这些你都知道吗?

作者: 4ca1bbef6a0c | 来源:发表于2020-06-16 16:47 被阅读0次

    前言

    做 Android 开发肯定离不开跟 Handler 打交道,它通常被我们用来做主线程与子线程之间的通信工具,而 Handler 作为 Android 中消息机制的重要一员也确实给我们的开发带来了极大的便利。
    可以说只要有异步线程与主线程通信的地方就一定会有 Handler。

    在面试中Handler也是经常被问的一个点,那么本篇文章就以问答的方式,带你了解一下关于Handler的重要知识点。

    面试官:一个线程有几个 Looper?几个 Handler?

    小王:
    一个Thread只能有一个Looper,一个MessageQueen,可以有多个Handler
    以一个线程为基准,他们的数量级关系是: Thread(1) : Looper(1) : MessageQueue(1) : Handler(N)

    面试官:Handler 内存泄漏原因? 以及最佳解决方案?
    小王:

    • 泄露原因:
      Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。

    • 解决方案

    1. 最直接的思路就是避免使用非静态内部类。使用Handler的时候,放在一个新建的文件中来继承Handler或者使用静态的内部类来替代。静态内部类不会隐含的持有外部类的引用,因此这个activity也就不会出现内存泄漏问题。
    2. 如果你需要在Handler内部调用外部Activity的方法,你可以让这个Handler持有这个Activity的弱引用,这样便不会出现内存泄漏的问题了。
    3. 另外,对于匿名类Runnable,我们同样可以设置成静态的,因为静态内部类不会持有外部类的引用。
    4. 注意:如果使用Handler发送循环消息,最好是在Activity的OnDestroy方法中调用mLeakHandler.removeCallbacksAndMessages(null);移除消息。(这不是解决内存泄漏的方法)

    5.两种解决办法如下:

    弱引用(WeakReference)

     public class SampleActivity extends Activity {
    
      /**
       * Instances of static inner classes do not hold an implicit
       * reference to their outer class.
       * 弱引用的方式
       */
        private static class MyHandler extends Handler {
        private final WeakReference<SampleActivity> mActivity;
        public MyHandler(SampleActivity activity) {
          mActivity = new WeakReference<SampleActivity>(activity);
        }
    
        @Override
        public void handleMessage(Message msg) {
          SampleActivity activity = mActivity.get();
          if (activity != null) {
             //to Something
          }
        }
     }
    

    静态

     //定义成static的,因为静态内部类不会持有外部类的引用
      private final MyHandler mHandler = new MyHandler(this);
      private static final Runnable sRunnable = new Runnable() {
          @Override
          public void run() {//to something}
      };
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
        finish();
      }
    }
    

    面试官:为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么准备?
    小王:
    每一个handler必须要对应一个looper,主线程会自动创建Looper对象,不需要我们手动创建,所以主线程可以直接创建handler。
    在new handler的时候没有传入指定的looper就会默认绑定当前创建handler的线程的looper,如果没有looper就报错。

    因为在主线程中,Activity内部包含一个Looper对象,它会自动管理Looper,处理子线程中发送过来的消息。而对于子线程而言,没有任何对象帮助我们维护Looper对象,所以需要我们自己手动维护。
    所以要在子线程开启Handler要先创建Looper,并开启Looper循环

    如果在子线程中创建了一个Handler,那么就必须做三个操作:

    1. prepare();
    2. loop();
    3. quit();

    面试官:子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
    小王:
    在Handler机制里面有一个Looper,在Looper机制里面有一个函数,叫做quitSafely()和quit()函数,这两个函数是调用的MessageQueue的quit()。

     /**
         * Quits the looper.
         * <p>
         * Causes the {@link #loop} method to terminate without processing any
         * more messages in the message queue.
         * </p><p>
         * Any attempt to post messages to the queue after the looper is asked to quit will fail.
         * For example, the {@link Handler#sendMessage(Message)} method will return false.
         * </p><p class="note">
         * Using this method may be unsafe because some messages may not be delivered
         * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
         * that all pending work is completed in an orderly manner.
         * </p>
         *
         * @see #quitSafely
         */
        public void quit() {
            mQueue.quit(false);
        }
     
        /**
         * Quits the looper safely.
         * <p>
         * Causes the {@link #loop} method to terminate as soon as all remaining messages
         * in the message queue that are already due to be delivered have been handled.
         * However pending delayed messages with due times in the future will not be
         * delivered before the loop terminates.
         * </p><p>
         * Any attempt to post messages to the queue after the looper is asked to quit will fail.
         * For example, the {@link Handler#sendMessage(Message)} method will return false.
         * </p>
         */
        public void quitSafely() {
            mQueue.quit(true);
        }
    

    再进入到MessageQueue的quit()函数。

    void quit(boolean safe) {
            if (!mQuitAllowed) {
                throw new IllegalStateException("Main thread not allowed to quit.");
            }
     
            synchronized (this) {
                if (mQuitting) {
                    return;
                }
                mQuitting = true;
     
                if (safe) {
                    removeAllFutureMessagesLocked();
                } else {
                    removeAllMessagesLocked();
                }
     
                // We can assume mPtr != 0 because mQuitting was previously false.
                nativeWake(mPtr);
            }
        }
    

    它会remove消息,把消息队列中的全部消息给干掉。
    把消息全部干掉,也就释放了内存

    private void removeAllFutureMessagesLocked() {
            final long now = SystemClock.uptimeMillis();
            Message p = mMessages;
            if (p != null) {
                if (p.when > now) {
                    removeAllMessagesLocked();
                } else {
                    Message n;
                    for (;;) {
                        n = p.next;
                        if (n == null) {
                            return;
                        }
                        if (n.when > now) {
                            break;
                        }
                        p = n;
                    }
                    p.next = null;
                    do {
                        p = n;
                        n = p.next;
                        p.recycleUnchecked();
                    } while (n != null);
                }
            }
        }
    

    而在quit()函数的最后一行,有一个nativeWake()函数。

     // We can assume mPtr != 0 because mQuitting was previously false.
                nativeWake(mPtr);
    

    这个函数的调用,就会叫醒等待的地方,醒来之后,就接着往下执行。

                //native的方法,在没有消息的时候回阻塞管道读取端,只有nativePollOnce返回之后才能往下执行
                //阻塞操作,等待nextPollTimeoutMillis时长
                nativePollOnce(ptr, nextPollTimeoutMillis);
    

    往下执行后,发现 Message msg = mMessages; 是空的,然后就执行了这个,就接着往下走。

                    if (msg != null) {
     
                       ......
     
                    } else {
                        // No more messages.
                        //没有消息,nextPollTimeoutMillis复位
                        nextPollTimeoutMillis = -1;
                    }
    

    然后又调用了这个方法,并且return了null。

                    // Process the quit message now that all pending messages have been handled.
                    //如果消息队列正在处于退出状态返回null,调用dispose();释放该消息队列
                    if (mQuitting) {
                        dispose();
                        return null;
                    }
    

    所以说,这个时候Looper就结束了(跳出了死循环),则达成了第二个作用:释放线程

    面试官:既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?
    小王:
    这里主要关注 MessageQueue 的消息存取即可,看源码内部的话,在往消息队列里面存储消息时,会拿当前的 MessageQueue 对象作为锁对象,这样通过加锁就可以确保操作的原子性和可见性了。

    消息的读取也是同理,也会拿当前的 MessageQueue 对象作为锁对象,来保证多线程读写的一个安全性。


    面试官:我们使用 Message 时应该如何创建它?
    小王:
    创建的它的方式有两种:
    一种是直接 new 一个 Message 对象,
    另一种是通过调用 Message.obtain() 的方式去复用一个已经被回收的 Message,
    当然日常使用者是推荐使用后者来拿到一个 Message,因为不断的去创建新对象的话,可能会导致垃圾回收区域中新生代被占满,从而触发 GC。

    Message 中的 sPool 就是用来存放被回收的 Message,当我们调用 obtain 后,会先查看是否有可复用的对象,如果真的没有才会去创建一个新的 Message 对象。

    补充:主要的 Message 回收时机是:
    1.在 MQ 中 remove Message 后;
    2.单次 loop 结束后;
    3.我们主动调用 Message 的 recycle 方法后

    面试官:Looper死循环为什么不会导致应用卡死?
    小王:
    Launch桌面的图标第一次启动Activity时,会最终走到ActivityThread的main方法,在main方法里面创建Looper和MessageQueue处理主线程的消息,然后Looper.loop()方法进入死循环,我们的Activity的生命周期都是通过Handler机制处理的,包括 onCreate、onResume等方法,下面是loop方法循环。

    主线程的主要方法就是消息循环,一旦退出消息循环,那么你的应用也就退出了,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。

    造成ANR的不是主线程阻塞,而是主线程的Looper消息处理过程发生了任务阻塞,无法响应手势操作,不能及时刷新UI。

    阻塞与程序无响应没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序是不会无响应的。

    总结:应用卡死压根与这个Looper没有关系,应用在没有消息需要处理的时候,它是在睡眠,释放线程;卡死是ANR,而Looper是睡眠。

    最后

    本文以问答的方式来列了一些在面试中,经常会会被面试官问到的一些关于Handler的问题,由于文章篇幅的原因,很多问题都只是出略回答了一下,如果有朋友需要更详细的答案的,可以私信我【答案】或者【点这里】免费领取!

    相关文章

      网友评论

        本文标题:关于Handler,看看面试官都问了我哪些?这些你都知道吗?

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