Handler处理消息相关源码解析
Posted 涂程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler处理消息相关源码解析相关的知识,希望对你有一定的参考价值。
一、handler的用法
1.1、用于线程切换
Handler handler = new Handler(Looper.getMainLooper());
new Thread(new Runnable() {
@Override
public void run() {
handler.post(() -> {
binding.tv.setText("hello world");
});
}
}).start();
1.2、线程间通信
为了防止handler导致内存泄露,handler对Activity采用弱引用,因弱引用不会影响Activity的java生命周期。
static class MyHandler extends Handler {
WeakReference<MainActivity> mActivityWeakReference;、
public MyHandler(MainActivity activity) {
mActivityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
MainActivity activity = mActivityWeakReference.get();
if (activity != null && msg!=null && msg.obj!=null) {
//todo
}
}
}
MyHandler myHandler = new MyHandler(MainActivity.this);
new Thread(new Runnable() {
@Override
public void run() {
Message message = Message.obtain();
message.obj = "aaa";
mMyHandler.sendMessage(message);
}
}).start();
1.3、任务延时执行
static class MyRunnalbe implements Runnable {
@Override
public void run() {
Log.e("test", "test memory leak?");
}
}
//发送延时任务
Handler handler3 = new Handler(Looper.getMainLooper());
handler3.postDelayed(new MyRunnalbe(), 10000);
最后大家不要忘了,一定记得在Activity的onDestroy及时解除Message对Handler的强引用。
@Override
protected void onDestroy() {
super.onDestroy();
mMyHandler.removeCallbacksAndMessages(null);
}
二、Handler(Loope(MessageQueue))的创建
2.1、Handler创建
先看看handler的构造函数。
构造函数肯定是相互调用,只需要看最多的参数的那个
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) { xxxx }
-
从构造可以看出,Handler的创建需要Looper,没有Looper玩不转呐,我们之前写handler用法的时候,都是在主线程创建Handler,它构造里面会调用Looper.myLoop()获取Looper。
-
ThreadLocal可用于线程间的数据隔离,这是解决多线程并发的一种手段 (不共享可变资源)。将线程需要的Looper保存在ThreadLocal里面。
-
我们App的入口函数就是ActivityThread的main方法,一般在main方法里面就做两件事,第一件就是启动创建主线程的Looper,启动Looper轮询;第二件事就是将ApplicationThread的binder对象注册给AMS(AMS拿到它后就可以调用App的方法)。loop轮询一旦跳出了死循环,App直接就抛异常了。
-
如果最后一个参数async为true,那么通过handler.sendMessage的message都会打上异步消息的标签。
//from Handler
public Handler(@Nullable 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()");
}
.......
}
//from Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//myLooper直接从Threadlocal中取
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
//创建主线程Looper,因为主线程Looper不支持退出
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
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));
}
//from ActivityThread
public static void main(String[] args) {
.......
//创建主线程Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
//向AMS注册,将app的binder对象给AMS
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
.....
//启动轮询
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Q: 如果直接在子线程里面new Handler()创建,从ThreadLocals里面肯定是取不到对应的looper的,所以会抛异常。
需要显式调用Looper.prepare()才行。
子线程中弹吐司也要先通过Looper.prepare()创建Looper,不然也会奔溃。
原因同上👆
2.1.1、ThreadLocal原理
都说ThreadLocal可用于线程间数据隔离,完美的解决并发问题,那啥原理呢? 先看看用法。
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = t.threadLocals; //线程的t.threadLocals
if (map != null)
map.set(this, value); //直接添加
else
createMap(t, value); //那就给当前线程创建这个ThreadLocalMap类型的t.threadLocals成员
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); //斐波那契散列算法
for (Entry e = tab[i]; e != null;e = tab[i = nextIndex(i, len)]) { //开放寻址法解决冲突
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value); //key就是ThreadLocal
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash();
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
threadlocal.set(xxx)时,会从当前线程(主线程/子线程)取出它的ThreadLocalMap类型的成员threadLocals,不为null就直接添加了,为空则先创建。ThreadLocalMap底层采用数组实现,不像HashMap采用数组+链表/红黑树实现。这里的元素的index采用斐波那契散列算法实现,采用开放寻址法解决散列冲突,具体参考ThreadLocalMap的hash算法。数组元素Entry很有特点。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry是一个弱引用类,直接通过entry.get()可以获取到Threadlocal引用,因其作为entry的key,只能对应一个值value。同时也参与table数组index的hash算法(i = key.threadLocalHashCode & (len-1))。如通过entry.get()获取threadLocal为null时,此时value不为null,就有发生泄漏可能,一般建议就是用完及时进行remove掉。
2.2、Looper的创建
public static void prepare() {
prepare(true);
}
public static void prepare() {
prepare(true);
}
//quitAllowed 是否支持退出,默认是支持退出的。但是主线程的looper不支持退出。
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();
Handler创建需要Looper,Looper创建需要MessageQueue。如果多次调用Loopr.prepare来创建Looper会报错,即在一个线程里面创建多个Looper会报错。
主线程的Looper当然不支持退出,不然随便调用looper.quit,App就奔溃了。用户子线程中通过Looper.prepare()创建Looper默认是true,支持退出的。
2.3、MessageQueue的创建
private long mPtr; // used by native code
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
MessageQueue创建时,创建了一个long类型的成员,native可以通过这个long指针直接强转成native对象用,这里是底层的NativeMessageQueue的指针。
从这里可以看出,一个线程可以创建无数个Handler,但只能有一个Looper,一个MessageQueue。
三、Looper.loop() 与 handler.sendMessage(xx)
3.1、开启轮询消息Looper.loop()
入口函数ActivityThread.main()方法中,就开启了Looper.loop()。
//from Looper
public static void loop() {
......
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // 可能阻塞
......
msg.target.dispatchMessage(msg);
......
msg.recycleUnchecked(); //消息进入循环池
}
}
Q: 如果让我们写个Handler分发、处理Message,你会怎么写?
因为Handler的分发事件可能在其他线程,被称为生产者,处理Message的Handler被称为消费者,典型的生产消费模型,肯定会考虑生产多了+消费慢,生产少了+消费快的场景,此时伪代码如下:
public synchronized void loop() {
long now = System.currentTimeMillis();
int count; //mMessageQueue.top()就是获得一个Message
while ((count = mMessageQueue.size()) == 0 || mMessageQueue.top().when > now) {
long diff = (count == 0) ? 0 : (now - mMessageQueue.top().when);
try {
this.wait(diff); //这里就类似Message msg = queue.next();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
......
this.notifyAll();
}
系统肯定不会用wait/notify+synchronized这种性能欠佳的组合实现,对的,底层用的是Epoll机制实现
来看看MessageQueue的Next方法。
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands(); //被调用说明后面的代码可能会引起线程阻塞(此处不懂)
}
nativePollOnce(ptr, nextPollTimeoutMillis) //epoll_wait
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
/此处msg没有target(handler),那就表示这个msg为同步屏障消息,配合异步消息使用的
if (msg != null && msg.target == null) {
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 {
//终于拿到一个可用的Message了,单链表取出Message的操作,模板代码
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next; //所以mMessage就是下一个要处理的Message。记住了
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1; //-1传给native的epoll_wait,表示永远不能唤醒,需要主动被wake
}
//messageQueue为empty了 或者 mMessage是将来才处理的(when还没到)
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true; //那就继续阻塞
continue;
}
//每次处理的handler最多只能是4个,多了不处理????
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = idler.queueIdle(); //idleHandler的queueIdle返回值true/false处理不同
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0以上是关于Handler处理消息相关源码解析的主要内容,如果未能解决你的问题,请参考以下文章