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