1.Android Handler面试连环18连问 手写增强版跨进程的Handler【至少涨薪10k】

![handler.png](https://upload-images.jianshu.io/upload_images/11218161-d6df8f190bb693cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

本文主要三点目的

第一.面试18连问

第二.epoll机制源码三分钟搞定

第三.手写可以跨进程的Handler通信方案,没错跨进程。既可以线程通信,又可以跨进程通信**


handler.png

面试连环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是啥?有什么使用场景?

handler3.png

1.Handler基本使用


handler3.png

一般情况下,在主线程中我们绑定了Handler,并在事件触发上面创建新的线程用于完成某些耗时的操作,当子线程中的工作完成之后,会对Handler发送一个完成的信号,而Handler接收到信号后,就进行主UI界面的更新操作。

Handler被设计出来的原因?有什么用?

Handler机制主要为了解决以下2个问题

  1. 不要阻塞UI线程;
  2. 不要在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相当于事件驱动,心跳机制!

  1. 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方法,我们需要重写的

这几个角色是如何协同工作的呢?简单概括为下面四个步骤:

  1. handler发送消息到message queue,这个消息可能是一个message,可能是一个runnable
  2. looper负责从message queue取消息
  3. looper把消息dispatch给handler
  4. 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是按照时间从前往后有序排列的。

同步屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。

不过异步消息却例外,屏障不会挡住异步消息,

因此可以认为,屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。

同步屏障: 往消息队列插入一个同步屏障消息,这时候消息队列中的同步消息不会被处理,而是优先处理异步消息。这里很好理解,

同步屏障的处理代码在MessageQueuenext方法:

删除屏障消息的方法很简单,就是不断遍历消息队列,知道找到屏障消息,退出循环的条件有两个,一是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: &quot;JetBrains Mono&quot;, 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: &quot;JetBrains Mono&quot;, 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: &quot;JetBrains Mono&quot;, 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: &quot;JetBrains Mono&quot;, 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: &quot;JetBrains Mono&quot;, 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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容