Handler 常见的面试问题?

一、前言:

主要介绍 Looper 常见的面试题,包括以下几个问题:

handler 题目.jpg
  1. 先说一下造成ANR的原因:
  • 造成ANR的原因一般有两种:
    1. 在 5s 内没有相响应事件的处理,例如:键盘按下、屏幕触摸等(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)
    2. 广播没有在 10s 内执行完毕。
  • 为了避免ANR异常,android使用了Handler消息处理机制。让耗时操作在子线程运行。

2、介绍一下Handler

  • 答:Handler机制是Android系统中用于实现线程之间通信的一种机制。它包含了一个MessageQueue对象和一个Looper对象,用来处理MessageQueue中的消息。
  • 当我们需要在一个线程中更新UI或执行一些耗时操作时,我们可以使用Handler机制。我们可以在主线程中创建一个Handler对象,并将其关联到主线程的Looper对象上。然后,在其他线程中,我们可以通过Handler.post()方法或Handler.sendMessage()方法向主线程发送消息,这些消息会被加入到主线程的MessageQueue中。主线程通过Looper.loop()方法不断地从MessageQueue中获取消息,并根据消息类型执行相应的操作。
  • 通过这种方式,我们可以在其他线程中发送消息,然后在主线程中处理这些消息,从而实现线程之间的通信。这种机制也被广泛应用于Android应用程序中的异步任务、定时器等场景。

二、解决问题:

1. 一个线程有几个 Handler?

答:一个线程内创建 n 个 handler,就有 n个 Handler。

2. 一个线程有几个 Looper?如何保证?

答:一个线程有 1 个 Looper;通过 ThreadLocal 来保证一个线程只有一个 Looper。

3. Handler 内存泄漏原因?为什么其他内部类没有说过这个问题?

答:内部类持有外部类的引用(默认持有了外部类的 this);原理不同(MessageQueue->message->handler->activity)。

4.为何主线程可以 new Handler?如果在子线程中new Handler要做什么准备?

答:在主线程中,已经默认帮我们创建了Looper.prepare()和Looper.loop()方法,所以可以直接拿来使用;子线程是默认没有 Looper 的,如果需要使用 Handler 就必须为线程创建 Looper。我们经常提到的主线程,也就是 UI 线程,它就是 ActivityThread,ActivityThread被创建时就会初始化 Looper,这也就是在主线程中默认可以使用 Handler 的原因。

子线程使用 Handler方法如下:

 //1.创建Looper对象
  Looper.prepare();
 //2.实例化Handler
 handler =new Handler(){
 public void handleMessage(Message msg) {
     //接受主线程发送过来的消息
     super.handleMessage(msg);
     String info =(String) msg.obj;
};
};
 //3.循环读取MessageQueue
 Looper.loop();

下方是发送消息:

 public void sendMessage(View v){
        if(handler!=null){
            Message msg =Message.obtain();
            msg.obj ="来自主线程的数据";
            handler.sendMessage(msg);
        }
    }

5.子线程中维护的 Looper,消息队列无消息的时候处理方案是什么?有什么用?

答:无消息的时候,会调用 quit()或 quitSafely()方法,删除所有消息,释放内存,释放线程。

  • quit():会直接退出 Looper;
  • quitSafely():只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全退出。
  • Looper 退出后,通过 Handler 发送消息会失败,这个时候 Handler 的 send 方法会返回 false。

子线程的三个步骤如下:

//1.创建Looper对象
 Looper.prepare();
//2.循环读取MessageQueue
 Looper.loop();
//3.释放内存
 quit();

阻塞函数:

  Message msg = queue.next();
  if(msg == null){
   return;
 }

注意:主线程不允许调用 quit()方法。

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

答:Synchronized 保证发消息和取消息的安全性;handler delay 的消失,时间不完全准确;

注意:Synchronized修饰方法、静态方法和代码块;

7. 我们使用Mesage 时应该如何创建它?

答: 使用 obtain() 方法,内存复用,防止抖动。

发送消息:

 public void sendMessage(View v){
        if(handler!=null){
            Message msg =Message.obtain();
            msg.obj ="来自主线程的数据";
            handler.sendMessage(msg);
        }
    }

8.使用 Handler 的 postDelay 后消息对列会有什么变化?

答:消息对列为空,消息不会执行,计算等待时间,然后进行消息队列操作。

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

  • 在处理消息的时候使用了Looper.loop()方法,并且在该方法中进入了一个死循环,同时Looper.loop()方法是在主线程中调用的,那么为什么没有造成阻塞呢?

看看Looper.loop()方法无限循环部分的代码

while (true) {
   //取出消息队列的消息,可能会阻塞
   Message msg = queue.next(); // might block
   ...
   //解析消息,分发消息
   msg.target.dispatchMessage(msg);
   ...
}
  • 为什么这个死循环不会造成ANR异常呢?

答:因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。

10 . ActivityThread中main方法

  • ActivityThread类的注释上可以知道这个类管理着我们平常所说的主线程(UI线程)
  • 首先 ActivityThread 并不是一个 Thread,就只是一个 final 类而已。我们常说的主线程就是从这个类的 main 方法开始,main 方法很简短
public static final void main(String[] args) {
    ...
    //创建Looper和MessageQueue
    Looper.prepareMainLooper();
    ...
    //轮询器开始轮询
    Looper.loop();
    ...
}
  • 简单的来说:ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的程序也就可以退出了。

11. 处理消息handleMessage方法

如下所示

  • 可以看见Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施。
  • 如果某个消息处理时间过长,比如你在onCreate(),onResume()里面处理耗时操作,那么下一次的消息比如用户的点击事件不能处理了,整个循环就会产生卡顿,时间一长就成了ANR。
public void handleMessage(Message msg) {
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
    switch (msg.what) {
        case LAUNCH_ACTIVITY: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
            final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
            r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
            handleLaunchActivity(r, null);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }
        break;
        case RELAUNCH_ACTIVITY: {
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
            ActivityClientRecord r = (ActivityClientRecord) msg.obj;
            handleRelaunchActivity(r);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        }
        break;
        case PAUSE_ACTIVITY:
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
            handlePauseActivity((IBinder) msg.obj, false, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 2) != 0);
            maybeSnapshot();
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            break;
        case PAUSE_ACTIVITY_FINISHING:
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
            handlePauseActivity((IBinder) msg.obj, true, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 1) != 0);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            break;
        ...........
    }
}

12. loop的循环消耗性能吗?

答:主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。

13. 什么是 ThreadLocal?

答:ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则获取不到数据。

14. 系统为什么不允许在子线程中访问 UI 呢?

答:这是因为 Android 的 UI 控件不是线程安全的,如果在多线程中并发访问可能会导致 UI 控件处于不可期的状态。

15. 为什么系统不对 UI 控件的访问加上锁机制呢?

答:首先加上锁机制会让 UI访问的逻辑变得复杂;其次锁机制会降低 UI 访问的效率,因为锁机制会阻塞某些线程的执行。最简单且高效 的方法就是采用单线程模型处理 UI 操作。

16. Handler是什么?

  • 答:Handler机制是Android系统中用于实现线程之间通信的一种机制。它包含了一个MessageQueue对象和一个Looper对象,用来处理MessageQueue中的消息。
  • 当我们需要在一个线程中更新UI或执行一些耗时操作时,我们可以使用Handler机制。我们可以在主线程中创建一个Handler对象,并将其关联到主线程的Looper对象上。然后,在其他线程中,我们可以通过Handler.post()方法或Handler.sendMessage()方法向主线程发送消息,这些消息会被加入到主线程的MessageQueue中。主线程通过Looper.loop()方法不断地从MessageQueue中获取消息,并根据消息类型执行相应的操作。
  • 通过这种方式,我们可以在其他线程中发送消息,然后在主线程中处理这些消息,从而实现线程之间的通信。这种机制也被广泛应用于Android应用程序中的异步任务、定时器等场景。

