面试官还问 Handler?那我要给你讲个故事
2.1.myLooper().mQueue.next(); //循环获取 MessageQueue 中的消息
nativePollOnce(); //阻塞队列
native -> pollInner() //底层阻塞实现
native -> epoll_wait();
2.2.Handler.dispatchMessage(msg);//消息分发
myLooper().mQueue.next()实现原理
通过 myLooper().mQueue.next() 循环获取 MessageQueue 中的消息,如遇到同步屏障 则优先处理异步消息.
同步屏障即为用 Message.postSyncBarrier()发送的消息,该消息的 target 没有绑定 Handler。在 Hnandler 中异步消息优先级高于同步消息。
可通过创建 new Handler(true)发送异步消息。ViewRootImpl.scheduleTraversals 方法就使用了同步屏障,保证 UI 绘制优先执行。
Handler.dispatchMessage(msg)实现原理
优先回调 msg.callback。
其次回调 handler 构造函数中的 callback。
最后回调 handler handleMessage()。
**Hander 发
送消息的流程**
1.Handler handler = new Handler();//初始化 Handler
1.Handler.mLooper = Looper.myLooper();//获取当前线程 Looper。
2.Handler.mQueue = mLooper.mQueue;//获取 Looper 绑定的 MessageQueue 对象。
2.handler.post(Runnable);//发送消息
sendMessageDelayed(Message msg, long delayMillis);
sendMessageAtTime(Message msg, long uptimeMillis);
Handler.enqueueMessage();//Message.target 赋值为 this。
Handler.mQueue.enqueueMessage();//添加消息到 MessageQueue。
MessageQueue.enqueueMessage()方法实现原理
如果消息队列被放弃,则抛出异常。
如果当前插入消息是即时消息,则将这个消息作为新的头部元素,并将此消息的 next 指向旧的头部元素,并通过 needWake 唤醒 Looper 线程。
如果消息为异步消息则通过 Message.when 长短插入到队列对应位置,不唤醒 Looper 线程。
接下来该面试官问了
经常有人问为什么主线程的 Looper 阻塞不会导致 ANR?
首先我们得知道 ANR 是主线程 5 秒内没有响应。
什么叫 5 秒没有响应呢?Android 系统中所有的操作均通过 Handler 添加事件到事件队列,Looper 循环去队列去取事件进行执行。如果主线程事件反馈超过了 5 秒则提示 ANR。
如果没有事件进来,基于 Linux pipe/epoll 机制会阻塞 loop 方法中的 queue.next()中的 nativePollOnce()不会报 ANR。
对于以上的例子来说,ANR 可以理解为用户进行即时点餐后没按时上菜(当然未按时上菜的原因很多,可能做的慢(耗时操作 IO 等),也可能厨具被占用(死锁),还有可能厨师不够多(CPU 性能差)等等。。。),顾客发起了投诉,或差评。但如果约定时间还没到,或者当前没人点餐,是不会有差评或投诉产生的,因此也不会产生 ANR。
以上的所有内容均围绕原理,源码,接下来我们举几个特殊场景的例子
1.为什么子线程不能直接 new Handler()?
new Thread(new Runnable() {
@Override
public void run() {
Handler handler = new Handler();
}
}).start();
以上代码会报以下下错误
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:119)
at com.example.test.MainActivity$2.run(MainActivity.java:21)
at java.lang.Thread.run(Thread.java:919)
通过报错提示 “not called Looper.prepare()” 可以看出提示没有调用 Looper.prepare(),至于为什么我们还得看下源码
public Handler(Callback callback, boolean async) {
...省略若干代码
//通过 Looper.myLooper()获取了 mLooper 对象,如果 mLooper ==null 则抛异常
mLooper = Looper.myLooper();
if (mLooper == null) {
//可以看到异常就是从这报出去的
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
" that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public static @Nullable Looper myLooper() {
//而 myLooper()是通过 sThreadLocal.get()获取的,那 sThreadLocal 又是个什么鬼?
return sThreadLocal.get();
}
//可以看到 sThreadLocal 是一个 ThreadLocal<Looper>对象,那 ThreadLocal 值从哪赋值的?
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//sThreadLocal 的值就是在这个方法里赋值的
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));
}
通过以上的源码注释,完全明白了报错的日志的意思,报错日志提示我们没有调用 Looper.prepare()方法,而 Looper.prepare()方法就是 sThreadLocal 赋值的位置。
那子线程怎么创建 Handler 呢?只需在 new Handler()之前调用下 Looper.prepare()即可。
2. 为什么主线程可以直接 new Handler?
子线程直接 new Handler 会报错,主线程为什么就不会报错呢?主线程我也没有调用 Looper.prepare()啊?那么我们还得看下源码了。
//我们看下 ActivityMain 的入口 main 方法,调用了 Looper.prepareMainLooper();
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
}
//看到这一下就明白了,原来主线程在启动的时候默认就调用了 prepareMainLooper(),而在这个方法中调用了 prepare()。
//提前将 sThreadLocal 进行赋值了。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
如果有对 ThreadLocal 对象不明白的可看我这篇 ThreadLocal原理解析
3.Handler 为什么会内存泄露?
首先普及下什么叫内存泄露,当一个对象不再使用本该被回收时,但另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这种情况下就产生了内存泄漏。
我们举一个 Handler 内存泄露的场景。
public class HandlerActivity extends AppCompatActivity {
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
handler.sendEmptyMessageDelayed(1,5000);
}
}
当以上代码写完后编译器立马会报黄并提示 “this handler should be static or leaks might occur...Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.”
大致意思就说 “由于这个处理程序被声明为一个内部类,它可以防止外部类被垃圾回收。如果处理程序正在对主线程以外的线程使用 Looper 或 MessageQueue,则不存在问题。如果处理程序正在使用主线程的 Looper 或 MessageQueue,则需要修复处理程序声明,如下所示:将处理程序声明为静态类;并且通过 WeakReference 引用外部类”。
说了这么一大堆,简单意思就是说以上这种写法,默认会引用 HandlerActivity,当 HandlerActivity 被 finish 的时候,可能 Handler 还在执行不能会回收,同时由于 Handler 隐式引用了 HandlerActivity,导致了 HandlerActivity 也不能被回收,所以内存泄露了。
我们来写一种正确的写法
public class HandlerActivity extends AppCompatActivity {
MyHandler handler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
handler.sendEmptyMessageDelayed(1,5000);
}
private static class MyHandler extends Handler{
private WeakReference<HandlerActivity> activityWeakReference;
评论