美文网首页
关于Handler面试题常问问题

关于Handler面试题常问问题

作者: 陈科比大宝贝儿 | 来源:发表于2020-09-05 18:31 被阅读0次
1. 一个线程可以创建几个Handler?创建Looper的两种方式?

一个线程可以创建多个Handler。
一般是在主线程中实现一个Handler,然后在子线程中使用它。

class HandlerActivity: AppCompatActivity() {

   private val mHandler = MyHandler()

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       // 在子线程中通过自定义的 Handler 发消息
       thread {
           mHandler.sendEmptyMessageDelayed(1, 1000)
       }
   }

   // 自定义一个 Handler
   class MyHandler: Handler() {
       override fun handleMessage(msg: Message) {
           Log.i("HandlerActivity", "主线程:handleMessage: ${msg.what}")
       }
   }
}

或者有时候需要在子线程中创建运行在主线程中的Handler

class HandlerActivity: AppCompatActivity() {
    private var mHandler: Handler? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        thread {
            //获得main looper 运行在主线程
            mHandler = MyHandler(Looper.getMainLooper())
            mHandler!!.sendEmptyMessageDelayed(1, 1000)
        }
    }
     // 自定义一个 Handler
    class MyHandler(): Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity", "子线程:handleMessage: ${msg.what}")
        }
    }
}
2. 一个线程有几个looper,几个message,几个messageQueue怎么保证?Looper的工作流程。

一个线程只有一个looper,一个message。
looper在Handler初始化的时候获取looper:mLooper = Looper.myLooper();在这一句中Handler通过Looper.myLooper方法获取到了Looper对象,那我们看看这个Looper.myLooper()方法做了什么事情呢。它是如何返回一个Looper对象的呢?

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

ThreadLocal提供了线程的局部变量,每个线程可以通过set()和get()方法来对这个局部变量进行操作,但是不会和其他线程的局部变量产生冲突,实现了线程的数据隔离,ThreadLocal中填充的变量是属于当前线程的,该变量对于其他线程而言是隔离的。

public static void prepare() {
        prepare(true);
}

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
}

可以看得出,最后调用了是prepare(boolean quitAllowed)方法,而这个方法首先判断,如果sThreadLocal有值,就抛异常,没有值才会塞进去一个值。其实很好理解,就是说prepare方法必须调用但也只能调用一次,不调用没有值,抛异常,调用多次也还抛异常

接下来再看看这行sThreadLocal.set(new Looper(quitAllowed));做了什么吧,它是如何塞进去的呢?

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}

set方法首先获取到了当前的线程,然后获取一个map。这个map是以键值对形式存储内容的。如果获取的map为空,就创建一个map。如果不为空就塞进去值。要注意的是,这里面的key是当前的线程,这里面的value就是Looper。也就是说,线程和Looper是一一对应的。也就是很多人说的Looper和线程绑定了,其实就是以键值对形式存进了一个map中。

3. handler如何延迟发送消息?
4. Looper.loop()方法在主线程死循环,为啥不会造成ANR?

具体的流程是:
1、mainThread中ActivityThread首先创建了一个运行在主线程的Looper,并且把它和主线程进行了绑定。
2、Looper又创建了一个MessageQueue,然后调用Looper.loop方法不断地在主线程中尝试取出Message
3、Looper如果取到了Message,那么就在主线程中调用发送这个Message的Handler的handleMessage方法。
4、我们在主线程或者子线程中通过Looper.getMainLooper为参数创建了一个Handler。
5、在子线程中发送了Message,主线程中的Looper不断循环,终于收到了Message,在主线程中调用了这个Handler的handleMessage方法。

Handler是构成整个Android系统的基础,正是Looper的死循环才让Android程序能够不退出。所有的类似于屏幕刷新,UI互动都是一种事件,通过Handler发送给了Looper来进行分发。整个Android程序可以说就是运行在这个死循环中。

5. Handler为什么会造成内存泄漏?如何避免造成内存泄漏?

内部类持有外部类的对象,handler持有activity的对象,当页面activity关闭时,handler还在发送消息,handler持有activity的对象,导致handler不能及时被回收,所以造成内存泄漏。
为啥其他内部类不会呢?
因为当handler发送消息时,会有耗时操作,并且会利用线程中的looper和messageQueue进行消息发送,looper和messageQueue的生命周期是很长的,和application一样,所以handler不容易被销毁,所以造成内存泄漏。
如何解决?
1.把handler生命成静态内部类,静态内部类不会持有activity,所以不会造成内存泄漏,
2.弱引用(WeakReference):把使用handle的activity设置成弱引用,

        protected MyHandler handler = new MyHandler(this);
        public abstract void handlerMessage1(Message msg);
        public static class MyHandler extends Handler {
          private WeakReference<BaseActivity> weakReference;
          public MyHandler(BaseActivity activity) {
              this.weakReference = new WeakReference<BaseActivity>(activity);
          }
          @Override
          public void handleMessage(Message msg) {
            weakReference.get().handlerMessage1(msg);
        }
    }

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
3.页面销毁时,清空发送的消息:

