Handler1-消息传递机制
2100 Words|Read in about 10 Min|本文总阅读量次
Handler是一个优秀的内存共享方案。其内存管理和设计思路相当完整。 通过Handler来通知UI组件更新或者是处理对应消息。那么Handler消息机制是什么?
Handler1 消息传递机制
开篇问题
1.一个线程有几个handler
2.一个线程有几个looper,如何保证
3.Handler会出现内存泄漏问题吗,怎么解决内存泄漏
4.子线程总如何使用Handler
android 和linux 交互会使用socket
可以说Handler、Binder、Socket是整个Android系统的灵魂。从本文开始从源码角度分析Handler
handler使用
1.handler初始化
Handler构造,当前api30之后,废弃了2个,隐藏3个,实际我们可用的只有两个构造,必须指定当前是哪个Looper
1@Deprecated
2public Handler() {
3 this(null, false);
4}
5
6@Deprecated
7public Handler(@Nullable Callback callback) {
8 this(callback, false);
9}
10//可用的构造方法1
11public Handler(@NonNull Looper looper) {
12 this(looper, null, false);
13}
14//可用的构造方法2
15public Handler(@NonNull Looper looper, @Nullable Callback callback) {
16 this(looper, callback, false);
17}
18
19/*
20* @hide
21*/
22@UnsupportedAppUsage
23public Handler(boolean async) {
24 this(null, async);
25}
26
27/*
28* @hide
29*/
30public Handler(@Nullable Callback callback, boolean async) {
31 if (FIND_POTENTIAL_LEAKS) {
32 final Class<? extends Handler> klass = getClass();
33 if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
34 (klass.getModifiers() & Modifier.STATIC) == 0) {
35 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
36 klass.getCanonicalName());
37 }
38 }
39
40 mLooper = Looper.myLooper();
41 if (mLooper == null) {
42 throw new RuntimeException(
43 "Can't create handler inside thread " + Thread.currentThread()
44 + " that has not called Looper.prepare()");
45 }
46 mQueue = mLooper.mQueue;
47 mCallback = callback;
48 mAsynchronous = async;
49}
50
51/*
52* @hide
53*/
54@UnsupportedAppUsage
55public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
56 mLooper = looper;
57 mQueue = looper.mQueue;
58 mCallback = callback;
59 mAsynchronous = async;
60}
举例说明
1//可用的构造方法1
2private final Handler mUIHandler = new Handler(Looper.getMainLooper()){
3 @Override
4 public void handleMessage(Message msg) {
5 Log.d(TAG, "->>>mUIHandler);
6 }
7};
1//可用的构造方法2
2private final Handler childHandler = new Handler(handlerThread.getLooper(), new Handler.Callback() {
3 @Override
4 public boolean handleMessage(@NonNull Message msg) {
5 Log.d(TAG, "->>>ChildCallback|handleMessage");
6 return true;
7 }
8});
其中,关于Looper.getMainLooper()和Looper.myloop()
Looper.myLooper()
获取当前进程的looper对象
Looper.getMainLooper()
获取主线程的Looper对象
通过Handler构造函数可以看出: 一个 Handler 中只能有一个 Looper。而一个 Looper 则可以对应多个 Handler,只要把 Looper 往 Handler 的构造方法里扔扔扔就好了。
2.handler发送消息
所有发送消息的方式都在上面罗列,一般常用的为直接或间接调用sendMessageDelayed方法,而其调用的sendMessageAtTime会内部判断消息队列是否为空,消息队列为空就不做处理。
并且这个msg中还会存在时间when,target还有setAsynchronous
1)其中Message的target属性在enqueMessage的时候会将msg.target = this,即Message持有Handler对象
2)其中在enqueMessage中的判断mAsynchronous,通常情况下都是同步消息,这个判断为false
3.handler处理消息
message回到handler中的流程
Looper.java//loop函数
msg.target.dispatchMessage(msg)—»
Handler.java//dispatchMessage函数
分三种情况
1/**
2 * Handle system messages here.
3 */
4public void dispatchMessage(@NonNull Message msg) {
5 if (msg.callback != null) {
6 handleCallback(msg);//第一种情况
7 } else {
8 if (mCallback != null) {
9 if (mCallback.handleMessage(msg)) {
10 return;//第二种情况
11 }
12 }
13 handleMessage(msg);//第三种情况
14 }
15}
1.收发消息一体
Message通过post(Runnable)等方法进行发送走handleCallback(msg);
1public class MainActivity extends AppCompatActivity {
2 private TextView msgTv;
3 private Handler mHandler = new Handler(Looper.getMainLooper());
4
5 @Override
6 protected void onCreate(Bundle savedInstanceState) {
7 super.onCreate(savedInstanceState);
8 setContentView(R.layout.activity_main);
9 msgTv = findViewById(R.id.tv_msg);
10
11 //消息收发一体
12 new Thread(new Runnable() {
13 @Override public void run() {
14 String info = "第一种方式";
15 mHandler.post(new Runnable() {
16 @Override public void run() {
17 msgTv.setText(info);
18 }
19 });
20 }
21 }).start();
22 }
23}
2.收发分开,实现Callback接口
在Handler构造的时候传入Handler内部抽象类,优先处理。mCallback.handleMessage(msg)这个函数会回调到构造里面Callback,最后在结尾是返回一个boolean类性值,一般为true,即外部不需要在handleMessage一次,立刻结束本次的message传递。
1public interface Callback {
2 /**
3 * @param msg A {@link android.os.Message Message} object
4 * @return True if no further handling is desired
5 */
6 boolean handleMessage(@NonNull Message msg);
7}
1public class MainActivity extends AppCompatActivity {
2 private TextView msgTv;
3 private Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
4 //接收消息,刷新UI
5 @Override public boolean handleMessage(@NonNull Message msg) {
6 if (msg.what == 1) {
7 msgTv.setText(msg.obj.toString());
8 }
9 //false 重写Handler类的handleMessage会被调用, true 不会被调用
10 return false;
11 }
12 });
13
14 @Override
15 protected void onCreate(Bundle savedInstanceState) {
16 super.onCreate(savedInstanceState);
17 setContentView(R.layout.activity_main);
18 msgTv = findViewById(R.id.tv_msg);
19
20 //发送消息
21 new Thread(new Runnable() {
22 @Override public void run() {
23 Message message = Message.obtain();
24 message.what = 1;
25 message.obj = "第二种方式 --- 2";
26 mHandler.sendMessage(message);
27 }
28 }).start();
29 }
30}
3.重写Handler类
调用handler重新handleMessage方法,走handleMessage(msg);
1public class MainActivity extends AppCompatActivity {
2 private TextView msgTv;
3 private Handler mHandler = new Handler(Looper.getMainLooper()) {
4 //接收消息,刷新UI
5 @Override public void handleMessage(@NonNull Message msg) {
6 super.handleMessage(msg);
7 if (msg.what == 1) {
8 msgTv.setText(msg.obj.toString());
9 }
10 }
11 };
12
13 @Override
14 protected void onCreate(Bundle savedInstanceState) {
15 super.onCreate(savedInstanceState);
16 setContentView(R.layout.activity_main);
17 msgTv = findViewById(R.id.tv_msg);
18
19 //发送消息
20 new Thread(new Runnable() {
21 @Override public void run() {
22 Message message = Message.obtain();
23 message.what = 1;
24 message.obj = "第三种方式 --- 3";
25 mHandler.sendMessage(message);
26 }
27 }).start();
28 }
29}
Handler消息传递
优先级队列
根据上述可以看到Handler发送消息,最终会调用enqueMessage
1//handler#enqueMessage
2private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
3 long uptimeMillis) {
4 msg.target = this;
5 msg.workSourceUid = ThreadLocalWorkSource.getUid();
6
7 if (mAsynchronous) {
8 msg.setAsynchronous(true);
9 }
10 return queue.enqueueMessage(msg, uptimeMillis);
11}
最终返回的是一个布尔值,queue.enqueMessage,其中queue是MessageQueue类型的
1//MessageQueue#enqueueMessage
2boolean enqueueMessage(Message msg, long when) {
3 ...
4 synchronized (this) {
5 msg.when = when;
6 //这里的mMessages是一个Message类型的链表(Message中有next属性值)
7 Message p = mMessages;
8 boolean needWake;
9 //三个判断指的是这个链表没有Message,传入的当前p为当前第一个Message
10 if (p == null || when == 0 || when < p.when) {
11 // New head, wake up the event queue if blocked.
12 msg.next = p;
13 mMessages = msg;
14 needWake = mBlocked;//mBlocked通常为true的时候是无idle handlers,纯等待
15 } else {
16 // Inserted within the middle of the queue. Usually we don't have to wake
17 // up the event queue unless there is a barrier at the head of the queue
18 // and the message is the earliest asynchronous message in the queue.
19 //这个needWake中的后两个判断,第一个是用来判断当前MessageQueue是否开启同步屏障且传入的Message是异步的
20 needWake = mBlocked && p.target == null && msg.isAsynchronous();
21 Message prev;
22 //循环开始在适当的位置把传入的msg插入到mMessageQueue中
23 for (;;) {
24 prev = p;
25 p = p.next;
26 if (p == null || when < p.when) {
27 break;
28 }
29 if (needWake && p.isAsynchronous()) {
30 needWake = false;
31 }
32 }
33 msg.next = p; // invariant: p == prev.next
34 prev.next = msg;
35 }
36 // We can assume mPtr != 0 because mQuitting is false.
37 if (needWake) {
38 //用于唤醒next方法中的nativePollOnce
39 nativeWake(mPtr);
40 }
41 ...
42 }
43 return true;
44}
拆解上述方法,核心的就是其中两个判断if (p == null || when == 0 || when < p.when),正好对应两个形式的链表的插入,从而得出正是一个按照时间顺序排列的链表。
1)如果判断if (p == null || when == 0 || when < p.when)成立,则为下图
2)如果判断if (p == null || when == 0 || when < p.when)不成立,当前msg传入的when<p.when,则为下图
3)如果判断if (p == null || when == 0 || when < p.when)不成立,当前p=null,则为下图
那么既然有插入链表的操作,就有从MessageQueue中取出的操作,再来看next方法
1//MessageQueue#enqueueMessage
2Message next() {
3 ...
4 int pendingIdleHandlerCount = -1; // -1 only during first iteration
5 for (;;) {
6 if (nextPollTimeoutMillis != 0) {
7 //当需要阻塞时,防止进程持有对象的时间超过需要的时间,做一个保护作用
8 Binder.flushPendingCommands();
9 }
10 //这个是用来阻塞的,两种方式解除
11 //1.nativeWake的时候继续执行下去。即直到euqueMessage中插入了一个比nextPollTimeoutMillis小的时间的msg
12 //2.nextPollTimeoutMillis==now,时间消耗完毕,开始取消阻塞
13 //3.退出消息队列所在的循环Looper,即mQuitting=true
14 nativePollOnce(ptr, nextPollTimeoutMillis);
15
16 synchronized (this) {
17 // Try to retrieve the next message. Return if found.
18 final long now = SystemClock.uptimeMillis();
19 Message prevMsg = null;
20 Message msg = mMessages;
21 if (msg != null) {
22 if (now < msg.when) {
23 // Next message is not ready. Set a timeout to wake up when it is ready.
24 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
25 } else {
26 // Got a message.
27 mBlocked = false;
28 //这个判断是用于同步屏障中异步消息的判断,这里暂时不考虑
29 if (prevMsg != null) {
30 prevMsg.next = msg.next;
31 } else {
32 mMessages = msg.next;
33 }
34 msg.next = null;
35 if (DEBUG) Log.v(TAG, "Returning message: " + msg);
36 msg.markInUse();
37 return msg;
38 }
39 } else {
40 // No more messages.
41 nextPollTimeoutMillis = -1;
42 }
43
44 // Process the quit message now that all pending messages have been handled.
45 if (mQuitting) {
46 dispose();
47 return null;
48 }
49 //没有IdleHandler的时候,直接continue
50 if (pendingIdleHandlerCount <= 0) {
51 // No idle handlers to run. Loop and wait some more.
52 mBlocked = true;
53 continue;
54 }
55 }
56 ...
57 // While calling an idle handler, a new message could have been delivered
58 // so go back and look again for a pending message without waiting.
59 nextPollTimeoutMillis = 0;
60 }
61}
拆解可知,这个链表是按照msg插入when的时间的排列的链表,符合先进先出,即为优先级队列。
1)当前的now<msg.when,开始阻塞线程
这里边用到了nativePollOnce,用于阻塞线程,三种方式可以取消阻塞
1.nativeWake的时候继续执行下去。即直到euqueMessage中插入了一个比nextPollTimeoutMillis小的时间的msg 2..nextPollTimeoutMillis==now,时间消耗完毕,开始取消阻塞
3.退出消息队列所在的循环Looper
2)开始获取Message,返回圈红的Message
在MessageQueue里面Handler中的三个native方法,主要含义具体分析详见Ahab的文章
1//Handler中的三个native方法
2private native static long nativeInit(); //返回 ptr
3private native void nativePollOnce(long ptr, int timeoutMillis); //阻塞
4private native static void nativeWake(long ptr); //唤醒
Looper
根据上述可知Handler会通过enqueMessage来将消息根据延时的时间排列成优先级队列先进先出,那么谁去执行next让其每次一个个有序的将MessageQueue中的Message取出来呢?这时候需要用到Looper。
获取next方法
听名字就知道是一个循环,主要是看里面loop方法
1//Looper.java#loop
2public static void loop() {
3 final Looper me = myLooper();
4 final MessageQueue queue = me.mQueue;//mQueue = new MessageQueue(quitAllowed);默认是无法退出的
5
6 // Make sure the identity of this thread is that of the local process,
7 // and keep track of what that identity token actually is.
8 //重置当前线程上传入的IPC的身份,确保此线程的标识是本地进程的标识
9 Binder.clearCallingIdentity();
10 final long ident = Binder.clearCallingIdentity();
11
12 for (;;) {
13 Message msg = queue.next(); // might block
14 if (msg == null) {
15 // No message indicates that the message queue is quitting.
16 return;
17 }
18 // Make sure the observer won't change while processing a transaction.
19 //observer是用于Handler分发操作的监听,监听产生问题的trace
20 //补充说明:这里面的sObserver是在LooperStateService.java里面设置下来的
21 //Looper.setObserver(enable ? mStates : null);
22 //其中mStates定义 LooperState mStates implement Looper.Observer
23 //这里的enable是通过系统属性来区分debug.sys.Looper_state_enabled
24 final Observer observer = sObserver;
25
26 Object token = null;
27 if (observer != null) {
28 token = observer.messageDispatchStarting();
29 }
30 long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
31 try {
32 //将获取到的msg分发到dispatchMessage
33 msg.target.dispatchMessage(msg);
34 if (observer != null) {
35 observer.messageDispatched(token, msg);
36 }
37 dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
38 } catch (Exception exception) {
39 if (observer != null) {
40 observer.dispatchingThrewException(token, msg, exception);
41 }
42 throw exception;
43 } finally {
44 ThreadLocalWorkSource.restore(origWorkSource);
45 if (traceTag != 0) {
46 Trace.traceEnd(traceTag);
47 }
48 }
49 ...
50 //msg资源池回收机制
51 msg.recycleUnchecked();
52 }
53}
拆解loop方法,其实就是做了几件事
1)重复循环获取next方法,因为next方法存在阻塞,所以queue.next()也会阻塞,且唯一退出这个方法的是就是获取一个null的Messgae
2)将获取到的msg分发到dispatchMessage,用这个方法来对消息进行处理,msg.target持有了Handler对象,那么依然会回到Handler中去使用。通过Looper使得整个消息队列形成了闭环。
3)处理完消息之后,通过msg.recycleUnchecked(),将消息回收到消息池里
这里的消息放到消息池,是运用了设计模式的享元模式
具体看一下Message的构成
1//Message.java的属性
2public static final Object sPoolSync = new Object();//用于消息池的上锁功能
3private static Message sPool;
4private static int sPoolSize = 0;//默认消息池的大小
5
6private static final int MAX_POOL_SIZE = 50;//消息池最大容量50
7
8private static boolean gCheckRecycle = true;//默认queueMessage的时候mQuitting=true的时候会回收
显然Message属性会持有一个next,spool是消息队列的头,属于链表
i) obtain
1//Message.java#obtain获取方法
2public static Message obtain() {
3 synchronized (sPoolSync) {
4 if (sPool != null) {
5 Message m = sPool;
6 sPool = m.next;
7 m.next = null;
8 m.flags = 0; // clear in-use flag
9 sPoolSize--;
10 return m;
11 }
12 }
13 return new Message();
14}
方法拆解如下,类似上述的消息机制:
一般调用方式为如下所示,基本囊括了平时使用的发送消息的方法,一般我们自己使用的时候,也要多使用obtainMessage方法
1@NonNull
2public final Message obtainMessage()
3{
4 return Message.obtain(this);
5}
6@NonNull
7public final Message obtainMessage(int what)
8{
9 return Message.obtain(this, what);
10}
11@NonNull
12public final Message obtainMessage(int what, @Nullable Object obj) {
13 return Message.obtain(this, what, obj);
14}
15public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
16 Message msg = Message.obtain();
17 msg.what = what;
18 return sendMessageDelayed(msg, delayMillis);
19}
20private static Message getPostMessage(Runnable r) {
21 Message m = Message.obtain();
22 m.callback = r;
23 return m;
24}
ii) recycleUnchecked
enqueMessage调用的mQuitting中的msg.recycle(),调用到这个方法,会将sPoolSize增加。这个方法用到的都是quit方法中直接或间接调用recycleUnchecked,不需要手动去调用该方法。
1//Message.java#recycleUnchecked回收方法
2@UnsupportedAppUsage
3void recycleUnchecked() {
4 // Mark the message as in use while it remains in the recycled object pool.
5 // Clear out all other details.
6 flags = FLAG_IN_USE;
7 what = 0;
8 arg1 = 0;
9 arg2 = 0;
10 obj = null;
11 replyTo = null;
12 sendingUid = UID_NONE;
13 workSourceUid = UID_NONE;
14 when = 0;
15 target = null;
16 callback = null;
17 data = null;
18
19 synchronized (sPoolSync) {
20 if (sPoolSize < MAX_POOL_SIZE) {
21 next = sPool;
22 sPool = this;
23 sPoolSize++;
24 }
25 }
26}
方法拆解如下,这个Message加入消息池的方式是头部添加。
这里边唯一和消息队列的区别是,消息队列是先进先出,这里是先进后出,这个消息池类似压栈的过程。
退出looper方法
有两种退出Looper,quitSafely和quit,最终调用到MessageQueue中的quit方法,对应true和false
1void quit(boolean safe) {
2 if (!mQuitAllowed) {
3 //ui主线程是不允许退出的
4 throw new IllegalStateException("Main thread not allowed to quit.");
5 }
6
7 synchronized (this) {
8 if (mQuitting) {
9 return;
10 }
11 mQuitting = true;
12
13 if (safe) {
14 //只移除所有尚未执行的消息,不移除时间戳等于当前时间的消息
15 removeAllFutureMessagesLocked();
16 } else {
17 //暴力移除所有消息
18 removeAllMessagesLocked();
19 }
20
21 // We can assume mPtr != 0 because mQuitting was previously false.
22 nativeWake(mPtr);
23 }
24}
拆解quit方法,主要做了以下事
1)设置标记位mQuitting=true
2)移除所有消息(安全退出和强制退出)
3)唤醒在next中的nativePollOnce,在next方法中通过if (mQuitting)返回了null,回到Looper.loop方法中。Looper.loop方法中获取的msg为null,最终也退出Looper,完成整一个退出流程。
重新新建looper方法
有退出就有重新建的方法,
1//Looper.java#prepare
2public static void prepare() {
3 prepare(true);
4}
这里的prepare执行,是可以进行退出操作,而Ui主线程中是在ActivityThread中执行prepareMainLooper,这个looper是不可以退出的,即只能在子线程中完成可以退出的looper。
举例说明
1//子线程总如何使用Handler的例子
2private Looper mLooper;
3private void initHandler() {
4 new Thread(new Runnable() {
5 @SuppressLint("HandlerLeak")
6 @Override
7 public void run() {
8 //子线程开启一个Looper
9 mLooper = Looper.prepare();
10 handler = new Handler(Looper.myLooper(), new Handler.Callback() {
11 @Override
12 public boolean handleMessage(Message msg) {
13 Log.d(TAG, "->>>handlermessage");
14 if (null != mLooper)
15 {
16 //退出looper
17 mLooper.quit();
18 }
19 return true;
20 }
21 });
22 //执行Looper.loop,即开启消息队列的无限循环模式
23 Looper.loop();
24 }
25 }).start();
26}
Handler流程图
根据Looper,Handler,Message和MessageQueue可以得知Handler流程,这个流程是典型的的生产者消费者模式。
问题回答
1.一个线程有几个handler
一个,ui主线程中可以有activty,service,他们都可以使用UI主线程
2.一个线程有几个looper,如何保证
一个looper。java中prepare函数如果存在threadlocal那么会抛出异常不能new looper,所以一个threadlocal对应一个looper。这个会在下一节重点分析。
3.Handler会出现内存泄漏问题吗,怎么解决内存泄漏
会存在,原因是由于msg.target=this,持有了handler对象。另外,Looper对象在主线程中,整个生命周期都是存在的,MessageQueue是在Looper对象中,也就是消息队列也是存在在整个主线程中,因此Handler又是和Activity存在强引用关系。即内部类持有外部类引用。this调用ondestory并不能释放,jvm认为当前this正在被handler使用。
解决:使用静态内部类弱引Activity、清空消息队列
4.子线程如何使用Handler
子线程使用handler的例子在上面已经举例说明,不再重复。
参考
[1] 小呆呆666, 万字图文,带你学懂Handler和内存屏障, 2021.
[2] zcbiner, Handler机制与生产者消费者模式, 2018.
[3] 业志陈, 一文读懂 Handler 机制, 2021.
[4] Ahab, 介绍一下 Android Handler 中的 epoll 机制, 2020.