0°

Android Handler面试分享

从源码解释Handler常见面试问题

Tip:问题均从网上收集,参照了部分解答,在此谢过。

Q1:消息机制Hander是什么?有什么作用?有哪些要素?流程是怎样的?

1、Handler是什么?

是Android中一套在应用内使用的消息机制。

2、有什么作用

通过该机制,我们得以方便的发送消息以及收消息,我们常见的UI更新机制也是基于Handler运行。

3、有哪些要素

Handler的核心有这“三大件”:

Handler:提供给使用者的入口(发送、收消息)

MessageQueue:消息(Message)的队列,存储消息顺序

Looper:消息分发的驱动器,loop()一直在轮询(while(true))是否有消息,并分发消息

4、流程是怎么样的?

使用知乎大佬的图解释一下:

其他元素的职责在上面介绍过,这里说下Message的分类大概有哪些:

Message:消息,分为硬件产生的消息(如:按键、触摸、绘制等)和软件产生的消息

事实上,在整个消息循环的流程中,并不只有Java层参与,很多重要的工作都是在C++层来完成的。我们来看下这些类的调用关系。(copy from :https://zhuanlan.zhihu.com/p/38373877

注:虚线表示关联关系,实线表示调用关系。

在这些类中MessageQueue是Java层与C++层维系的桥梁,MessageQueue与Looper相关功能都通过MessageQueue的Native方法来完成,而其他虚线连接的类只有关联关系,并没有直接调用的关系,它们发生关联的桥梁是MessageQueue。

Q2:为什么系统不建议在子线程访问UI?

避免多线程更新造成的一系列问题。

Q3:一个Thread可以有几个Looper?几个Handler?

1、一个Thread只有一个Looper,这个是通过ThreadLocal来保证的。

2、可以有多个Hander,Handler和Looper的关系是多对一的。

Q4:如何将一个Thread线程变成Looper线程?Looper线程有哪些特点?

调用Loop.prepareMainLooper,使线程的Looper变成主线程。

调用Looper.prepare(),就可以为当前线程创建一个Looper副本。

Q5:可以在子线程直接new一个Handler吗?那该怎么做?

不可以的,因为在子线程,所以这个Handler一定需要指定由哪个Looper分发消息;

所以在创建之前一定需要调用Looper.prepare()方法。

主线程在ActivityThread已经调用了,创建了mainLooper。

Q6:Message可以如何创建?哪种效果更好,为什么?

Message创建的时候android官方提倡使用Handler.obtainMessage()。

因为Message中保存了一个静态的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();
}

什么时候加入到对象链表中呢?

public void recycle() {
  if (isInUse()) {
    if (gCheckRecycle) {
      throw new IllegalStateException("This message cannot be recycled because it "
          + "is still in use.");
   }
    return;
 }
  recycleUnchecked();
}

消息使用完成调用recycle就可以回收该对象。

Q7:这里的ThreadLocal有什么作用?

ThreadLocal为每个线程保存了一个Looper副本。

ThreadLocal可以看这里:https://www.cnblogs.com/jasongj/p/8079718.html

Q8:主线程中Looper的轮询死循环为何没有阻塞主线程?

主线程中的Looper是保证应用一直运行的关键,我们常说的ANR实际上是占用主线程时间过长。

ActivityThread中初始化MainLooper

public static void main(String[] args) {
  Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
  SamplingProfilerIntegration.start();
  //...
  Looper.prepareMainLooper();
​
  ActivityThread thread = new ActivityThread();
  thread.attach(false);
​
  if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
 }
​
  if (false) {
    Looper.myLooper().setMessageLogging(new
        LogPrinter(Log.DEBUG, "ActivityThread"));
 }
​
  // End of event ActivityThreadMain.
  Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  Looper.loop();//mainLooper一直循环,保证应用进程一直存活
​
  throw new RuntimeException("Main thread loop unexpectedly exited");
}

Q9:使用Hanlder的postDealy()后消息队列会发生什么变化?

调用了postDealy之后,MessageQueue会设置Message.when(保存消息应该什么时候执行)的值,

通过该值,MessageQueue会该消息插入到自己保存的消息队列(链表实现)中。

boolean enqueueMessage(Message msg, long when) {
  if (msg.target == null) {
    throw new IllegalArgumentException("Message must have a target.");
 }
  if (msg.isInUse()) {
    throw new IllegalStateException(msg + " This message is already in use.");
 }
​
  synchronized (this) {
    if (mQuitting) {
      IllegalStateException e = new IllegalStateException(
          msg.target + " sending message to a Handler on a dead thread");
      Log.w(TAG, e.getMessage(), e);
      msg.recycle();
      return false;
   }
​
    msg.markInUse();
    msg.when = when;//设置应该什么时候执行
    Message p = mMessages;
    boolean needWake;
    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 {
      // Inserted within the middle of the queue. Usually we don't have to wake
      // up the event queue unless there is a barrier at the head of the queue
      // and the message is the earliest asynchronous message in the queue.
      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;
   }
​
    // We can assume mPtr != 0 because mQuitting is false.
    if (needWake) {
      nativeWake(mPtr);
   }
 }
  return true;
}

Q10:如何解决Handler导致的泄漏问题

Handler导致泄漏的根本原因是Handler创建的时候为应用的内部类,持有了外部类的引用,

这个时候遇到有消息执行比较久(如:网络请求等),就会导致已经退出Activity了但是其引用还被Handler持有。

解决方案有两个建议:

1、实例化Handler的时候尽量创建一个静态内部类

2、保存外部Context的引用的时候使用弱引用

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
axure商城
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论