handler.removeCallbacksAndMessages()
6. 子线程如何创建hander?主线程给子线程的Handler发送消息怎么写?

子线程如果要创建Handler,必须通过Looper.prepare()方法创建Looper,在主线程中ActivityThread已经帮我们创建好了,我们不需要自己去创建,但如果在子线程中创建Handler,要么使用Looper的mainLooper,要么自己调用Looper.prepare()方法创建属于这个线程的looper对象。如下是创建了一个子线程的Looper对象:

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare();  
        mHandler = new Handler() {  
            public void handleMessage(Message msg) {
                //TODO 子线程处理消息
            }
        };
        Looper.loop(); 
    }
}
mHandler.sendMessage()//主线程发送消息
7. 为什么建议使用Message.obtain()来创建Message实例?

提高消息的复用

public static Message obtain() {
   synchronized (sPoolSync) {
       if (sPool != null) {
           Message m = sPool;
           sPool = m.next;
           m.next = null;
           m.flags = 0; // clear in-use flag
           sPoolSize--;
           return m;
       }
   }
   return new Message();
}

可以看到,obtain方法是将一个Message对象的所有数据清空,然后添加到链表头中。sPool就是个消息池,默认的缓存是50个。
Looper在分发结束以后,会将用完的消息回收掉,并添加到回收池里。

8. 为什么子线程中不可以直接new Handler()而主线程中可以?

如果要创建Handler,必须通过Looper.prepare()方法创建Looper,在主线程中ActivityThread已经帮我们创建好了,我们不需要自己去创建,但如果在子线程中创建Handler,要么使用Looper的mainLooper,要么自己调用Looper.prepare()方法创建属于这个线程的looper对象。

9. 请描述MessageQueue的数据结构和工作流程。

按时间先后顺序排列的单链表,在Handler的构造方法中MessageQueue被赋值。最后发送消息都调用的是MessageQueue的queue.enqueueMessage(msg, uptimeMillis)方法。现在我们已经拿到了queue,然后在这个单链表中进行发消息。

// MessageQueue.java
//省略部分代码
boolean enqueueMessage(Message msg, long when) {

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;

        //【1】拿到队列头部
        Message p = mMessages;
        boolean needWake;

        //【2】如果消息不需要延时,或者消息的执行时间比头部消息早,插到队列头部
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            //【3】消息插到队列中间
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

可以看到,消息队列是一个根据消息【执行时间先后】连接起来的单向链表。想要获取可执行的消息,只需要遍历这个列表,对比当前时间与消息的执行时间,就知道消息是否需要执行了。好了

相关文章

  • 关于Handler面试题常问问题

    1. 一个线程可以创建几个Handler?创建Looper的两种方式? 一个线程可以创建多个Handler。一般...

  • Handler

    Handler 简单使用Handler Looper MessageQuene 源码解析面试题 Handler...

  • iOS 面试收录

    收录前言:网上收录iOS 面试中可能会遇到的问题 iOS面试题-面试常问问题(一) include、#import...

  • Android超实用最全面试大纲(二)

    文章目录: Handler面试题 AsyncTask面试题 HandlerThread面试题 IntentServ...

  • Android最全面试大纲(二)

    文章目录: Handler面试题 AsyncTask面试题 HandlerThread面试题 IntentServ...

  • Handler面试题总结

    面试题总结 Handler是一个比较重要的东西,所以把网上发的Handler中的面试题总结了一下,这些面试题没问题...

  • iOS面试题常问问题

    第一题:运行时机制的原理和运用场景? 原理:runtime 运行时,OC就是基于此开发和支持运行的。例如消息发送机...

  • 安卓面试题 进阶篇

    关于安卓面试题部分目前整理了两篇: 基础篇 进阶篇 Handler、Looper、MessageQueue构成的安...

  • Android 面试复习资料

    Android面试题总结 1.handler 1.handelr机制2.子线程创建handler需要注意启动loo...

  • Handler机制整理

    Handler机制整理目录介绍1.关于handler消息机制图2.关于handler基本介绍3.使用handler...

网友评论

      本文标题:关于Handler面试题常问问题

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