1. Handler 的创建
创建Handler 依赖Looper, 而创建Looper实例时,也会创建Looper要管理和使用的 MessageQueue, Looper 存储在ThreadLocal中, 具体步骤:
- 先尝试获取当前线程的 Looper, 若获取失败就抛异常,故在此之前应先创建 Looper
- Looper.prepare() -> 调用 new Looper() 创建实例(在构造中会创建一个 MessageQueue),并将该实例放到 ThreadLocal(Looper 的静态常量,全局只有一个) 中
- ThreadLocal#set()
- Handler 的构造中 给 mLooper, mQueue 赋值
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback) {
this(callback, false);
}
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
// 可能造成内存泄漏的有:
// 非静态的 匿名内部类 、 成员内部类、 方法内定义的内部类
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
// 获取本线程的 Looper,
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
// 通过Looper获取 MessageQueue
mQueue = mLooper.mQueue;
// 通过callback参数可不用以扩展Handler子类的方式 定义 Handler收到消息后如何处理消息
mCallback = callback;
mAsynchronous = async;
}
// Looper.class
public static @Nullable Looper myLooper() {
// static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
return sThreadLocal.get(); // 获取当前线程的 Looper
}
// 为线程创建 Looper 循环
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));
}
private Looper(boolean quitAllowed) {
// 创建Looper要管理的消息队列
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
也可使用其他线程的 Looper 创建 Handler实例,处理消息的逻辑在Looper所属的线程
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
2. 消息的发送
// Handler.class
// 发送 Runnable 类型的消息
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
// 将 Runnable 封装到一个 Message 中, 即 Message 的 callback 字段
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
// 真正发送消息的方法
// 系统开始运行到当前所经历的时间 + 要延迟的时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// post() send() 发送消息最终都调用此方法
// 将 msg 插入到消息队列中
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
// 入队
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// target 引用指向当前 Handler
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 让 MessageQueue 完成入队操作
return queue.enqueueMessage(msg, uptimeMillis);
}
3. MessageQueue 完成消息的入队
MessageQueue 的结构可看成一个链表:
执行消息插入时,先看链表是否为空,若为空则当前新消息的节点作为链表的头结点:
若不为空,在链表中找到第一个消息处理时刻晚于当前消息的节点,将当前消息节点插入到链表中
// MessageQueue.class
boolean enqueueMessage(Message msg, long when) {
// Handler
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.");
}
// 消息的入队和出队 使用了 MessageQueue 的对象锁
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) {
// 可看做唤醒 mPtr 指向的 C++层的 MessageQueue
nativeWake(mPtr);
}
}
return true;
}
4. Looper 从MessageQueue中取消息
Looper 自从创建完毕且调用了 loop() 方法后,就开启了一个死循环,一直尝试从 MessageQueue 中取消息
Looper # loop() 方法主要就执行两个任务:
- 通过 MessageQueue#next() 取 MessageQueue 中的 消息 (有就取出来,没有就阻塞)
- 将取出的 Message 用它的target字段引用的 Handler 处理
// Looper.class
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); // MessageQueue 阻塞式的取队列中的消息
if (msg == null) {
// 返回空表明 MessageQuue 正在退出
return;
}
... // 略去不影响流程的
try {
msg.target.dispatchMessage(msg);
...
}
...
msg.recycleUnchecked();
}
}
// MessageQueue.class
@UnsupportedAppUsage
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
// mPtr是 MessageQueue 的一个 long型成员变量,关联一个在C++层的MessageQueue,阻塞操作就是通过它做的,当 MessageQueue 被 quit() 之后, mPtr就变为0
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
... // 用于存储 下一次从队列中取消息需要延迟的时间
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 用于阻塞 MessageQueue 的阻塞方法
// 当 nextPollTimeoutMillis = -1 时,会一直阻塞
// 当 nextPollTimeoutMillis = 0 时,会立即返回,不会阻塞
// 当 nextPollTimeoutMillis > 0 时,最长阻塞 nextPollTimeoutMillis 毫秒, 若期间有程序唤醒会立即返回
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// msg.target == null 时,表示此消息是消息屏障(通过postSyncBarrier()发送的)
// 当遇到消息屏障时会循环找下一个消息,一直找到第一个异步消息(如果有的话)
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
// 找到第一个异步消息
if (msg != null) {
if (now < msg.when) {
// 找到第一个异步消息,但是当前还没到该消息要被处理的时刻, 就设置一个延时
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 当前到了消息要被处理的时刻
// Got a message.
mBlocked = false;
// msg 为待处理目标消息, prevMsg 为 msg 的在队列中的前一个消息
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
// mMessages 为队列中的第一个消息
mMessages = msg.next;
}
// 取出 msg
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
}
...
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
5. 处理消息
// Handler.class
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
// Message 的 callback 字段 封装了 通过 post() 发送的 Runnable
if (msg.callback != null) {
handleCallback(msg);
} else {
// 构造 Handler 时传入了 自定义的 Callback 的实例
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 扩展Handler重写 handleMessage() 方法
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
问题
1. Handler 可能引起的内存泄漏
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
// 可能造成内存泄漏的有:
// 非静态的 匿名内部类 、 成员内部类、 方法内定义的内部类
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
1.1 内部类简介
对于至于某个类有关,而与其他类没什么关系的类可定义在某个类的内部。 内部类是编译器层级的概念,虚拟机不知道它的存在,编译器会把内部类编译成一个独立的文件。
内部类有以下几种:
- 静态内部类:
a. 它可访问外部类的 类方法 和 类变量 (即使是私有的)
实现原理:
编译器将Outer(外部类) 和 Inner(内部类) 编译成两个独立的类;若在定义时内部类访问了外部类的类私有变量,外部类就会多一个针对该变量的静态访问方法
,供内部类编译生成的类使用
b. 在外部类中可直接使用(类似其他普通类)
c. 在外部类的外部使用时Outer.StaticInner staticInner = new Outer.StaticInner();
- 成员内部类
a. 可访问外部类的 类属性、类方法、实例变量、实例方法(即使是私有的)
实现原理:
编译器将内部类和外部类编译成两个独立的类文件:Outer.class 和 Outer$Inner.class, 在后者的构造方法中会传入外部类对象(即 Outer),而在生成的Outer.class 中针对在内部类中访问的私有属性会提供静态公有(default)访问方法(参数为外部类对象), 内部类有了外部类对象就可根据这些方法访问外部类的实例方法和属性。即内部类持有外部类的一个对象,外部类提供自身的实例变量和方法的传入参数为外部类对象的访问方法
.
b. 在外部类中可直接使用(类似其他普通类)
c. 在外部类的外部使用时:成员内部类的对象总是与一个外部类对象关联的
,所以需要先有外部类对象,才能创建内部类对象:Outer.Inner inner = new Outer().new Inner();
(当然亦可拆开写,一个对象后可跟 new 来新建一个对象)
d.不能有静态的方法和属性
,可能是因为静态方法和属性作为类的属性和方法是可直接使用的,但内部类一般不能脱离于外部类单独使用- 方法内部类 (定义在一个方法体中)
a. 若是在外部类的实例方法中定义的,则可访问外部类的 实例方法和属性,也可访问外部类的静态方法和属性
b. 若是在外部类的静态方法中定义的,则只能访问外部类的类方法和属性
c. 只能在方法的内部使用
d. 可访问方法的参数和局部变量,但它们必须被声明为final
实现原理: 类似普通内部类 同样是持有外部类实例的引用- 匿名内部类
a. 它没有名字,在创建对象时,定义类。
b. 它没有构造方法,但是可有初始化代码块(只能有一份 合理)。
c. 能访问外部类的所有的方法和属性
实现原理:
如: 在Outer.java 中使用匿名内部类,那么编译器会生成两个类文件: Outer.class 和 Outer$1.class
在Outer$1.class 的构造方法中会传入一个Outer对象,Outer.class 中也会声明通过 Outer对象访问自身属性和方法的 静态default方法供 Outer$1.class 用来访问外部类的属性和方法
总之: 非静态的内部类实例 会 持有其外部类的实例的引用
1.2 引起内存泄漏
一个例子: 在Activity中直接使用 Handler handler = new Handler() { ....... // 通过handleMessage() 定义如何处理消息}
就可能引发内存泄漏
因为: Handler 对象持有了外部的 Activity 对象的引用, 而通过Handler 发送了Message到 MessageQueue 中,这个 Message 的target 字段就持有这个 Handler, 所以如果Message 在 MessageQueue 中存在挺长时间,Activity 在执行了 onDestroy() 之后也还不能被销毁
1.3 避免引起内存泄漏的方法
- 扩展Handler子类时,使用静态内部类 或者 单独在一个文件中定义
- 在Handler的子类中需要使用Activity 的属性时,使用弱引用处理
即在 Handler 子类的构造方法中传入 Activity 实例,然后用弱引用引用它:
WeakReference<HandlerActivity> mActivty;
1.4 Java 中的几种引用
- 强引用:最普通的常见引用,只要有这种引用引用某实例,它就不能被回收
- 软引用:通常的垃圾回收不会回收它们,但当马上要发生OOM时,会回收软引用引用的对象,要是回收了这些内存还不够,就发生OOM
- 弱引用:弱引用不会影响垃圾收集,若一个对象只有弱引用引用它,并且通过其他的判断,它也该回收了,它就会被回收
- 虚引用:虚引用也不会影响垃圾收集,只是当它应用的实例被回收后,系统会受到一个通知
2. 关于 ThreadLocal
线程本地变量,即
每个线程都有一个对这个变量的独有拷贝
:
对于一个用ThreadLocal定义的变量,如ThreadLocal<Integer> local = new ThreadLocal<>();
每个线程对它都有一份自己的值,且各自互不影响
实现原理:
每个线程都有一个 ThreadLocalMap,存储的是类似 HashMap 的 Entry结构,存放的键值对是 以 当前 ThreadLocal 对象为 key, 以当前线程下的 ThreadLocal 的值为value
public class ThreadLocal<T> { // T 为 ThreadLocal 实际存储的变量的类型
// Creates a thread local variable.
public ThreadLocal() {
}
public T get() {
Thread t = Thread.currentThread();
// 获取当前线程的 threadLocalMap
ThreadLocalMap map = getMap(t);
// 能获取当前 线程的 ThreadLocalMap
if (map != null) {
// 看是否有当前ThreadLocal 对应的节点(封装键值对的实例)
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 当前ThreadLocal 没在当前线程的 ThreadLocalMap 中 或 ThreadLocalMap 为空
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue(); // 返回 null, 即普通对象的空值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 能获取到本线程的 ThreadLocalMap, 就将当前键值对设置到map中
if (map != null)
map.set(this, value);
// 不能就创建 ThreadLocalMap,并设置值
else
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
...
}
3. 关于为何 Looper.loop 为何不会引起ANR
3.1 ANR 简介
Application Not Responding, 即应用程序无响应。Android中,ActivityManagerService 和 WindowManagerService 会监测应用程序的响应时间,若应用的
主线程
在特定的时间内
对输入事件
或特定操作
没有处理完毕就会产生ANR。对因没处理完输入事件而产生的ANR,系统会弹出对话框,提示用户是等待还是结束应用。
3.2 产生ANR的条件
- 主线程
- 超时时间
- 输入事件 或 特定操作
只有应用程序的主线程才可能产生ANR; 产生ANR的上下文(Activity/Service)不同,规定的超时时间也不同; 输入事件指的是按键 触屏等设备输入事件, 特定操作 Service、 BoradcastReceiver 各个生命周期函数
3.3 具体的ANR类型
主线程对输入事件在5s内没处理完毕:
- Window:每个Activity都有一个PhoneWindow, 它表示一个能够显示的窗口,能接收系统分发给它的各种输入事件(分发给DecorView)
- InputDispatcher: 运行在系统进程中,它负责将屏幕收到的输入事件分发给当前活动的窗口,即 Window
- InputChannel: Window 和 InputDispatcher 运行在不同的进程中, InputChannel 就是让InputDispatcher 用来传递事件给Window的
此类 ANR 的产生流程:
ji
当应用程序的 Window 处于 Active 状态且能够接收输入事件时,系统底层上报的输入事件就会被 InputDispatcher 分发给当前应用,应用程序的主线程通过 InputChannel 读入输入事件交给视图界面处理。在这个处理的过程中,InputDispatcher 会不断的检测这个处理过程是否超时,若超时通过一系列回调,调用 WMS 的notifyANR(),最终调用AMS中的 Handler 发送 SHOW_NOT_RESPONDING_MSG 类型的消息,从而让Activity弹出对话框, 另外也会输出log
主线程执行BroadcastReceiver 的 onReceive() 方法在10s内没结束:
这种情况的 ANR不会弹对话框,只是输出log
主线程执行Service各个生命周期的函数在20s内没结束:
也是只有log, 不弹对话框
Looper.loop() 涉及不到系统进程,也涉及不到 AMS/WMS, 也就不会产生ANR了