Handler面试那些事

1、子线程到主线程通信都有哪些方式?子线程到主线程通信的原理?

这个是 Android 特有的概念。线程间的通信方式:eventbus、rxjava、livedata。然而,这些方式的底层原理都是基于 handler,所以,下面我将为大家讲解 handler 通信原理。

handler调度流程:

子线程: handler.sendMessage(msg) =》 handler.enqueueMessage =》MessageQueue.enqueueMessage()
主线程:Looper.loop=》queue.next()=》handler.handleMessage()

handler的核心原理图:

Handler面试那些事

两个重要类的图解: ​

Handler面试那些事

2、handler内存泄漏的原因是什么? 

Handler面试那些事

JVM 垃圾回收机制:GCroot 回收机制

持有链: static sThreadLocal -》 mLooper -》MessageQueue -》msg -》handler -》 activity

解决方法:打破持有链

3、子线程中如何创建handler?

  • 写法一:直接在子线程创建 handler,错误
new Thread(
                () -> {
                    Handler mHandler = new Handler(new Handler.Callback() {
                        @Override
                        public boolean handleMessage(@NonNull Message msg) {
                            return false;
                        }
                    });

                    mHandler.sendMessage(new Message());
                }
).start();

报错展示:

Process: com.wust.empty02, PID: 25002
    java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:207)
        at android.os.Handler.<init>(Handler.java:133)
        at com.wust.empty02.MainActivity.lambda$onCreate$0$MainActivity(MainActivity.java:20)
        at com.wust.empty02.MainActivity$$ExternalSyntheticLambda0.run(Unknown Source:2)
        at java.lang.Thread.run(Thread.java:919)

报错源码位置:

Handler面试那些事

  • 写法二:正确写法
new Thread(
                () -> {
                    //创建该线程的 looper
                    Looper.prepare();
                    //创建该线程的 handler
                    Handler mHandler = new Handler(new Handler.Callback() {
                        @Override
                        public boolean handleMessage(@NonNull Message msg) {
                            return false;
                        }
                    });
                    //looper开启循环
                    Looper.loop();
                    
                }
).start();

 这种写法有一个缺点:这样的话 该线程就只能创建一个handler。

  • 写法三:通过  public Handler(@NonNull Looper looper) 该构造方法进行 handler 创建
package com.wust.empty02;


import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MyHandlerThread t = new MyHandlerThread();
        t.start();
        try {
            //这句话如果去掉 就会导致 t.getmLooper() 为空,因为你无法保证 Handler handler = new Handler(t.getmLooper()); 在 t 线程 run 之后执行
            Thread.sleep(1000*3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Handler handler = new Handler(t.getmLooper());
    }
}

class MyHandlerThread extends Thread {
    private Looper mLooper;

    @Override
    public void run() {
        Looper.prepare();
        this.mLooper = Looper.myLooper();
        Looper.loop();
    }

    public Looper getmLooper() {
        return mLooper;
    }
}

从上面代码的注释 就可以很明显的看出其缺点之所在了。无法保证 t.getmLooper() 在 线程 run 方法之后执行。

  • 方法四:使用 HandlerThread 创建线程
HandlerThread t = new HandlerThread("wustyq");
t.start();
Handler handler = new Handler(t.getLooper());

系统的这个 HandlerThread 中的 run 方法为什么就能保证在 t.getLooper() 之前执行呢??关键代码 + 关键思想如下:

Handler面试那些事

为什么要用  synchronized ? 为了解决并发操作 =》 即 run 和  getLooper 只能同时执行一个。

为什么要用  notifyAll() ?因为有可能多个线程调用了wait()方法进行等待。

为什么要用 while?不用 if?因为这个 wait() 可能由别的线程 notify() 唤醒。所以要使用while反复检测。

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

quit:loop退出。防止内存泄漏。不会被停止。

5、handler如何处理发送延时消息?

会调用底层 Linux =》 epoll 完成等待

6、我们使用Message时应该如何创建它?

obtain()

上一篇:Handler工作原理之学习Handler的用法及原理笔记(一)


下一篇:HandlerThread源码分析,androidjetpack视频