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线程访问。

第一步: 线程:先把线程构建出来

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 ThreadLocal sThreadLocal = 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相当于事件驱动,心跳机制!

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

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

  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

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 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是按照时间从前往后有序排列的。

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

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

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

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

同步屏障的处理代码在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 循环

`

 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.loop();
解决办法:在子线程中添加了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();
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 ThreadLocal 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);
  }
        }
    }
}
public class Message {
    public String obj;
 public Handler target;   public Message() {

    }

    public Message(String obj) {
        this.obj = obj;
  }
}
public class MessageQueue {

    BlockingQueue 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();
  }

    }
}
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);
  }
                }
            }
        });
  }

你可能感兴趣的:(1.Android Handler面试连环18连问 手写增强版跨进程的Handler【至少涨薪10k】)