17. 消息机制是什么?

答:Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程。

18. ThreadLocal工作原理是什么?

答:ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而且只有在指定的线程里才能获取到存储的数据,对于其他线程来说是获取不到的。
消息机制为什么要用到这个呢?因为对于Handler来说,要获取到当前线程的Looper,而Looper的作用域是当前线程,并且不同线程有不同的Looper,这个时候就可以很方便的通过ThreadLocal对Looper进行存取。

19.Handler和Message、MessageQueue、Looper之间的关系?

Message:Handler接收与处理的消息对象

MessageQueue:
消息队列,以先进先出队列形式管理所有的Message,且在初始化Looper对象时会创建一个与之关联的MessageQueue。每个线程最多只有一个MessageQueue。MessageQueue通常都是由Looper来管理,而主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue。其他非主线程,不会自动创建Looper。

Looper:
每个线程只能够一个Looper,管理MessageQueue,不断从中去除Message分发给对应的Handler处理。
通俗一点讲:当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送消息;而我们发送的消息会先到主线程的MessageQueue中进行等待,由Looper按先入先出顺序取出,再根据Message对象的what属性分发给对应的Handler进行处理。

20. 在子线程中创建Handler报错是为什么?

错误信息:
java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
不能再子线程中新建handler,没有呼叫Looper.prepare();,

答:handler构造函数中 需要持有一个looper的对象,如果没有,提示这个错误;
looper的主要作用是与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。然后looper的loop()方法就是不断从MessageQueue中取出消息,交给handler去发送消息。 而子线程是没有默认looper的,所以就会报错了。 解决办法也很简单,我们只需要调用prepare()方法,新建looper对象就好。看一下prepare都干了什么:
Looper.prepare()

21.如何在子线程创建Looper?

Looper.prepare();

22.Looper.loop()在什么情况下会退出?

1、next方法返回的msg == null
2、线程意外终止

23.Android如何保证一个线程最多只能有一个Looper?

1、Looper的构造方法为private,所以不能直接使用其构造方法创建。

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

要想在当前线程创建Looper。需使用Looper的prepare方法,Looper.prepare()。
假设如今要我们来实现Looper.prepare()这种方法,我们该怎么做?我们知道,Android中一个线程最多仅仅能有一个Looper,若在已有Looper的线程中调用Looper.prepare()会抛出RuntimeException(“Only one Looper may be created per thread”)。

2、看一看Android的中Looper的源代码。

public class Looper {

    static final ThreadLocal sThreadLocal = new 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()方法中调用了ThreadLocal的get和set方法。然而整个过程没有加入同步锁,Looper是怎样实现线程安全的?
通过ThreadLocal来保证Looper的唯一。

24.Handler 消息处理流程

图片.png

流程图解析: 相关名词

  • UI线程:就是我们的主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue;
  • Handler:作用就是发送与处理信息,如果希望Handler正常工作,在当前线程中要有一个Looper对象
  • Message:Handler接收与处理的消息对象
  • MessageQueue:消息队列,先进先出管理Message,在初始化Looper对象时会创建一个与之关联的MessageQueue;
  • Looper:每个线程只能够有一个Looper,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理!

简述:

当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送信息;而我们发送的信息会先到主线程的MessageQueue进行等待,由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理!

参考文章:
https://blog.csdn.net/wl1433289703/article/details/95633042

  • 1.技术博客汇总
  • 2.开源项目汇总
  • 3.生活博客汇总
  • 4.其他汇总

————————————————
原文链接:https://blog.csdn.net/m0_37700275/article/details/89419095

你可能感兴趣的:(Handler 常见的面试问题?)