美文网首页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