本文主要三点目的
第一.面试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线程访问。
第一步: 线程:先把线程构建出来
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Menlo; background-color: rgb(43, 43, 43);">new Thread(){
@Override
public void run() {
Looper.prepare(); for(int i=0;i<1000;i++){
Log.d("peng","i="+i);
}
Looper.loop();
}
}.start();</pre>
2、Looper
问题: Looper是怎么创建的?
在线程里面创建的。然后保存在ThreadLocal中。
对于Looper主要是prepare()和loop()两个方法。
首先看prepare()方法:主要是通过ThreadLocal绑定Looper
<pre style="margin: 8px 0px; white-space: pre-wrap; overflow-wrap: break-word; line-height: 35px; widows: 1; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">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)); }</pre>
同一个线程中不能多次调用prepare方法,否则会抛出异常。如上面
ThreadLocal :在Looper中构建出来的
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;">public final class Looper { private static final String TAG = "Looper"; // sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();</pre>
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:单链表的数据结构
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;">private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread(); }</pre>
在构造方法中,创建了一个MessageQueue(消息队列)。
<pre style="margin: 8px 0px; white-space: pre-wrap; overflow-wrap: break-word; line-height: 35px; widows: 1; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread(); }</pre>
Loop方法:里面一个For循环,阻塞队列,不断的取消息
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">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);
}
}</pre>
looper方法:调用dispatchMessager方法:dispatchMessager方法再调用handlemessage方法
msg.target:就是Handler对象。看到message,持有handler的引用
Message 源码:Message的数据结构
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas, monospace; font-size: 0.817rem;">public final class Message implements Parcelable { <pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas, monospace;"> @UnsuportedAppUsage
/package/ Handler target;</pre> </pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas, monospace; font-size: 0.817rem;"> public int what; public int arg2; // sometimes we store linked lists of these things
/package/ Message next;</pre>
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}</pre>
Looper主要作用:
1、 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2、 loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。
好了,我们的异步消息处理线程已经有了消息队列(MessageQueue),也有了在无限循环体中取出消息的哥们,现在缺的就是发送消息的对象了,于是:Handler登场了。
Handler是怎么创建的:手动创建,自己创建的
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;">private Handler handler = new Handler(Looper.getMainLooper()) {</pre>
<pre style="margin: 8px 0px; white-space: pre-wrap; overflow-wrap: break-word; line-height: 35px; widows: 1; font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">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; }</pre>
发送消息:把消息添加到了MessageQueue队列里面,把MSG添加到了MessagerQueue里面。所有的消息都是通过sendMessageAtTime();
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">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); }</pre>
MessageQueue的enqueueMessage方法:存消息源码
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;">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;
}</pre>
存放消息:
注释1:p是队列头部,满足3个条件则把消息放到队列头部
1.队列中没有消息,p==null 2.入队的消息没有延时 3.入队的消息的延时比队列头部的消息延时短
注释2:消息插入到链表中,需要移动链表,对比消息的延时,插入到合适的位置
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;">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);
}
}</pre>
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,加热并降低设备的速度。
源码如下:
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">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);
}
}
}</pre>
移除消息:通过MessageQueue
<pre style="margin: 8px 0px; white-space: pre-wrap; overflow-wrap: break-word; line-height: 35px; widows: 1; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null); }</pre>
实例写法:
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler2 = new Handler();</pre>
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">
<pre style="margin: 8px 0px; font-family: Consolas;">
Looper.loop();</pre>
}
}).start();</pre>
[图片上传失败...(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
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">Looper looper = Looper.myLooper(); handler = new Handler(looper) {</pre>
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未到达发送的时间则阻塞。
新加入的数据会根据时间点的大小判断需要插入的位置,同时还需要判断是否需要唤醒线程去发送当前的队首的消息。
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas, monospace; font-size: 0.817rem;">public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
</pre>
### 7.如果在当前线程内使用Handler postdelayed 两个消息,一个延迟5s,一个延迟10s
然后使当前线程sleep 5秒,以上消息的执行时间会如何变化?
答:照常执行
扩展:sleep时间<=5 对两个消息无影响,5< sleep时间 <=10 对第一个消息有影响,第一个消息会延迟到sleep后执行,sleep时间>10 对两个时间都有影响,都会延迟到sleep后执行。
总结:如果sleep的时间<=延时的时间,是没有影响的。大于就有影响
问题: 假如发了一个延时消息5s,然后这个时间没有其他消息了。怎么处理?
通过延时,阻塞。
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas, monospace; font-size: 0.817rem;">if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue; }</pre>
聊下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 循环
`<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-size: 0.8rem;"> 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(); }</pre>
<pre style="margin: 8px 0px; font-weight: bold; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-size: 0.8rem;">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();
}
}
};</pre>
这2个looper,到底用哪个?用主looper
在线程切换中,不会
<pre style="margin: 8px 0px; font-weight: bold; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Menlo; font-size: 0.8rem;"> Looper.prepare(); for(int i=0;i<1000;i++){
Log.d("peng","i="+i);
}
Looper.loop(); </pre>
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();
看源码:用这个可以,但是有内存泄露导致
<pre style="margin: 8px 0px;">Looper.prepare();
Looper.loop();</pre>
解决办法:在子线程中添加了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();
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-size: 0.817rem;">checkForRelayout();</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;"> 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();</pre>
view里面的
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-size: 0.817rem;">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;
}
}</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-size: 0.817rem;">ViewRootImpl方法里面:// 如果当前线程不是主线程就抛出异常</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-size: 0.817rem;">void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}</pre>
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之后创建的,真相大白!
如果检测线程要优先执行
<pre style="margin: 8px 0px;">invalidate快与requestLayout();那么更新ui没有问题</pre>
如果先requestLayout().里面调用了线程检测,在invalidate就会有问题
或者:ViewRootImpl没有创建
既然是用ViewRootImpl的checkThread方法来检查线程,那么就说明在onCreate的时候ViewRootImpl还没被创建,所以不会走checkThread方法,自然就不会报错了
每次view刷新的时候都会去检测一次线程
**12.Android 线程A与线程B如何通信的 **
类似子线程往主线程发消息一样,其实android中线程通信无非就是handler和looper的操作。
需要注意的就是要loop.prapare()和looper.loop()。不调用Loop.loop()方法的话,是收不到消息的,
**demo: **
**<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">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(); }</pre>**
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的写法有什么不一样?
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; background-color: rgb(43, 43, 43);">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);
}
};</pre>**
**## Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一样?为什么这么设计?
插件话用到了这个玩意。
源码分析:
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">/**
* 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);
}
}</pre>**
* 如果为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()方法!
**<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">@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()); }
</pre>**
通过源码分析:
如果callback为空和不为空的话
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}</pre>
使用当前线程的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字段,通过指针的方式来进行对象管理,不得不说,是一种非常巧妙的设计方式,一来降低了设计复杂度,而且由于没有创建额外的数据容器来管理对象,减轻了内存的压力,实现了轻量化的目的。
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-weight: bold; background-color: rgb(43, 43, 43); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">private static Message sPool;</pre>
```
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的线程安全的。
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: "JetBrains Mono", monospace; font-size: 0.817rem;">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; }</pre>
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步搞定
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">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();//取消息 }
</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">
public class Looper {
public Looper() {
this.mQueue = new MessageQueue();
}
final static ThreadLocal<Looper> threadLocal=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);
}
}
}
}</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">public class Message {
public String obj;
public Handler target; public Message() {
}
public Message(String obj) {
this.obj = obj;
}
}
</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">public class MessageQueue {
BlockingQueue<Message> blockingDeque=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();
}
}
}
</pre>
<pre style="margin: 8px 0px; background-color: rgb(43, 43, 43); color: rgb(169, 183, 198); font-family: Consolas; font-size: 0.817rem;">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);
}
}
</pre>
列子:
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">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(); }</pre>
错误代码如下:
<pre style="margin: 8px 0px; color: rgb(169, 183, 198); font-family: Consolas; font-size: 1.375rem; background-color: rgb(43, 43, 43);">*/*** ** 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);
}
}
}
});
}</pre>