0%

Android消息机制

ThreadLocal

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而且只有在指定的线程里才能获取到存储的数据,对于其他线程来说是获取不到的。ThreadLocal适用于变量在线程间隔离而在方法或类间共享的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static class ThreadLocalMap {
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}

ThreadLocalMap 维护了每个 ThreadLocal 与具体实例的映射,它的每个 Entry 都是对键的弱引用,防止内存泄漏。看下其工作流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;

// ThreadLocal.java
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

ThreadLocalMap是 Thread 类的一个字段,即由 Thread 维护 ThreadLocal 对象与具体实例的映射。读取实例时,线程首先通过getMap(t)方法获取自身的 ThreadLocalMap。获取到 ThreadLocalMap 后,通过map.getEntry(this)方法获取该 ThreadLocal 在当前线程的 ThreadLocalMap 中对应的 Entry。

对于已经不再被使用且已被回收的 ThreadLocal 对象,它在每个线程内对应的实例由于被线程的 ThreadLocalMap 的 Entry 强引用,无法被回收,可能会造成内存泄漏

针对该问题,在 ThreadLocalMap 的 set 方法中会将所有键为 null 的 Entry 的值设置为 null,从而使得该值可被回收。接着会将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收。通过这种方式,ThreadLocal 可防止内存泄漏。

MessageQueue

概述

MessageQueue是消息队列,主要包含两个操作:插入和读取,而读取也包含了删除操作,即每读取一个消息就会从MessageQueue中删除掉这个已读的消息。MessageQueue的实现是通过单链表的数据结构来维护消息列表的(当前对象保留下一个对象的地址引用)。

Message

1
2
3
4
5
6
7
8
9
10
11
12
13
public final class Message implements Parcelable {
public int what;
public Object obj;
long when;
Handler target;
Message next;
Runnable callback;

// 设置在Barrier时是否仍可以不受Barrier的影响被正常处理,如果没有设置Barrier,异步消息就与同步消息没有区别
public void setAsynchronous(boolean async) {
// ...
}
}

Message分类:

  • 同步消息:正常情况下通过Handler发送的Message都属于同步消息,除非在发送的时候指定其是一个异步消息,同步消息会按顺序排列在队列中。
  • 异步消息:一般情况下与同步消息没有区别,只有在设置了同步屏障(barrier)时才有所不同。
  • 屏障消息(Barrier):屏障(Barrier)是一种特殊的Message,它的target为null(只有屏障的target可以为null),并且arg1属性被用作屏障的标识符来区别不同的屏障。屏障的作用是用于拦截队列中同步消息,放行异步消息。

同步异步消息类型由 isAsynchronous 方法得到,通过 MessageQueue.postSyncBarrier() 方法可以发送一个Barrier。当发现遇到barrier后,队列中后续的同步消息会被阻塞,而异步消息则不受barrier的影响,直到通过调用MessageQueue.removeSyncBarrier()释放了指定的barrier。屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。插入普通消息会唤醒消息队列,但是插入屏障不会。

相关源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// MessageQueue.java
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
// 没有为tartget赋值
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;

Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}

public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();

// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}

一般情况下,app会频繁的大量使用Message对象,如果每次都去new一个新对象出来使用,用后再通过GC进行垃圾回收,那很可能会导致频繁的gc,影响程序的性能。因此,android提供了一个Message对象池,当开发者需要一个Message对象时,调用Message.obtain()从这个池子里取一个对象出来使用,这样做可以优化一定的性能,使用的是享元模式。

IdleHandler

IdleHandler可以用来提升性能,主要用在我们希望能够在当前线程消息队列空闲时做些事情(UI线程在显示完成后,如果线程空闲我们就可以提前准备其他内容)的情况下,不过最好不要做耗时操作。具体用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//getMainLooper().myQueue()或者Looper.myQueue()
Looper.myQueue().addIdleHandler(new IdleHandler() {
@Override
public boolean queueIdle() {
// 要处理的事情
// 返回false表示执行过一次后会移除
// 返回true表示一直keep不移除,每次消息队列空闲后都会执行
return false;
}
});

// MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}

enqueueMessage入队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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) {
msg.recycle();
return false;
}

msg.markInUse(); // 标记为in use
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;
// 将队列中的Message按照处理时间先后插入
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;
}

