Android 线程通信
Posted danfengw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 线程通信相关的知识,希望对你有一定的参考价值。
Handler相关
代码分析:
Handler 持有 Looper 和 MessaegeQueue
可以在子线程创建handler吗
可以,需要调用Looper.prepare和Looper.loop
主线程的Looper和子线程的Looper的区别
子线程的Looper可以退出,主线程的Looper不可以退出
Looper 和MessageQueue 有什么关系
一对一的关系
消息循环过程是怎么样的
Looper.loop() 通过 for (;😉 死循环,不断从messageQueue中获取消息,并进行处理
消息是怎么发送的
handler.sendMessage()发送消息后加入到消息队列中
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);
消息是怎么处理的
handler.dispatchMessage()
public void dispatchMessage(@NonNull Message msg)
if (msg.callback != null)
handleCallback(msg);
else
if (mCallback != null)
if (mCallback.handleMessage(msg))
return;
handleMessage(msg);
handler 的消息延时是怎么实现的
(1)消息队列按照消息触发时间排序
(2) 设置epoll_wait 的超时时间,使其在特定时间唤醒
是发送延时 还是消息处理延时?
处理延时
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis)
if (delayMillis < 0)
delayMillis = 0;
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
插入消息队列,从下方代码可以看出来,消息会根据时间when进行排队
boolean enqueueMessage(Message msg, long when)
…………
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;
…………
延时精度怎么样
延时精度不高
handler 内存泄漏解决(2点)
1 有延时消息,要在 Activity 销毁的时候移除 Messages
2 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者 Activity 使用弱引用。
IdleHandler
IdleHandler 说白了,就是 Handler 机制提供的一种,可以在 Looper 事件循环的过程中,当出现空闲的时候,允许我们执行任务的一种机
制。
MessageQueue 提供了 add/remove IdleHandler 的方法,是否需要成对使用?
不需要,IdleHandler的queueIdle()返回false时,只会执行一次,并被移除
当 mIdleHanders 一直不为空时,为什么不会进入死循环?
只有在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;
pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;
IdleHandler 的 queueIdle() 运行在那个线程?
陷进问题,queueIdle() 运行的线程,只和当前 MessageQueue 的 Looper 所在的线程有关;
子线程一样可以构造 Looper,并添加 IdleHandler;
主线程的IdleHandler 如果进行耗时操作会怎样?
1.Thread.sleep(n) n > 10 页面会卡死,但不会崩溃,如果页面有动图,则动图变为静态图(视频未尝试,猜测也会处于静止状态) 但此时如果点击页面按钮,则会无响应进入anr,如果不点击,n秒过后恢复正常。
2.网络请求: 不会崩溃,但会报错 IdleHandler threw exception android.os.NetworkOnMainThreadException
3.文件写入本地: 成功。测试所用文件为小文件,大文件猜测也一样,同sleep,中途不点击页面无事,点击anr。
哪些地方用到了IdleHandler
GcIdler ,Idler
适用场景–延迟执行、批量任务(任务密集、只关注最终结果)
(1)Activity启动优化(加快App启动速度):onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间
(2)想要在一个View绘制完成之后添加其他依赖于这个View的View,当然这个用View#post()也能实现,区别就是前者会在消息队列空闲时执行
(3)发送一个返回true的IdleHandler,在里面让某个View不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作
(4)一些第三方库中有使用,比如LeakCanary,Glide中有使用到,具体可以自行去查
主线程进入Loop循环为什么没有anr
public static final void main(String[] args)
...
//创建Looper和MessageQueue
Looper.prepareMainLooper();
...
//轮询器开始轮询
Looper.loop();
...
(1)ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了
(2)anr是应用没有在规定的时间内完成AMS指定的任务,而不是因为主线程loop循环,而是因为主线程中有耗时任务
消息屏障
消息种类
普通消息
屏障
异步消息
ps:屏障消息插入后会block住普通消息,优先处理异步消息,这样的异步消息类似于:vsyc信号刷新界面之类。
如何插入消息屏障的
MessageQueue
private int postSyncBarrier(long when)
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this)
final int token = mNextBarrierToken++;
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;
(1)消息屏障的target是空的
(2)消息屏障也是有时间戳的,并且也会按照时间戳排序
(3)消息屏障插入队列后,并没有唤醒线程,插入队列后会返回一个token,这个token主要是用来取消消息屏障的时候,需要根据这个token从消息队列中查找。
(4)此外postSyncBarrier方法是private的
(5)我们平时发送的消息是不允许target为空的
删除屏障:
public void removeSyncBarrier(int token)
删除屏障的时候需要唤醒线程
屏障的处理:
MessageQueue 的 next() 调用的时候,
如果第一条消息是屏障会block住后面的普通消息,不会block异步消息,后面如果有异步消息,异步消息如果到了时间了,就执行,否则就等待。
插入消息队列的时候
MessageQueue 的enqueueMessage方法
如果插入到消息队列头,如果当前线程是休眠的,就要唤醒他
如果没有插入到队列头,如果当前线程是休眠的,并且队列头是屏障,并且当前消息是最早的一条异步消息,就要唤醒线程。
消息队列是空的时候,插入一个屏障,会触发idleHandler吗
不会触发idleHandler,插入一个屏障是不会唤醒线程的。
如果删除了屏障,消息队列空了,会触发idleHandler吗
不会,因为idleHandler已经被调用过了,就不会触发了。
如果消息队列只有一个屏障消息,插一个普通消息会idleHandler吗
有可能,主要看屏障的时间到了没有
如果消息队列只有一个屏障消息,插一个异步消息会idleHandler吗
有可能,同上
ThreadLocal
java 的ThreadLocal 与 Android的ThreadLoacal
以上是关于Android 线程通信的主要内容,如果未能解决你的问题,请参考以下文章
Java多线程-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier