美文网首页Android开发经验谈Android技术知识Android开发
Handler面试题全解析,看完表示Handler彻底懂了

Handler面试题全解析,看完表示Handler彻底懂了

作者: 躲得过初一躲不过十五 | 来源:发表于2022-08-21 21:13 被阅读0次

前言

做Android开发的肯定离不开跟Handler打交道,它通常被我们来做主线程与子线程之间的通信工具,而Handler作为Android中消息机制的重要医院也确实给我们的开发带来了极大的便利。

可以说只要有异步线程与主线程通信的地方就一定有Handler。在面试中Handler也是常常被问到的一个点,哪现在就介绍一下关于Handler的重要知识点。

1.一个线程有几个Looper?几个Handler

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

2.Handler内存泄漏原因?以及解决方案

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

*解决方案

  • 最直接的思路就是避免使用非静态内部类,使用Handler的时候,放在一个新建的文件中来继承Handler或者使用静态的内部类来替代。静态内部类不会隐含的持有外部类的引用,因为这个Activity也就不会出现内存泄漏的问题。
  • 如果你需要在Handler内部调用外部activity的方法,你可以让这个Handler持有这个activity的弱引用,这样便不会出现内存泄漏的问题了。
  • 另外,对于匿名类Runnable,我们同样可以设置成静态的,因为静态内部类不会持有外部类的引用。
  • 注意:如果使用Handler发送循环消息,最好是在ActivityOnDestroy方法中调用meleakHandler.removecallbacksAndMessages(null);移除消息。(这不是解决内存泄漏的方案)
  • 两种解决方法如下:弱引用和静态
    • 弱引用(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 <SamleActivity>
    mActivity;Public MyHandler(SampActivity activity) {
      mActivity = new WeakReference<sampleActivity>(activity)
    }
    @Override
    public void handleMessage(message msg) {
       SamleActivity 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();
    }
 }

3.为何主线程可以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();

4.子线程中维护的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 message to the queue after the looper is asked to quit will fail.
      * Four 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 thelooper terminates.  Consider using {@link #quiSafely} 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 messages 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);
   }

再进入到MessageQueuequit()函数

  void quit (boolean safe) {
            if (!mQuitAllowed) {
               throw new IlllegalstateException(''Main thread not allowed to quit.'');
            }
            synchronized (this)
                 {  if (mQuitting)
                    return
                 }
                 mQutting = true;
                 if (safe)  {
                     removeAllFutureMessagesLocked():
                 } else {
                 // 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)
                        { ruturn
                     }
                     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 assum 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;
    }

然后又调用了这个方法,并且returnnull

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

5.既然可以存在多个Handler往MessageQueue中添加数据(发消息时各Handler`可能处于不同线程),哪它内部是如何确保线程安全的?

这里主要关注MessageQueue的消息存取即可,看源码内部的话,在往消息队列里面存储消息时,会拿当前的MessageQueue对象作为锁对象,这样通过加锁就可以确保操作的原子性和可见性了。

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

6.我们使用Message时应该如何创建它

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

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

补充:主要的Message回收时机是:

  • 在MQ中remove Message后;
  • 单次loop结束后
  • 我们主动调用messagerecycle方法后

7.Looper死循环为什么不会导致应用致死?

launch桌面的图标第一次启动Activity时,最终走到ActivityThreadmain方法,在main方法里面创建LoopermessageQueue处理主线程的消息,然后Looper.loop()方法进入死循环,我们的Activity的生命周期都是通过Handler机制处理的,包括oncreat,onResume方法,下面是loop方法循环。

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

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

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

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

相关文章

网友评论

    本文标题:Handler面试题全解析,看完表示Handler彻底懂了

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