enqueueMessage方法会根据唤醒时间的先后顺序插入,在插入成功返回true,否则返回false。nativeWake 方法会唤醒CPU。

next取下条消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}

int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
// 本地方法, 释放CPU资源,-1表示一直等待
nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// msg.target == null就是同步屏障消息,那么只取异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 下一条消息尚未ready,设置timeout以在ready后唤醒
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
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;
}

// 处理IdleHandler的逻辑
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;
}

if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}

// 循环遍历所有IdleHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler

boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
}
// 如果IdleHandler接口的queueIdle方法返回false说明只执行一次需要删除
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}

// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// 在处理IdleHandler的时候,一个新的Message可能已经下发了,因此不等待直接循环查找
nextPollTimeoutMillis = 0;
}
}

在next方法中会死循环查找满足条件的Message,找到则返回,若已经退出则返回null。总结一下next方法做的几件事情:

  • 根据delay来指定timeout,唤醒CPU;
  • 优先处理异步消息,然后处理达到timeout的同步消息;
  • 最后如果队列空闲则处理IdleHandler逻辑。

Looper

概述

Looper在消息机制里扮演着消息循环的角色,它不停的从消息队列里查看是否有新消息,如果有,则立即处理新消息,没有则一直阻塞在那里。当需要为一个线程创建一个Looper的时候,需要调用Looper的两个静态方法就可以给这个线程创建一个Looper对象了,这两个方法是Looper.prepare()Looper.loop()

prepare

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
new Thread(){
@override
public void run(){
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();

// 把Looper对象保存到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));
}

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

Looper.prepare()方法创建了一个Looper实例,并通过ThreadLocal将其保存到当前线程中,一个线程只允许创建一个Looper实例,即一个线程中Looper.prepare()方法只能被调用一次。

loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void loop() {
final Looper me = myLooper();
// 要先调用Looper.prepare()方法
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// ...

for (;;) {
Message msg = queue.next(); // 可能阻塞
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

try {
msg.target.dispatchMessage(msg);
} finally {
}
// ...
msg.recycleUnchecked();
}
}

Looper.loop()方法首先是获取到当前线程的Looper对象和绑定到Looper对象的这个消息队列,接着就是一个死循环了。当消息队列没有消息时,则等待消息;当有消息时,则把这个消息通过Handler(msg.target是一个Handler对象)的dispatchMessage方法来处理这个消息,这也就到了Handler里面去执行了,这样就成功地将代码逻辑切换到指定的线程中去了。

quit

1
2
3
4
5
6
7
public void quit() {
mQueue.quit(false);
}

public void quitSafely() {
mQueue.quit(true);
}

如果当这个loop执行到一半的时候要退出怎么办呢?Looper有一个quit方法,此方法被调用时,Looper会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,并标识为退出状态,当消息队列被标记为退出状态时,next方法就会返回null,这样loop里面的死循环就跳出去了。

总结

  1. 当Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false;
  2. 在子线程中,如果手动为其创建了Looper,那么在所有消息处理完成之后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper;
  3. Looper.loop()方法应该置于run方法的最后,不然之后的代码不会运行。除非调用了looper的quit方法;
  4. 主线程的Looper.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。因为Android是由事件驱动的,looper.loop()不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在Looper.loop()的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了Looper.loop(),而不是Looper.loop()阻塞它。

Handler

概述

Handler主要包括消息的发送和接收,其实是相当于Handler给自己发送了一条消息,只是消息经过了消息队列以及Looper,最后才到Handler的handleMessage方法里。

构造函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async; // mAsynchronous用来控制Barrier时是否仍可以不受Barrier的影响被正常处理,如果没有设置Barrier,异步消息就与同步消息没有区别
}

public Handler(Callback callback, boolean async) {
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;
}

send

Handler的消息的发送主要由post系列方法以及send的系列方法来实现,post的方法最终都是通过send的方法来实现的。而send系列方法最后都是通过sendMessageAtTime方法来实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
// ...
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

发送消息其实就是往消息队列里面插入一个消息,返回的boolean值表示插入是否成功。

当Handler把消息插入到消息队列里后,MessageQueue就通过next方法把这个消息返回给Looper,而Looper则通过loop方法调用Handler的dispatchMessage方法。

dispatchMessage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

