问题1:一个线程中有几个Handler?
答:多个,一个Activity或service就是在主线程中,在主线程中可以new多个Handler。
问题2:一个线程有几个Looper?如何保证?
答:一个线程只有一个Looper(一旦创建不能修改),通过ThreadLocal类中的内部类ThreadLocalMap进行维持。(一个Looper对应一个MessageQueue,MessageQueue是在Looper构造函数中创建的)
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
ThreadLocalMap 与当前线程是一一对应的
ThreadLocalMap是ThreadLocal的静态内部类
ThreadLocalMap: <key, value>,key = this,即唯一的 ThreadLocal,value = Looper,但是key和value存放在一个数组中,如下格式。
key1 |
---|
value1 |
key2 |
value2 |
… |
如何保证唯一? --》只让set一次
Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); // 表示sThreadLocal 是唯一的
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) { //sThreadLocal.get()获得的值就是Looper对象
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
ThreadLocalMap是Map的存储结构,每次存的时候都会根据key(ThreadLocal)进行判断,当前的ThreadLocal是否有对应的Looper,这样就可以表示一个ThreadLocal对应唯一的一个Looper对象。
ThreadLocal.java
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
问题3:Handler内存泄漏原因?为什么其他的内部类没有说过有这个问题?
答:原因:内部类持有外部类的引用,默认的会持有外部this的引用。
message会持有handler的引用(msg.target.dispatchMessage(msg)),handler会持有this的引用(msg.target = this),message没有处理之前,一直都会处在内存中。如果消息delay 1分钟之后,在这个时间内会一直持有this的引用。
持有关系:MessageQueue—>Message—>handler—>activity
其他内部类持有外部类并没有耗时的东西,没有另外的东西持有内部类,就不会出现内存泄漏问题。
解决办法:软引用 + static
问题4:为何主线程可以new Handler?如果想要在子线程中new Handler要做些什么准备?
答:在ActivityThread类中的main()方法中,有对主函数的Looper进行prepare()(Looper.prepareMainLooper())和loop()(Looper.loop())操作。主线程中的所有代码全部都运行在Looper.prepareMainLooper()和Looper.loop()两个函数之间。
如果想要在子线程中new Handler需要进行如下三步操作:
1)prepare()
2)Loop()
3)quit()或quitSafely()—>用于子线程消息队列中无消息处于等待状态的处理
如果没有执行prepare操作会报“No Looper; Looper.prepare() wasn’t called on this thread.”。
特别注意: 主线程是不允许使用quit操作的。
如果主线程中调用了quit()方法会报如下异常:
MessageQueue.java
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
... ...
}
问题5:子线程中维护的Looper,消息队列无消息的时候处理方案是什么?有什么用?
答:消息队列无消息会一直处于等待状态。
(执行到next()方法中的nativePollOnce(ptr, nextPollTimeoutMillis);语句,处于linux等待状态。直到enqueueMessage()方法中执行了nativeWake(mPtr)语句才会唤醒nativePollOnce之后的操作。)
messageQueue消息处理机制:
① 如果消息队列不为空,当前时间还没到,会执行nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞等待,等待nextPollTimeoutMillis时间,时间到了会自己唤醒。
② 如果消息队列为空,当messageQueue执行enqueueMessage()流程的时候,会执行nativeWake(mPtr)进行唤醒。
解决方案:调用Looper的quitSafely()或quit(),
作用:1)释放内存,2)释放线程。
quitSafely()---->安全的
quit()---->不安全的
子线程调用quit()流程:
① 执行quit()流程,会将mQuitting
变量赋值为 true
,并执行nativeWake(mPtr)
进行线程唤醒
操作;
MessageQueue.java
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true; // 进行mQuitting 设置为true
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr); // 进行唤醒
}
}
② 在MessageQueue类中的next()方法会对阻塞流程nativePollOnce(ptr, nextPollTimeoutMillis)进行唤醒。
MessageQueue.java
Message next() {
...
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis); //进行阻塞
...
}
...
if (mQuitting) { //此时mQuitting为true
dispose();
return null; //返回为null
}
...
}
③ Looper.loop流程中进行next()取消息流程
Looper.java
public static void loop() {
final Looper me = myLooper();
...
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) { // 往下执行loopOnce() 流程
return; //loopOnce()返回为false的时候直接return
}
}
}
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false; //此处message为null时返回false
}
...
}
当message为空时,loopOnce()返回为false的时候直接return,表现子线程退出,
问题6:既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同线程),那它内部是如何确保线程安全的?
答:MessageQueue是一个优先级队列,根据时间先后顺序排队的单链表
。MessageQueue没有设置限制,可以无限加入message,但是受手机内存的限制。
在enqueueMessage()加消息过程中会进行synchronized操作,并且在next()取消息过程中也会进行synchronized操作。从而保证了线程安全性。
MessageQueue.java
往消息队列中存消息
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.");
}
... ...
}
从消息队列中取消息
MessageQueue.java
@UnsupportedAppUsage
Message next() {
... ...
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) {
... ...
}
为了保证安全性,加入synchronized,从而导致Handler delay的消息中的时间不完全准确。
synchronized可以修饰:方法,静态方法、代码块(object)、代码块(this)
问题7:我们使用Message时应该如何创建它?
答:采用obtain()方法进行创建。采用享元设计模式
,内存复用。
当MessageQueue进行释放消息的时候,其实并没有把消息释放掉,在执行recycleunchecked()方法时只是把消息里面的内容置空,然后把置空的消息放到池子中,往队列头中进行添加。
Obtain()是从池子队列头取一个点
如果采用new()
进行创建,可能会出现内存抖动
现象。
问题8:使用Handler的postDelay后消息队列会有什么变化?
答:如果消息队列为空,该消息不会执行,计算等待时间,让消息进行wait。
问题9:Looper死循环为什么不会导致应用卡死?
答:每个app都有一个自己的虚拟机。
Launcher—>application—>zygote—>虚拟机—>ActivityThread
所有的生命周期都发运行在Loop里面,都是以消息的方式存在
主线程的唤醒方式有哪些?
① 输入的事件
② Looper添加消息
ANR产生原因:
①在5秒内没有响应输入的事件(例如:按键按下,屏幕触摸)
②BroadcastReceiver在10秒内没有执行完毕
每一个事件就相对于一个message。
为什么不会卡死:
疑惑点:Message msg = queue.next();
此时主线程处于休眠状态,卡死是ANR
问题10:Message、Handler、MessageQueue、Looper 的之间的关系?
首先,是这个 MessageQueue
,MessageQueue是一个消息队列,它的数据结构形式是有单链表
实现的优先级队
列,它可以存储 Handler 发送过来的消息,其内部提供了进队和出队的方法来管理这个消息队列,其出队和进队的原理是采用单链表的数据结构进行插入和删除的,即enqueueMessage()方法和 next()方法。这里提到的 Message,其实就是一个Bean 对象,里面的属性用来记录 Message 的各种信息。
然后,Looper
是一个循环器,它可以循环的取出MessageQueue 中的 Message,其内部提供了 Looper 的初始化和循环出去Message 的方法,即 prepare()方法和loop()方法。在 prepare()方法中,Looper会关联一个MessageQueue,而且将 Looper 存进一个 ThreadLocal 中,在loop()方法中,通过 ThreadLocal 取出 Looper,使用MessageQueue的next()方法取出 Message 后,判断 Message 是否为空,如果是则 Looper 阻塞,如果不是,则通过 dispatchMessage()方法分发该 Message 到 Handler 中,而Handler 执行 handlerMessage()方法,由于 handlerMessage()方法是个空方法,这也是为什么需要在 Handler 中重写 handlerMessage()方法的原因。这里要注意的是Looper 只能在一个线程中只能存在一个。这里提到的ThreadLocal,其实就是一个对象,用来在不同线程中存放对应线程的 Looper。
最后,Handler
是Looper 和MessageQueue的桥梁,Handler内部提供了发送 Message 的一系列方法,最终会通过 MessageQueue的enqueueMessage()方法将 Message 存进 MessageQueue 中。我们平时可以直接在主线程中使用 Handler,那是因为在应用程序启动时,在入口的 main 方法中已经默认为我们创建好了 Looper。
问题11:为什么在子线程中创建 Handler 会抛异常?
Handler 的工作是依赖于 Looper 的,而 Looper(与消息队列)又是属于某一个线程(ThreadLocal 是线程内部的数据存储类,通过它可以在指定线程中存储数据,其他线程则无法获取到),其他线程不能访问。因此 Handler 就是间接跟线程是绑定在一起了。因此要使用 Handler 必须要保证 Handler 所创建的线程中有 Looper 对象并且启动循环。因为子线程中默认是没有 Looper 的,所以会
报错。 正确的使用方法是:
private final class WorkThread extends Thread {
private Handler mHandler;
public Handler getHandler() {
return mHandler;
}
private final class WorkThread extends Thread {
public void quit() {
mHandler.getLooper().quit();
}
@Override
public void run() {
super.run();
//创建该线程对应的Looper,
// 内部实现
// 1。new Looper()
//2。将1步中的lopper放在ThreadLocal里,ThreadLocal是保存数据的,
主要应用场景是:线程间数据互不影响的情况
// 3。在1步中的Looper的构造函数中new MessageQueue();
//其实就是创建了该线程对用的Looper,Looper里创建MessageQueue来实现
消息机制
/对消息机制不懂得同学可以查阅资料,网上很多也讲的很不错。
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what);
}
};
//开启消息的死循环处理即:dispatchMessage
Looper.loop();
/注意这3个的顺序不能颠倒
Log.d("WorkThread", "end");
}
}
问题12、如何从子线程发送消息到主线程中进行处理的?
答:首先需要了解下handler调度流程。通过new Thread()创建一个子线程执行hander.sendMessage()操作往MessageQueue消息队列中插入消息(MessageQueue其实是一个存储消息的容器,属于是一个变量,不属于具体哪个线程,线程中执行的函数属于具体哪个线程)。一个线程对应一个ThreadLocalMap,ThreadLocalMap存储着唯一的ThreadLocal(key)和唯一的Looper(value),并且一个Looper对应一个MessageQueue。取消息流程会执行主线程中Looper.loop(),会调用到next()方法进行msg.target.dispatchMessage()以及后续的handleMessage()操作,这些都是在主线程执行的。
(Handler调度流程)
插入消息流程:
handler send***/post*** -----> Handler.sendMessage() -->...--> messageQueue.enqueueMessage //往messageQueue消息队列中插入消息
取消息流程:
Looper. loop() ----> messageQueue ----> next()----> msg.target.dispatchMessage() ----> handlerMessage
附录一些视频截图(https://ke.qq.com/course/341933?_bid=167&_wv=2147483649&term_id=100406336&singlemessage=&taid=9303882710988717)
根据时间先后顺序生产一个优先级队列:(Handler调度流程)
插入消息流程:
handler send***/post*** -----> Handler.sendMessage() -->…–> messageQueue.enqueueMessage //往messageQueue消息队列中插入消息
取消息流程:
Looper. loop() ----> messageQueue ----> next()----> msg.target.dispatchMessage() ----> handlerMessage