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

Posted 初一十五啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler面试题全解析,看完表示Handler彻底懂了相关的知识,希望对你有一定的参考价值。

前言

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是睡眠。

如果你觉得以上的内容,学完还有点意犹未尽?毕竟学无止境。那么还能即时领取一份,【(视频+文字)腾讯最全面Android高级学习笔记】,内容如下:

包含(视频+文字):Android基础-性能优化-Framework-compose开源项目-音视频初中高-架构-车载-Flutter-Kotlin-Harmony OS+音视频详细文档。

一丶T10级Android工程师必备基础技能

涉及: 注解、泛型、Retrofit、架构动态编程技术原理、ButterknifeJavaSSit丶虚拟机丶并发内存丶Synchronize丶并发原理之线程池丶数据序列化

二丶T10工程师核心优化能力

涉及: 启动优化、内存优化、启动优化速度、卡顿优化、布局优化、崩溃优化、应用启动全流程(源码深度解析)等内容.

三丶Framework

涉及Framework通信、Framework底层服务、Framework事件机制等内容.

四丶Compose(开源项目)

五.音视频开发

涉及:C和C++基础语法,H264编码基础和进阶,H265编码原理和应用MediaCodec硬解码,Media内核源码,WeChat视频通话。初级-中-高

附带音视频开发预习资料文档

涉及:视频区,视频压缩,音视频同步,FFmpeg,OPenGL,OpenSL ES,抖音美颜滤镜,交叉编译,视频变速,FFmpeg实现音视频同步

六丶Android资深架构师

涉及:Arraylist,Okhttp,Retrofit,图片加载,Dagger 2,MVC.MVP.MVVM,Jetpack Room

七丶Android车载工程师

涉及Android Auto,汽车媒体应用,构建Android Auto即时通信应用,构建车载导航和地图注点应用,构建Android Automotive OS视频应用,测试Android车载应用,分发Android汽车应用,适用于汽车的Google Play服务,Android Automotive OS的通知.

八丶Flutter高级工程师

涉及Dart语法,Flutter动画丶组件丶网络请求以及Flutter3.0简介。

九.Harmony OS

涉及Ability组件,分布式任务,事件总线,Harmony OS线程,UI自定义控件

十丶Kotlin相关

涉及:对象丶类丶继承丶变量丶常量丶拓展函数等20多个内容**

以上是关于Handler面试题全解析,看完表示Handler彻底懂了的主要内容,如果未能解决你的问题,请参考以下文章

源码解析 Handler 面试宝典

Android Handler面试解析

面试再也不怕 Handler 了,消息传递机制全解析

Android Handler相关面试题你能答对多少?子线程和主线程是如何切换的?

Android handler详解(面试百分之100问到)

Android笔记之从源码解析Handler中ThreadLocal的作用以及IntentService解析