private static void handleCallback(Message message) {
message.callback.run();
}
  • 首先就是判断Message对象是否有callback对象,如果有,则调用callback的run方法,callback是通过Handler的post方法所传递的。
  • 如果Message对象没有callback对象,则跑到else代码块里,mCallback的handlerMessage方法和后面的那个handlerMessage方法其实是一样的,第一个是通过实现mCallback接口来得到的一个方法,而第二个是Handler的方法,两个方法都是空实现,当处理消息时都要重写这个两个方法中的一个,只是创建Handler时的构造方法不一样而已。

从上可以看出 Handler.Callback 有优先处理消息的权利,当某条消息被 Callback 拦截后(即返回true),则不会调用 Handler.handleMessage(msg) 方法。由此可以通过给 Handler 设置 mCallback 对象(可以通过反射等手段)来达到拦截该 Handler 对象里的消息的目的。如处理 SharedPreferences apply ANR 的时候便用到了这个方法。

扩展

Activity.runOnUiThread(Runnable)

1
2
3
4
5
6
7
8
9
final Handler mHandler = new Handler();

public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}

AsyncTask

AsyncTask是Android提供的一个处理异步任务的类,通过此类,可以实现UI线程和后台线程进行通讯,后台线程执行异步任务,并把结果返回给UI线程。

1
public abstract class AsyncTask<Params, Progress, Result> {}

泛型参数:

  • Params: 启动任务时输入的参数类型。
  • Progress: 后台任务执行中返回进度值的类型。
  • Result: 后台任务执行完成后返回结果的类型。

回调方法:

  • doInBackground: 必须重写,异步执行后台线程要完成的任务,耗时操作将在此方法中完成。(后台线程)
  • onPreExecute: 执行后台耗时操作前被调用,通常用于进行初始化操作。(UI线程)
  • onPostExecute: 当doInBackground方法完成后,系统将自动调用此方法,并将doInBackground方法返回的值传入此方法。通过此方法进行UI的更新。(UI线程)
  • onProgressUpdate: 当在doInBackground方法中调用publishProgress方法更新任务执行进度后,将调用此方法。通过此方法我们可以知晓任务的完成进度。(UI线程)

HandlerThread

更新UI时是子线程与UI主线程之间的通信,而子线程与子线程之间的通信需要用Handler+Thread来完成(需要自己操作Looper),Google官方提供了HandlerThread类,它是Thread的子类,用来在工作线程中执行任务,如耗时任务等,同时也有异步通信,消息传递的功能。

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
HandlerThread mHandlerThread = new HandlerThread("HandlerThread");
handlerThread.start();

final Handler handler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
System.out.println("收到消息");
}
};

@Override
protected void onDestroy() {
super.onDestroy();
mHandlerThread.quit();
}

// 在另一个线程给HandlerThread发送消息
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000); // 模拟耗时操作
handler.sendEmptyMessage(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();

HandlerThread类源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;

public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}

protected void onLooperPrepared() {
}

@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}

public Looper getLooper() {
if (!isAlive()) {
return null;
}

// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}

@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}

public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}

public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}

public int getThreadId() {
return mTid;
}
}

Handler.runWithScissors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 同步阻塞运行
public final boolean runWithScissors(final Runnable r, long timeout) {
if (r == null) {
throw new IllegalArgumentException("runnable must not be null");
}
if (timeout < 0) {
throw new IllegalArgumentException("timeout must be non-negative");
}
// 当前线程跟当前Handler都指向同一个Looper则直接运行
if (Looper.myLooper() == mLooper) {
r.run();
return true;
}

BlockingRunnable br = new BlockingRunnable(r);
return br.postAndWait(this, timeout);
}

private static final class BlockingRunnable implements Runnable {
private final Runnable mTask;
private boolean mDone;

public BlockingRunnable(Runnable task) {
mTask = task;
}

@Override
public void run() {
try {
mTask.run();
} finally {
synchronized (this) {
mDone = true;
notifyAll();
}
}
}

public boolean postAndWait(Handler handler, long timeout) {
if (!handler.post(this)) {
return false;
}

synchronized (this) {
if (timeout > 0) {
final long expirationTime = SystemClock.uptimeMillis() + timeout;
while (!mDone) {
long delay = expirationTime - SystemClock.uptimeMillis();
if (delay <= 0) {
return false; // timeout
}
try {
wait(delay);
} catch (InterruptedException ex) {
}
}
} else {
while (!mDone) {
try {
wait();
} catch (InterruptedException ex) {
}
}
}
}
return true;
}
}

