本文主要三点目的
第一.面试18连问
第二.epoll机制源码三分钟搞定
第三.手写可以跨进程的Handler通信方案,没错跨进程。既可以线程通信,又可以跨进程通信**
面试连环18连问:
1.Handler被设计出来的原因?有什么用?
2.Handler,Message,MessageQueue,Looper的关系,一对多。相互是如何创建的?
3.Handler,Message,MessageQueue,Looper的作用,一个流程
4.消息如何存放的?消息如何消费的?
5.Handler:为什么能切换线程
Message:什么数据结构?对象池?
MessageQueue:什么数据结构?
Looper:如何保证唯一
6.为什么死循环不会ANR---Looper
7.延时消息如何处理的-------messageeQueue
8.唤醒和阻塞,Epoll机制是如何?
9.内存泄漏的链路
10.消息屏障是干嘛的?
11.IdleHandler是啥?有什么使用场景?
1.Handler基本使用
一般情况下,在主线程中我们绑定了Handler,并在事件触发上面创建新的线程用于完成某些耗时的操作,当子线程中的工作完成之后,会对Handler发送一个完成的信号,而Handler接收到信号后,就进行主UI界面的更新操作。
Handler被设计出来的原因?有什么用?
Handler机制主要为了解决以下2个问题
- 不要阻塞UI线程;
- 不要在UI线程之外访问UI组件,即不能在子线程访问UI组件,只能在UI线程访问。
第一步: 线程:先把线程构建出来
new Thread(){
@Override
public void run() {
Looper.prepare(); for(int i=0;i<1000;i++){
Log.d("peng","i="+i);
}
Looper.loop();
}
}.start();
2、Looper
问题: Looper是怎么创建的?
在线程里面创建的。然后保存在ThreadLocal中。
对于Looper主要是prepare()和loop()两个方法。
首先看prepare()方法:主要是通过ThreadLocal绑定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)); }
同一个线程中不能多次调用prepare方法,否则会抛出异常。如上面
ThreadLocal :在Looper中构建出来的
public final class Looper { private static final String TAG = "Looper"; // sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocalsThreadLocal = new ThreadLocal ();
ThreadLocal是一个静态的,注意。而且是final。final具体使用
https://blog.csdn.net/mysimplelove/article/details/79568261
ThreadLocal保证了一个线程中只有一个Looper实例
ThreadLocal里面是一个map,存放key和value
ThreadLocal的作用:在线程里面保存数据
线程是默认没有Looper的,线程需要通过Looper.prepare()、绑定Handler到Looper对象、
Looper.loop()来建立消息循环looper相当于事件驱动,心跳机制!
- MessageQueue: MessageQueue是怎么创建出来的?
通过looper
MessagerQueue:单链表的数据结构
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread(); }
在构造方法中,创建了一个MessageQueue(消息队列)。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread(); }
Loop方法:里面一个For循环,阻塞队列,不断的取消息
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is. Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity(); for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}final long traceTag = me.mTraceTag;
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
looper方法:调用dispatchMessager方法:dispatchMessager方法再调用handlemessage方法
msg.target:就是Handler对象。看到message,持有handler的引用
Message 源码:Message的数据结构
public final class Message implements Parcelable {@UnsuportedAppUsage
/package/ Handler target;
public int what; public int arg2; // sometimes we store linked lists of these things
/package/ Message next;
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Looper主要作用:
1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。
好了,我们的异步消息处理线程已经有了消息队列(MessageQueue),也有了在无限循环体中取出消息的哥们,现在缺的就是发送消息的对象了,于是:Handler登场了。
Handler是怎么创建的:手动创建,自己创建的
private Handler handler = new Handler(Looper.getMainLooper()) {
public Handler(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());
}
} mLooper = Looper.myLooper();
if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()");
} mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async; }
发送消息:把消息添加到了MessageQueue队列里面,把MSG添加到了MessagerQueue里面。所有的消息都是通过sendMessageAtTime();
public boolean sendMessageAtTime(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); }
MessageQueue的enqueueMessage方法:存消息源码
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) { 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) {
nativeWake(mPtr);
}
}
return true; }boolean hasMessages(Handler h, int what, Object object) {
if (h == null) {
return false;
}
存放消息:
注释1:p是队列头部,满足3个条件则把消息放到队列头部
1.队列中没有消息,p==null 2.入队的消息没有延时 3.入队的消息的延时比队列头部的消息延时短
注释2:消息插入到链表中,需要移动链表,对比消息的延时,插入到合适的位置
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}synchronized (this) { if (mQuitting) { return;
}
mQuitting = true; if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
MessageQueue的next()方法
面试官: 最新的一条消息, 还没到时间如何触发?
msg != null 我们看下这部分,如果当前时间小于头部时间(消息队列是按时间顺序排列的)
那就更新等待时间nextPollTimeoutMillis,等下次再做比较
如果时间到了,就取这个消息并返回。
如果没有消息,nextPollTimeoutMillis被赋为-1,这个循环又执行到nativePollOnce继续阻塞!!
什么时候唤醒?
第一种答案:nativePollOnce到时间了自己唤醒
唤醒有2个
自动唤醒:nativePollOnce
手动唤醒:nativeWakeUp
Android 中 MessageQueue 的 nativePollOnce - just_yang - 博客园 (cnblogs.com)
第二种答案: 和sleep一样,阻塞一定得时间!!!!!
阻塞时间由timeoutMillis来指定———————————————那什么时候会阻塞呢?两种情况:
1、有消息,但是当前时间小于消息执行时间,也就是代码中的这一句:
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
这时候阻塞时间就是消息时间减去当前时间,然后进入下一次循环,阻塞。
2、没有消息的时候,也就是上述代码的最后一句:
if (msg != null) {}
else {
// No more messages.
nextPollTimeoutMillis = -1;
}
-1就代表一直阻塞。
epoll机制是怎么样子的?
1.looper方法里面 没有消息的时候,调用nativePollOnce(),休眠,block阻塞 。直到添加新消息
也就是获取下一个消息,这个方法可能会阻塞,当消息队列没有消息的时候.直到有消息,然后就会被唤醒,然后继续取消息.
//当消息队列为空时,这里会导致阻塞,直到有消息加入消息队列,才会恢复//这里是native方法,利用的是Linux管道(Pipe)机制阻塞nativePollOnce(ptr, nextPollTimeoutMillis);
2.阻塞之后什么时候唤醒。 消息来的时候,调用nativeWake方法唤醒next()方法。
将Message添加到队列时,框架会调用enqueueMessage()方法,里面也有个死的for循环,该方法不仅会将消息插入队列,还会调用native static void nativeWake(long)
Handler如果没有消息处理是阻塞的还是非阻塞的?阻塞
总结:存消息,需要唤醒,msgque中的方法 。取消息。looper。需要阻塞,休眠。他们都是通过native.
深度底层:
如果需要唤醒队列的话。 nativePollOnce和nativeWake的核心魔力发生在native(实际上是C ++)代码中。 Native MessageQueue使用名为epoll的Linux系统调用,该调用允许监视IO事件的文件描述符。 nativePollOnce在某个文件描述符上调用epoll_wait,而nativeWake写入描述符,这是IO操作之一,epoll_wait等待。然后内核从等待状态中取出epoll等待线程,并且线程继续处理新消息。如果您熟悉Java的Object.wait()和Object.notify()方法,您可以想象nativePollOnce是Object.wait()和NativeWake for Object.notify()的粗略等价物,因为它们的实现完全不同:nativePollOnce使用epoll,Object.wait()使用futex Linux调用。值得注意的是,nativePollOnce和Object.wait()都不会浪费CPU周期,因为当线程进入任一方法时,它会因线程调度而被禁用。如果这些方法实际上浪费了CPU周期,那么所有空闲应用程序将使用100%的CPU,加热并降低设备的速度。
源码如下:
Message next() { final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}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) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is 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;
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 (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);
}// Run the idle handlers.
// We only ever reach this code block during the first iteration. 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) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}if (!keep) { synchronized (this) { mIdleHandlers.remove(idler);
}
}
}
移除消息:通过MessageQueue
public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null); }
实例写法:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler2 = new Handler();
Looper.loop();}
}).start();
[图片上传失败...(image-1fb98f-1650683445363)]
流程总结:
准备过程:prepare===创建Looper对象,创建messageQueque对象。
使用的时候。发送消息的流程:handler.sendmesssage()------messageque存放msg
取消息的流程:得到looper------得到messageque-------得到msg,然后通过handle调用dispatch方法。然后调用handler方法,我们需要重写的
这几个角色是如何协同工作的呢?简单概括为下面四个步骤:
- handler发送消息到message queue,这个消息可能是一个message,可能是一个runnable
- looper负责从message queue取消息
- looper把消息dispatch给handler
- handler处理消息(handleMessage或者执行runnable)
handler和looper的关系有点类似于生产者和消费者的关系,handler是生产者,生产消息然后添加到message queue;looper是消费者,从message queue取消息****。(生产者消费者模式)
[图片上传失败...(image-8001d8-1650683445359)]
总结原理:
1、首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
2、Looper.loop()会让当前线程进入一个无限循环,不端从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。
3、Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。
4、Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。
5.报错Can't create handler inside thread that has not called Looper.prepare(),没有调用Looper.loop().UI线程默认调用了 ,在ActivityThread类里面的Looper.prepare()和Looper.loop()方法。Looper.prepare()和Looper.loop()方法。
6.面试:****Handler和Looper是什么关系?
hander创建的时候需要传入一个looper,looper在哪,handler在哪。默认不传,在哪个线程new,就是哪个线程的Looper
Looper looper = Looper.myLooper(); handler = new Handler(looper) {
handler构造方法里面的looper为什么不直接new?不能保证唯一性
handler不是独立存在的,一个handler,一定有一个专属的线程,一个消息队列,和一个looper与之关联。
Handler的作用就是:调度消息和runnable对象去被执行;使动作在不同的线程中被执行。
handler不仅可以分发消息,还可以分发runable。把runable封装成消息
public Handler(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");
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException
("Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
面试18连问:
**1.****Handler造成Activity泄漏****,用弱引用真的有用么? **
产生原因:第一内部类持有activity的引用 ,
第二:引用链关系:ThreadLocal---->Looper------> messageque------->msg---------handler----activity
handler造成内存泄漏是因为在Activity销毁的时候还有未执行完的任务
解决办法:
静态static可以解决内存泄漏
使用弱引用也可以解决内存泄漏,但是需要等到handler的中任务都执行完,才会释放activity内存,不如直接static释放的快
为何handler要定义为static的同时,还要用WeakReference 包裹外部类的对象?
这是因为我们需要使用外部类的成员,可以通过"activity. "获取变量方法等,如果直接使用强引用,显然会导致activity泄露。
handler造成内存泄漏有 两种方案:一种是业务逻辑上,在activity销毁的时候移除所有未执行的任务。
一种是从GC上,通过static的Handler或者弱引用解决。但是单独的使用弱引用性能不是太高。
最好的办法:要把handler也置空,比如在线程延迟3000之后延时消息。
问题: 为什么内部类会持有外部类的引用?
2.为什么Loop里面的Looper()方法用死循环?
要处理4大组件里面的事情,并不希望立马退出。
3.****主线程的Looper.loop一直在无限循环是吗?****为什么Looper中的Loop()方法不能导致主线程卡死?
主线程的死循环一直运行是不是特别消耗CPU资源呢?
1).并不是,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质是同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
2).耗时操作本身并不会导致主线程卡死, 导致主线程卡死的真正原因是耗时操作之后的触屏操作, 没有在规定的时间内被分发。
Looper 中的 loop()方法, 他的作用就是从消息队列MessageQueue 中不断地取消息(调用messageQue的next()方法), 然后将事件分发出去。
3).ANR和死循环没有关系。2个完全不是个东西。
只要有消息处理,没有消息处理会阻塞。
问题:阻塞不会导致ANR吗?
不会。ANR和阻塞2个事情,ANR是因为没用 消息及时处理。ANR有消息没用及时处理:因为埋炸弹的问题
4.handler是怎么做到线程的切换的?
Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了。
服务端开发里面的消息队列本身也是这个原理,队列对所有线程都是可见的,大家都可以往里面 enqueue 消息
用了主线程都looper。 Handler 是在他关联的 Looper 对应的线程中处理消息的。(主线程的looper)
真正的原因:messagequeue的,队列可以实现内存共享。然后looper觉得是发哪个线程!
因此 Looper 所处的线程也就决定了你 Handler 提交任务执行所在的线程。
5.一对多的关系和多对一的关系,它们之间的关系?
一个线程有多少个handler?
一个线程有多少个looper?
如何保证只有一个looper?
一个handler,一个messageque,可以对应多个线程
[图片上传失败...(image-9519cc-1650683445375)]
既然有多对一,或者一多的问题,多个handle可以发消息给msgqueue。怎么保证消息安全的?
加了锁,synizch();存和取都加了。
6.发送延时消息是怎么处理的
handler.postDelay并不是先等待一定的时间再放入到MessageQueue中,而是直接进入MessageQueue,以MessageQueue的时间顺序排列和唤醒的方式结合实现的。
延时消息的存在就让这个队列有些特殊性了,并不能完全保证先进先出,而是需要根据时间来判断,所以Android中采用了链表的形式来实现这个队列,也方便了数据的插入
根据消息队列入队规制,如果队列中没消息,那么不管要入队的消息有没有延时,都放到队列头。如果队列不空,那么要跟队列头的消息比较一下延时,如果要入队的消息延时短,则放队列头,否则,放到队列中去,需要移动链表。
Message是一个单链表结构,所以我们可以看到,当第一个元素是空的时候,加入的msg是在队首。当when是0的时候,前边已经说了,只有在sendMessageAtFrontOfQueue方法时才会传入,也需要加入队首。当这个msg的when比队首的msg的when小时(前边已经分析过了,when表示这个msg需要执行的时间点),也需要加入队首。
如果不是这三种情况,就对Message这个链表进行遍历,根据msg的when找到对应的位置插入即可。
如果队列中只有这个消息,那么消息不会被发送,而是计算到时唤醒的时间,先将Looper 阻塞,到时间就唤醒它。但如果此时要加入新消息,该消息队列的对头跟delay 时间相比更长,则插入到头部,按照触发时间进行排序,队头的时间最小、队尾的时间最大
总结:
sendMessageDelayed是通过阻塞来达到了延时发送消息的结果,那么会不会阻塞新添加的Message?
Handler在发送消息的时候,MessageQueue里的消息是按照发送时间点从小到大排列的,
如果最近的Message未到达发送的时间则阻塞。
新加入的数据会根据时间点的大小判断需要插入的位置,同时还需要判断是否需要唤醒线程去发送当前的队首的消息。
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
### 7.如果在当前线程内使用Handler postdelayed 两个消息,一个延迟5s,一个延迟10s
然后使当前线程sleep 5秒,以上消息的执行时间会如何变化?
答:照常执行
扩展:sleep时间<=5 对两个消息无影响,5< sleep时间 <=10 对第一个消息有影响,第一个消息会延迟到sleep后执行,sleep时间>10 对两个时间都有影响,都会延迟到sleep后执行。
总结:如果sleep的时间<=延时的时间,是没有影响的。大于就有影响
问题: 假如发了一个延时消息5s,然后这个时间没有其他消息了。怎么处理?
通过延时,阻塞。
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue; }
聊下Handler postDelay的底层原理。如果灭屏前调用SystemClock.uptimeMillis然后灭屏,等10秒亮屏,在打印SystemClock.uptimeMillis,这两个的时间差是10s吗,为什么。
举例:
1).先 一个消息sleep.3s, 然后另外一个消息postDelay1s ,
分析: 会先sleep3s 。然后再进行操作。发送延时1s的消息,但是messageque取的时候,有一个条件:
假如开始开机时间是1s。
因为msg.when=2s
然后运行next的方法,开机时间是1+3=4s. 然后现在时间(now)>执行时间(msg.when)---->把开始没有执行完的消息,立马执行
[图片上传失败...(image-9c6cd5-1650683445357)]
https://www.imooc.com/article/21997
8.消息屏障
Handler的Messgae种类分为三种:
- 普通消息:同步消息
- 异步消息
- 屏障消息
其中普通消息又称为同步消息,我们平时发的消息基本都是同步消息
一般来说,MessageQueue里面的所有Message是按照时间从前往后有序排列的。
同步屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。
不过异步消息却例外,屏障不会挡住异步消息,
因此可以认为,屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。
同步屏障: 往消息队列插入一个同步屏障消息,这时候消息队列中的同步消息不会被处理,而是优先处理异步消息。这里很好理解,
同步屏障的处理代码在MessageQueue
的next
方法:
删除屏障消息的方法很简单,就是不断遍历消息队列,知道找到屏障消息,退出循环的条件有两个,一是p.target == null,说明是屏障消息,二是p.arg1 == token,也说明p是屏障消息,因为在屏障消息入队的时候,设置过 msg.arg1 = token。找到屏障消息后,把它从消息队列中删除并回收。调用MessageQueue.removeSyncBarrier 方法可以移除指定的消息屏障
所以消息屏障和异步消息的作用很明显,在设置消息屏障后,异步消息具有优先处理的权利。
这时候我们回顾将消息添加到消息队列中时,可以发现,其实并不是每一次添加消息时,都会唤醒线程。 当该消息插入到队列头时,会唤醒该线程; 当该消息没有插入到队列头,但队列头是屏障,且该消息是队列中 靠前的一个异步消息,则会唤醒线程,执行该消息;
同步屏障和异步消息有具体的使用场景吗?
消息屏障的应用:
1).UI相关的操作优先级最高,比如消息队列有很多没处理完的任务,这时候启动一个Activity,当然要优先处理Activity启动,然后再去处理其他的消息,同步屏障的设计堪称一绝吧。
2).view刷新机制
3)关于Handler有一个需求,一个消息要立刻执行,要怎么做
9.可以在子线程直接new 一个Handler 吗?怎么做?
主线程为什么不用?
不可以,因为在主线程中,Activity 内部包含一个Looper 对象,它会自动管理Looper,处理子线程中发送过来的消息。而
对于子线程而言,没有任何对象帮助我们维护Looper 对象,所以需要我们自己手动维护。所以要在子线程开启Handler 要先创建Looper,并开启Looper 循环
`
new Thread(){@Override
public void run() {
Looper.prepare(); for(int i=0;i<1000;i++){
Log.d("peng","i="+i);
}
Looper.loop(); handler.sendEmptyMessageDelayed(2000,2000);//去更新ui
}
}.start(); }
private Handler handler = new Handler(Looper.getMainLooper()) {@Override
public void handleMessage(Message msg) {
super.handleMessage(msg); //通过时间计算得到 if (remainTime < 1) {
handler.removeCallbacksAndMessages(null);
return; }
remainTime = remainTime - 1;
progressValue = (int) ((max_time - remainTime) * 100 / max_time);
Log.d("peng", "handleMessage" + progressValue + "remainTime:" + remainTime);
if (progressValue < 101) {
handler.sendEmptyMessageDelayed(MSG_WHAT, 1000);
updateProgress();
}
}
};
这2个looper,到底用哪个?用主looper
在线程切换中,不会
Looper.prepare(); for(int i=0;i<1000;i++){
Log.d("peng","i="+i);
}
Looper.loop();
10.子线程中 Toast,showDialog的方法。(和上面一样!)
android如何在子线程中弹出ToasT`
new Thread(){
@Override
public void run() {
try {
Looper.prepare();
Toast.makeText(getApplicationContext(), "备份成功", 0).show();
Looper.loop();
} catch (Exception e) {
Looper.prepare();
Toast.makeText(getApplicationContext(), "备份失败", 0).show();
Looper.loop();
}
}
}.start();
看源码:用这个可以,但是有内存泄露导致
Looper.prepare();解决办法:在子线程中添加了Looper.prepare()以及Looper.loop()方法后,Toast的报错没有了,并且能够正常弹出toast。 子线程显示Toast是没有问题的,但是Toast是一个比较特殊的UI,跟系统有关系。 Toast本质上是一个window,跟activity是平级的,而我们平时所说的非UI线程不能更新UI,是因为在ViewRootImpl里面会有线程检查,checkThread只是Activity维护的View树的行为。 结果证明在子线程里面是可以弹Toast。那么问题来了,显示Toast是UI操作是毋庸置疑的,那么就是我一直认为的子线程不能进行UI操作的认识有误区? 可以在子线程中new Handler吗? 1).要先prepare一下。然后looper循环 2).看demo,发现有异步的问题。2个不在同一线程,handler和线程。用syched实现。比如可以用系统的handlerThread.看源码 ##### 主线程与子线程使用Handler的区别 答:主线程与子线程在使用Handler时主要区别在于子线程在new Handler之前需要保证子线程有Looper对象,否则会抛出异常, 也就是在new Handler之前需要先执行Looper.prepare方法,初始化一个Looper对象,存到ThreadLocal中。同一个线程中,Looper.prepare方法不能执行多次,否则也是会抛出异常的,因为如果你初始化多次的话,存入到ThreadLocal中,就不知道到底该用哪一个Looper对象了。 主线程中不需要Looper.prepare是因为在APP启动的时候,ActivityThread的main方法中执行了Looper.getMainLooper方法,初始化了一个Looper对象。 大家经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。 主线程的looper在哪里启动的? ActivityThread的main()函数! ActivityThread 的动力是什么? 11.子线程一定不能更新UI?线程中settextView,一定会报错吗? [https://www.jianshu.com/p/5a1af8e95fd5](https://www.jianshu.com/p/5a1af8e95fd5) 非常好。 在onCreate中用子线程更新UI不蹦,而延迟之后就发生车祸。 在viewRootIml源码中的 texview里面的settxt();Looper.loop();
checkForRelayout();requestLayout(); invalidate(); } else { // Dynamic width, so we have no choice but to request a new // view layout with a new text layout. nullLayouts(); requestLayout(); invalidate();view里面的public void requestLayout() { if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logic if this is the view requesting it, // not the views in its parent hierarchy ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null && viewRoot.isInLayout()) { if (!viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; } mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; } }ViewRootImpl方法里面:// 如果当前线程不是主线程就抛出异常void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }ViewRootImpl的checkThread方法点进去看。 ``` void checkThread() { // 如果当前线程不是主线程就抛出异常 if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } ``` ViewRootImpl中调用的地方是requestLayout方法 ``` @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { // 检测当前线程 checkThread(); mLayoutRequested = true; // view树遍历 scheduleTraversals(); } } ``` 其实原因很简单,既然是用ViewRootImpl的checkThread方法来检查线程,那么就说明在onCreate的时候ViewRootImpl还没被创建,所以不会走checkThread方法,自然就不会报错了。 最后的结果是ViewRootImpl是在onResume之后创建的,真相大白! 如果检测线程要优先执行invalidate快与requestLayout();那么更新ui没有问题如果先requestLayout().里面调用了线程检测,在invalidate就会有问题 或者:ViewRootImpl没有创建 既然是用ViewRootImpl的checkThread方法来检查线程,那么就说明在onCreate的时候ViewRootImpl还没被创建,所以不会走checkThread方法,自然就不会报错了 每次view刷新的时候都会去检测一次线程 **12.Android 线程A与线程B如何通信的 ** 类似子线程往主线程发消息一样,其实android中线程通信无非就是handler和looper的操作。 需要注意的就是要loop.prapare()和looper.loop()。不调用Loop.loop()方法的话,是收不到消息的, **demo: ** **private Handler handler; private void testHandler() { new Thread() { @Override public void run() { super.run(); Looper.prepare(); if (handler == null) { handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); Log.d("MainActivity", "handleMessage" + Thread.currentThread().getName()); } }; } Looper.loop(); } }.start(); new Thread() { @Override public void run() { super.run(); try { sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } Log.d("MainActivity", "sendEmptyMessageDelayed" + Thread.currentThread().getName()); handler.sendEmptyMessageDelayed(222, 2000); } }.start(); }** handler原理,主线程发送message给子线程? 原理:通过handler。哪个线程要收到,就通过哪个线程发送 [Android 使用handler实现线程间发送消息 (主线程 与 子线程之间)、(子线程 与 子线程之间)_良秋的专栏-CSDN博客](https://blog.csdn.net/a740169405/article/details/47070457) 13.handler运行在哪个线程? 看looper在哪个线程,handler用哪个? 因为:创建handler的时候传了一个looper **14.handler.post(Runnable) runnable是如何执行的? **handler机制,调用handler.post(Runnable)此Runnable运行在什么线程? Runuable什么时候执行,在handler.post,run的时候运行 这个要看handler,handler要看looper。模式直接new,是在主线程 开启的runnable会在这个handler所依附线程中运行,而这个handler是在UI线程中创建的,所以自然地依附在主线程中了。 postDelayed(new Runnable()) 而没有重新生成新的 New Thread() **Runable是怎么工作的?** 最终它们都会调用到Handler#sendMessageAtTime(Message msg, long uptimeMillis [图片上传失败...(image-49bb98-1650683445361)] Handler 的 post(Runnable) 与 sendMessage 有什么区别 所以post(Runnable) 与 sendMessage的区别就在于后续消息的处理方式,是交给`msg.callback`还是 `Handler.Callback`或者`Handler.handleMessage`。 **2种handler的写法有什么不一样?private Handler handler2=new Handler(new Handler.Callback() { @Override public boolean handleMessage(@NonNull Message message) { return false; } }); private Handler handler=new Handler(){ @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); } };** **## Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一样?为什么这么设计? 插件话用到了这个玩意。 源码分析:/** * Handle system messages here. */ public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }** * 如果为true,则不再执行Handler.handleMessage * 如果为false,则两个方法都要执行。 handler的Callback和handlemessage都存在,但callback返回true handleMessage还会执行么 Dispathch方法: 可以看到,除了在Handler#handleMessage(...)中处理消息外,Handler 机制还提供了两个 Callback 来增加消息处理的灵活性。具体来说,若设置了Message.Callback则优先执行,否则判断Handler.Callback的返回结果,如果返回false,则最后分发到Handler.handleMessage(...) **15.在子线程,如果消息轮询完了。线程处于什么状态?****Looper中的quitAllowed字段是啥?有什么用?** 要通过looper。quit()。清空消失,不然一直处于阻塞状态?looper有个for循环,处于block状态。然后子线程一直在运行,容易导致内存泄漏。 调用quitAllowed().唤醒。然后可以退出。 比如:ThreadHandler() * 主线程中,一般情况下肯定不能退出,因为退出后主线程就停止了。因为系统AMS等等。要处理消息,不能停止。所以是当APP需要退出的时候,就会调用quit方法,涉及到的消息是EXIT_APPLICATION,大家可以搜索下。* 子线程中,如果消息都处理完了,就需要调用quit方法停止消息循环。 ### 16.IdleHandler是啥?有什么使用场景? IdleHandler应用场景:leakcanery,启动流程里面:ActivityThread里面的handlerResumeActivity()方法! **@Override public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, r.nextIdle = mNewActivities; mNewActivities = r; if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r); Looper.myQueue().addIdleHandler(new Idler()); }** 通过源码分析: 如果callback为空和不为空的话public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }使用当前线程的MessageQueue.addIdleHandler方法可以在消息队列中添加一个IdelHandler。 ``` MessageQueue messageQueue = Looper.myQueue(); messageQueue.addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { return false; } }); ``` 当MessageQueue 阻塞时,即当前线程空闲时,会回调IdleHandler中的方法; > 注:a,添加IdelHandler时,消息队列不为空,当消息处理完或者剩下消息还没到触发时间,会回调方法 b,当添加IdelHandler时,消息队列为空,则当时不会触发回调 当IdelHandler接口返回false时,表示该IdelHandler只执行一次, 批量任务,任务密集,且只关注最终结果 例如,在开发一个IM类型的界面时,通常情况下,每次收到一个IM消息时,都会刷新一次界面,但是当短时间内, 收到多条消息时,就会刷新多次界面,容易造成卡顿,影响性能,此时就可以使用一个工作线程监听IM消息,在通过添加IdelHandler的方式通知界面刷新,避免短时间内多次刷新界面情况的发生。 ok,综上所述,IdleHandler就是当消息队列里面没有当前要处理的消息了,需要堵塞之前,可以做一些空闲任务的处理。 常见的使用场景有:启动优化。 17.Message 可以如何创建?哪种效果更好,为什么? 参考回答:可以通过三种方法创建: 直接生成实例Message m = new Message 通过Message m = Message.obtain 通过Message m = mHandler.obtainMessage() 后两者效果更好,因为Android 默认的消息池中消息数量是10,而后两者是直接在消息池中取出一个Message 实例,这样做就可以避免多生成Message 实例。 消息怎么复用的?Message为什么没有被回收?msg复用原理 消息的享元模式,有一个消息池 Message是什么数据结构?链表,一直指向下一个。 消息机制里需要频繁创建消息对象(Message),因此消息对象需要使用享元模式来缓存,以避免重复分配 & 回收内存。 具体来说,Message 使用的是有容量限制的、无头节点的单链表的对象池: 为啥:对象池就一个实体,不是一个集合? 从代码中我们可以看到,Message的复用机制没有使用任何一种数据结构,如LinkedArrayList,而是通过Message对象内部的spool和next字段,通过指针的方式来进行对象管理,不得不说,是一种非常巧妙的设计方式,一来降低了设计复杂度,而且由于没有创建额外的数据容器来管理对象,减轻了内存的压力,实现了轻量化的目的。private static Message sPool;``` public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); } 复制代码 ``` 18.Handler如何保证线程安全的 答:在Handler发送消息时,会将Message存入MessageQueue消息队列中,即enqueueMessage方法,这个方法中,有一个synchronized(this){}的方法块,同时在Looper.loop()方法中的MessageQueue.next()方法中也是使用synchronized加锁的方式来保证存取Message的线程安全的。boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } synchronized (this) { if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } 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) { nativeWake(mPtr); } } return true; }19\. 问题:问在一个工作线程中创建自己的消息队例应该怎么做? 20. Android-AsyncQueryHandler 参考博客: [http://blog.csdn.net/guolin_blog/article/details/9991569](http://blog.csdn.net/guolin_blog/article/details/9991569) [http://blog.csdn.net/lmj623565791/article/details/38377229](http://blog.csdn.net/lmj623565791/article/details/38377229) [https://blog.csdn.net/star_nwe/article/details/115255882](https://blog.csdn.net/star_nwe/article/details/115255882)(牛逼,被封了) [https://blog.csdn.net/jdsjlzx/article/details/110525796](https://blog.csdn.net/jdsjlzx/article/details/110525796)(非常完美) 手写handler流程: 3步搞定private void main() { Looper.prepare();//创建全局唯一的主线程looper final com.handler.myapplication.Handler handler=new com.handler.myapplication.Handler(){ @Override public void handleMessager(com.handler.myapplication.Message message) { super.handleMessager(message); } }; new Thread(){ @Override public void run() { super.run(); //子线程发送消息,存入消息 com.handler.myapplication.Message message=new com.handler.myapplication.Message(); message.obj="dddd"; handler.sendMessage(message); } }.start(); Looper.loop();//取消息 }public class Looper { public Looper() { this.mQueue = new MessageQueue(); } final static ThreadLocalthreadLocal=new ThreadLocal<>(); public MessageQueue mQueue; public static void prepare() { if(threadLocal!=null){ Log.d("peng","prepare"); } threadLocal.set(new Looper()); } protected static Looper myLooper(){ return threadLocal.get(); } public static void loop(){ Looper looper=myLooper(); MessageQueue messageQueue=looper.mQueue; Message messageResult=null; while (true){ try { messageResult=messageQueue.next(); } catch (InterruptedException e) { e.printStackTrace(); } if(messageResult!=null){ messageResult.target.dispatchMessage(messageResult); } } } } public class Message { public String obj; public Handler target; public Message() { } public Message(String obj) { this.obj = obj; } }public class MessageQueue { BlockingQueueblockingDeque=new ArrayBlockingQueue<>(50); public Message next() throws InterruptedException { return blockingDeque.take(); } public void enqueMessage(Message message) { try { blockingDeque.put(message); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Handler { private Looper mLooper; private MessageQueue messageQueue; public Handler() { this.mLooper = Looper.myLooper(); messageQueue=mLooper.mQueue; } public void handleMessager(Message message){ enqueueMessage(message); } private void enqueueMessage(Message message) { message.target=this; messageQueue.enqueMessage(message); } public void sendMessage(Message message) { enqueueMessage(message); } public void dispatchMessage(Message messageResult) { handleMessager(messageResult); } }列子:public void testHandler2(final TextView textView){ mHandler=new Handler(); new Thread(new Runnable() { @Override public void run() { while (true){ i++; mHandler.post(new Runnable() { @Override public void run() { textView.setText(""+i); Log.d("mHandler",""+i); } }); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); }错误代码如下:*/*** ** Error**,**while**死循环,主线程做了耗时的操作* *** ***@param*** *textView* **/* public void testHandler(final TextView textView){ mHandler=new Handler(getMainLooper()); mHandler.post(new Runnable() { @Override public void run() { // textView.setText(""+i); while (true){ Log.d("mHandler",""+i+"ThreadName"+Thread.currentThread().getName()); i++; if(i%1000==0){ textView.setText(""+i); } } } }); }