内存泄漏

原理

可以作为 GcRoot 的对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象

发生内存泄漏的原因:非静态内部类或者匿名内部类默认持有外部类的引用,当通过 handler.sendMessage 方法发送消息时,Message 被发送到 Handler 中的 MessageQueue 里面,也就是 Looper 里面的 mQueue 队列,Message 如有延迟处理则会留在 MessageQueue 里面,而 Message 持有 mHandler 的引用,Handler 可能持有外部 Activity 的引用。主线程的 Looper 对象生命周期和应用程序生命周期一样长。因此不会回收已被销毁的 Activity 对象,而 GCRoot 是 Looper.sThreadLocal 对象。

解决方案

清空Message

1
2
3
4
5
6
@Override
protected void onDestroy() {
//将Handler里面消息清空了。
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}

弱引用

为了避免非静态内部类&匿名内部类持有外部类引用可以采用静态内部类或者直接在外部编写该Handler的继承类。如果该类需要使用Activity相关变量,可以采用弱引用的方式将Activity的变量传过去。由于 gc 是无法控制的,如果发生 gc 导致 Acivity 被回收则无法正常工作了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class NoLeakHandler<T> extends Handler {

private WeakReference<SecondActivity> weakReference;

public NoLeakHandler(SecondActivity activity) {
this.weakReference = new WeakReference<>(activity);
}

@Override
public void handleMessage(Message msg) {
if (weakReference.get()!=null) {
Log.d("LLL", "handle...");
} else {
Log.d("LLL", "handle null...");
}
}
}

在onDestroy方法中尽量还是清空掉消息.

epoll

管道与eventfd

管道

  • 管道最常见的一个用法,就是将一个进程的输出导入到另一个进程的输入。
  • 数据以字节流的形式在管道中传输,也就是说,数据是没有消息边界。
  • 数据在管道中是单向流动的,管道的一端负责写数据,而另一端负责读数据。
  • 管道的缓冲区长度是 64 KB。

管道需要创建两个文件描述符,分别对应读写端,使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int pipFd[2];
pipe(pipFd); //打开管道

struct epoll_event event;
event.data.fd = pipFd[0]; //设置为监听管道读端
event.events = EPOLLIN | EPOLLET; //设置参数,接收可以 read() 的通知

int epfd = epoll_create(256); //创建 epoll 对象
int res = epoll_ctl(epfd, EPOLL_CTL_ADD, pipFd[0], &event); //添加管道读端为要监听的文件描述符

struct epoll_event allEvs[256];
int count = epoll_wait(epfd, allEvs, 256, 5000); //当前线程进入阻塞,等待被唤醒
for(int i = 0; i < count; i++){ //被唤醒,处理触发唤醒文件描述符
if(allEvs[i].data.fd == pipFd[0] && (allEvs[i].events & EPOLLIN)){
char buffer[256];
read(pipeFd, buffer, 100); //接收到管道可以进行读的信号,开始读取
}
}

// 其它线程
write(pipFd[1], str,strlen("hello"));

eventfd

  • eventfd 是 Linux 2.6.22后才开始支持的一种IPC通信方式,它的作用主要时用来做事件通知。
  • 与管道相比,eventfd的开销更小,因为eventfd只需要一个文件描述符。

管道和 eventfd 都可用于 epoll 等多路复用模型中,实现异步的信号通知功能。在 Android 6.0 之后,Looper 的唤醒从管道换成了 eventfd。

eventfd 是 Linux 系统中一个用来通知事件的文件描述符,基于内核向用户空间应用发送通知的机制,可以有效地被用来实现用户空间事件驱动的应用程序。

简而言之:eventfd 就是用来触发事件通知,它只有一个系统调用接口:

1
int eventfd(unsigned int initval, int flags);

表示打开一个 eventfd 文件并返回文件描述符,支持 epoll/poll/select 操作。

IO多路复用

IO 多路复用是一种同步 IO 模型,实现一个线程可以监视多个文件句柄。一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作,没有文件句柄就绪时会阻塞应用程序,交出 cpu。

与多进程和多线程技术相比,IO 多路复用技术的最大优势是系统开销小,系统不必为每个 IO 操作都创建进程或线程,也不必维护这些进程或线程,从而大大减小了系统的开销。

select、poll、epoll 就是 IO 多路复用三种实现方式。

  • select 最大连接数为进程文件描述符上限,一般为 1024;每次调用 select 拷贝 fd;轮询方式工作时间复杂度为 O(n)
  • poll 最大连接数无上限;每次调用 poll 拷贝 fd;轮询方式工作时间复杂度为 O(n)
  • epoll 最大连接数无上限;首次调用 epoll_ctl 拷贝 fd,调用 epoll_wait 时不拷贝;回调方式工作时间复杂度为 O(1)

epoll API:

1
2
3
4
5
6
7
8
9
10
11
12
// 返回一个 epfd,即 eventpoll 句柄。
int epoll_create(int size);

// 返回值:成功 0;失败 -1
// 对一个 epfd 进行操作。op 表示要执行的操作,包括 EPOLL_CTL_ADD (添加)、EPOLL_CTL_DEL (删除)、EPOLL_CTL_MOD (修改);
// fd 表示被监听的文件描述符;event 表示要被监听的事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

// 返回值:监听到的产生的事件数
// 等待 epfd 监听的 fd 所产生对应的事件。
// timeout:等待 IO 的超时时间,-1 表示一直阻塞直到来 IO 被唤醒,大于 0 表示阻塞指定的时间后被唤醒
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

Handler机制

主要分析 MessageQueue.java 中的三个 native 函数:

1
2
3
private native static long nativeInit(); //返回 ptr
private native void nativePollOnce(long ptr, int timeoutMillis); //阻塞
private native static void nativeWake(long ptr); //唤醒

nativeInit

首先来看 nativeInit 方法,nativeInit 在 MessageQueue 构造函数中被调用,其返回了一个底层对象的指针,对应实现在 android_os_MessageQueue.cpp 中:

1
2
3
4
5
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
// ...
return reinterpret_cast<jlong>(nativeMessageQueue);
}

可见 MessageQueue 对应的底层对象就是 NativeMessageQueue,而 NativeMessageQueue 初始化时会创建一个底层的 Looper 对象,Looper 的构造函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Looper::Looper(bool allowNonCallbacks) : mAllowNonCallbacks(allowNonCallbacks), ...{
// 通过 eventfd 系统调用返回一个文件描述符,专门用于事件通知
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
...
rebuildEpollLocked();
}

void Looper::rebuildEpollLocked() {
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event));
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
...
}

通过 epoll_create 创建 epoll 对象,然后调用 epoll_ctl 添加 mWakeEventFd 为要监听的文件描述符

nativePollOnce

nativePollOnce 会使线程休眠,看下底层实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mLooper->pollOnce(timeoutMillis);
...
}

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
for (;;) {
...
result = pollInner(timeoutMillis);
}
}

int Looper::pollInner(int timeoutMillis) {
...
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
}

通过调用 epoll_wait 方法,当前线程进入休眠,等待被唤醒

nativeWake

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
mLooper->wake();
}

void Looper::wake() {
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s", mWakeEventFd, strerror(errno));
}
}
}

关键逻辑是对 mWakeEventFd 发起写入操作,从而唤醒 nativePollOnce 中通过 epoll_wait 进入休眠的线程

扩展

在 Looper 中单独监控一个 mWakeEventFd 描述符为什么需要用到 epoll 呢,使用 recvform 这样的同步阻塞 IO 就行了呢?实际上 Looper 的功能是强大的,它提供了 addfd 的方法,外部可调用该方法动态添加需要监控的描述符与回调,Choreographer 中就是通过该方法监控 VSYNC 事件的。

所以 Native 层的 Looper 具有跨进程的能力?

总结

Android的消息机制的总体流程就是:Handler向MessageQueue发送一条消息(即插入一条消息),MessageQueue通过next方法(死循环查找)把消息传给Looper,Looper收到消息后开始处理,然后最终交给Handler自己去处理。换句话说就是:Handler给自己发送了一条消息,然后自己的handleMessage方法处理消息,只是中间过程经过了MessageQueue和Looper。这里的MessageQueue和Looper都有循环存在,调用的方法过程如下:

Handler消息机制

Handler导致内存泄漏的原因也就是因为在Looper的MessageQueue里面有还未处理的Message,而该Message又引用了Handler,如果该Handler采用的是非静态内部类或匿名内部类则该Handler又持有外部类Activity的引用,导致了Activity无法释放,因而产生了内存泄漏。

手写简易版的Handler机制