1、handler启动
APP启动时,会启动ActivityThread类中的main方法。main方法主要作用是做消息循环,一旦消息循环停止,APP程序就会退出。Android是事件驱动的,在Loop.loop()中不断接收、处理事件,而Activity的生命周期都由主线程的Loop.loop()来调度,所以 主线程Looper的存活周期和App的生命周期基本是一致的。当目前没有事件需要处理的时候,主线程就会阻塞;当 子线程向消息队列发送消息,主线程就被唤醒。 ActivityThread是一个 final 类。
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
//之前SystemServer调用attach传入的是true,这里到应用进程传入false就行
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
...
//一直循环,保障进程一直执行,如果退出,说明程序关闭
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
从上面代码可以知道,主线程的looper和其他的线程的 looper是一样的,只不过已经完成了Looper.prepare()和Looper.loop(),这就是为什么主线程中不需要调用这两个方法的原因。如果没有消息,那么loop里面就会被 messageQueue 阻塞。
因为我们写的代码就是通过 handler驱动起来的,我们activity的onCreate、onResume、onStop等等这些生命周期方法,包括我们的UI绘制的 信号,这些UI绘制的事件都是通过Handler Looper循环内部发起的。这些所有的事务都被封装成为了一个一个的 message,然后通过looper来调用handleMessage回调我们的各Activity和 各Fragment,然后执行这些组件里面的各个生命周期方法,所以我们的代码就是在循环里面执行的,也就是主线程一切皆Message。
所以如果某个消息执行时间过长,就可能会影响 UI 线程的 刷新效率,造成卡顿的现象。
2、Looper.loop无限循环会阻塞主线程么?
答案是肯定的,但是这并不是问题。
Looper无限循环是Looper不停取MessageQueen中的Message并执行这个message的一种机制。我们的APP中的事件,如Activity的生命周期切换、点击、长按、滑动、都是依赖这种机制。
如果主线程的MessageQueue中没有消息,便会阻塞在Loop的queue.next()中的nativePollOnce方法。这个方法是个native方法,调用这个方法其实就是通过调用我们在Linux里面的一个管道机制epoll。这时候调用完epoll_wait之后,主线程会进入休眠状态并释放CPU资源,如果下一个消息到达或者有事物发生,通过向pipe管道写入数据来进行唤醒主线程工作。
Message next() {
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 阻塞nextPollTimeoutMillis
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;
//msg.target == null 消息屏障
if (msg != null && msg.target == null) {
// 循环往下查找异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
// 到这里说明是普通的消息
if (msg != null) {
// 还没有到执行的时候,减一下得到剩余的时间
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 取出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;
}
...
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 没有要执行的IdleHandler,那么就continue,就会到下一轮循环然后阻塞
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);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
3、阻塞线程为什么不会ANR?
ANR(Application Not Responding)是应用无响应。Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。
发生ANR的主要四种情况:
1)Service Timeout:前台服务在20s内未执行完成;
2)BroadcastQueue Timeout:前台广播在10s内未执行完成;
3)ContentProvider Timeout:内容提供者在publish过超时10s;
4)InputDispatching Timeout:输入 事件分发超时5s,包括按键和触摸事件。
Looper循环的阻塞是在消息队列无消息需要处理时的一种机制,这种机制就是让CPU停下来去做别的事,避免cpu空转,这个机制和ANR是没有关系的,完全不是同一个事,所以自然不会导致ANR
4、ThreadLocal
认识ThreadLocal
ThreadLocal 是一个关于创建线程局部变量的类。
其实就是这个变量的作用域是线程,其他线程访问不了。
通常我们创建的变量是可以被任何一个线程访问的,而使用 ThreadLocal 创建的变量只能被当前线程访问,其他线程无法访问。顾名思义,本地线程。
那么ThreadLocal是如何确保只有当前线程可以访问呢?我们先来分析一下ThreadLocal里面最重要的两个函数,get()和set()两个函数。
public T get() {
Thread t = Thread.currentThread(); //code 1
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();
}
public void set(T value) {
Thread t = Thread.currentThread(); //code 2
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
从code1 和code2 可知,在给ThreadLocal去set值或者get值的时候都会先获取当前线程,然后基于线程去调用getMap(thread),getMap返回的就是线程thread的成员变量threadLocals。所以通过get 和set执行的函数,这样就保证了threadLocal的访问,一定是只能访问或许修改当前线程的值,这就保障了这个变量是线程的局部变量。
ThreadLocal在Looper中的使用
Looper与线程的关系是一对一的关系,一个线程只有一个Looper,一个Looper,只有一个MessageQueue,不同线程之间Looper对象是隔离的,那么Looper是怎么保障这一点的呢?
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {//code 1
throw new RuntimeException("Only one Looper may be create per thread");
}
sThreadLocal.set(new Looper(quitAllowed));// code 2
}
通过上面的代码发现Looper初始化是调用prepare函数进行,在调用prepare函数的时候代码会执行 到code 1,在code1会先去判断当前线程对于的ThreadLocal中是否存在looper的value,如果存在,那么就抛出异常,这样就保证了一个线程只会设置一次Looper。这个代码的执行流程,就是确保了一个线程只有一个 ThreadLocal,一个ThreadLocal就只有一个looper。
那么Handler和Looper是怎么关联起来的呢?
我们可以看一下Handler的构造方法:
public Handler(@Nullable Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
在Handler中有两个全局变量mLooper(当前Handler关联Looper)和mQueue(消息队列),并在构造函数中进行了初始化,重要的就是调用了:Looper.myLooper(),在myLooper()中handler通过调用线程局部变量sThreadLocal,获取当前线程的Looper,这里需要注意的是,如果当前线程没有关联的Looper,这个方法会返回null。
注意:Handler在哪个线程创建的,就跟哪个线程的Looper 关联,也可以在Handler的构造方法中传入指定的Looper。(详看第6点)
5、handler.post和postDelayed
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
从上面代码我们可以看出, post(Runnable r) 方法使用到的sendMessageDelayed函数,其实也就是调用了 sendMessageDelayed(Message msg) 。只是它使用到了我们的 getPostMessage 函数,将我们的Runnable转化为了我们的 Message,由此可知handler.post(Runnable r) 分发阶段实质上是和普通的Message是一样的。而postDelayed只是比post多一个延时时间而已。
6、callback返回true,handlerMessage是否执行?
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 1. 设置了Message.Callback(Runnable)
handleCallback(msg);
} else {
if (mCallback != null) {
// 2. 设置了 Handler.Callback(Callback )
if (mCallback.handleMessage(msg)) {
return;
}
}
// 3. 未设置 Handler.Callback 或 返回 false
handleMessage(msg);
}
}
public interface Callback {
public boolean handleMessage(Message msg);
}
通过以上代码可知,callback接口中会执行handlerMessage,此返回值如果返回true,那么在2处会直接return,因此handlerMessage不会执行。
7、HandlerThread
首先了解一下new Handler()和new Handler(Looper.myLooper())有什么区别,如果new Handler初始化()不传参数,默认绑定当前线程的Looper对象,如果new Handler(Looper.myLooper())初始化传参数,则绑定指定Looper对象。
比如在子线程中初始化主线程的hanlder,则可以new Handler(Looper.getMainLooper())用来指定主线程的Looper对象。在主线程如果需要绑定子线程的Looper对象,我们通常不会使用Looper.myLooper(),因为主线程执行初始化handler时,并不能保证子线程Looper对象已经创建,因此此时需要借助handlerThread来完成Looper对象的绑定。看下面的代码:
//code 1 创建子线程
class MyThread extends Thread{
private Looper looper;
@Override
public void run() {
Looper.prepare();//创建该子线程的Looper
looper = Looper.myLooper();//取出该子线程的Looper
Looper.loop();//只要调用了该方法才能不断循环取出消息
}
}
private MyThread thread;
private Handler mHandler;//将mHandler指定轮询的Looper
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(tv);
thread = new MyThread();
thread.start();//千万别忘记开启这个线程
//code 2 下面是主线程发送消息
mHandler = new Handler(thread.looper){
@Override
public void handleMessage(android.os.Message msg) {
Log.d("当前子线程是----->", Thread.currentThread()+"");
};
};
mHandler.sendEmptyMessage(1);
}
此时,就很可能发生空指针异常,因此需要借助HandlerThread类来完成。将上面的code1的子线程删除,code2处的代码修改一下,就不会报错了。
//实例化一个特殊的线程HandlerThread,必须给其指定一个名字
HandlerThread thread = new HandlerThread("handler thread");
thread.start();//千万不要忘记开启这个线程
//将mHandler与thread相关联
mHandler = new Handler(thread.getLooper()){
public void handleMessage(android.os.Message msg) {
Log.d("当前子线程是----->", Thread.currentThread()+"");
};
};
mHandler.sendEmptyMessage(1);//发送消息
这是为什么呢?我们看一下handlerThread的源码:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
通过上面代码可以看到,在执行Looper.myLooper()时,handlerThread的run方法加入了锁机制,这样就保证了Handler在与HandlerThread进行绑定时,发现Looper为空,Handler会一直等待Looper创建成功,然后才会执行后续的代码。并且,run方法自动帮我们执行了Looper.prepare()和Looper.loop();
8、子线程发消息到主线程有几种方式?
我们通常使用异步线程更新UI,比如AsyncTask(),runOnUiThread(),view.post()等等。但是这些方法底层都是调用handler完成的。
runOnUiThread():
new Thread(){
public void run(){
runOnUiThread(new Runnable(){
@Override
public void run(){
//更新UI
}
});
}
}
/*先判断当前线程是否是主线程,如果是,则直接运行。
如果不是,调用handler.post();
*/
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
View.post():
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
/*上面的方法调用的是HandlerActionQueue的post方法,而在Handler内部调用post的时候,先执行的是
sendMessageDelayed方法,然后执行sendMessageAtTime方法,紧接着执行enqueueMessage,最终执行
的是queue.enqueueMessage,最终执行行的方式都是一样的。
*/
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}
}
8、IdleHandler
IdleHandler 是 MessageQueue内定义的一个接口,一般可用于做性能优化。
当消息队列内没有需要立即执行的 message时,会主动触发 IdleHandler 的 queueIdle方法。返回值为 false,在执行完会remove,即只会执行一次;返回值为 true,即每次当消息队列内没有需要立即执行的消息时,都会触发该方法。
因此,IdleHandler 是闲时Handler 机制,可以在 Looper 事件循环的过程中,当出现空闲的时候,允许我们执行任务。 IdleHandler 被定义在 MessageQueue 中,它是一个接口。
// MessageQueue.java
public static interface IdleHandler {
boolean queueIdle();
}
使用IdleHanlder也很简单:
// MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) {
// ...
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
可以看到 add 、 remove 其实操作的都是 mIdleHandlers,它的类型是一个 ArrayList。
既然 IdleHandler 主要是在 MessageQueue 出现空闲的时候被执行,那么什么时候出现空闲? MessageQueue 是一个基于消息触发时间的优先级队列,队列出现空闲存在两种情况:
1. MessageQueue 为空,没有消息;
2. MessageQueue 中最近需要处理的消息,是一个延迟消息(when>currentTime),需要滞后执行;
这两个场景,都会尝试调用 IdleHandler。 处理 IdleHandler 的情况,就在 Message.next() 这个获取消息队列下一 个待执行消息的方法中:
Message next() {
// ...
// step 1
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// ...
if (msg != null) {
if (now < msg.when) {
// 得出休眠的时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now,
Integer.MAX_VALUE);
} else {
// Other code
// 寻找消息处理后返回
return msg;
}
} else {
// 没有更多的消息
nextPollTimeoutMillis = -1;
}
// step 2
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// step 3
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new
IdleHandler[Math.max(pendingIdleHandlerCount,4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// step 4
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
我们先了解一下 next() 中关于 IdleHandler 执行的主要逻辑:
1. 准备调用 IdleHandler 时,说明当前待调用的消息为 null,或者这条消息的执行时间没有到;
2. 当 pendingIdleHandlerCount < 0 时,根据 mIdleHandlers.size() 赋值给 pendingIdleHandlerCount;
3. 将 mIdleHandlers 中的 IdleHandler 复制到 mPendingIdleHandlers 数组中,这个数组是临时的,之后进入 for 循环;
4. 循环中从mPendingIdleHandlers数组中取出 IdleHandler,并执行其 queueIdle() 记录返回值存到 keep 中;
5. 当 keep 为 false 时,从 mIdleHandler 中移除当前循环的 IdleHandler,反之则保留;
需要特别注意的是,对 mIdleHandler 这个 List 的所有操作,都是通过 synchronized 来保证线程安全的。
那么,在keep为true时,IdleHandler 机制是如何保证不会进入死循环的?
我们梳理一下:
Step 1,循环执行前,pendingIdleHandlerCount 的初始值为 -1;nextPollTimeoutMillis=0代表不会进入休眠状态,natievPollOnce() 进入休眠所以不会死循环是不正确的;
Step 2,在 pendingIdleHandlerCount<0 时,才会通过 mIdleHandlers.size() 赋值。也就是说只有第一次循环 才会改变 pendingIdleHandlerCount 的值;
Step 3,如果 pendingIdleHandlerCount<=0 时,则循环 continue;
Step 4,重置 pendingIdleHandlerCount 为 0;
当第一次循环时,pendingIdleHandlerCount<0,会给pendingIdleHandlerCount赋值,然后执行到Step4,此时 Step3不会执行,当第二次循环时,pendingIdleHandlerCount 等于 0,在 Step 2 不会改变它的值,那么在 Step 3 中会直接 continus 继续会下一次循环,此时没有机会修改 nextPollTimeoutMillis。 那么 nextPollTimeoutMillis 有 两种可能:-1 或者下次唤醒的等待间隔时间,在执行到 nativePollOnce() 时则会进入休眠,等待下一次被唤醒。 下 次唤醒的时候,mMessage 必然会有一个等待执行的 Message,则 MessageQueue.next() 返回到 Looper.loop() 的 循环中,分发处理这个 Message,之后又是一轮新的 next() 中去循环。
dleHandler能解决什么问题呢?我们可以将一些相对 耗时或者一些影响整个线程运行的事务放到IdleHandler里面处理,譬如当我们需要收到调用GC的时候,GC一般会带来STW问题,于是我们可以将这个动作在IdleHandler里面执行,而android源码确实也是这样进行的。
9、handler内存泄漏
handler持有外部activity的引用,导致了activity走了destory的话,也无法释放资源,所以会造成内存泄漏。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
//code 1
msg.target = this;// 1.指定目标Handler为当前handler
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// 如果是个异步Handler,那么把消息设为异步消息
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 2. 把msg插入到消息队列中
return queue.enqueueMessage(msg, uptimeMillis);
}
通过looper.loop()可以看到msg.target.dispatchMessage(msg),而code1又反应了target就是handler,所以message持有handler,那么说明在messagequeue消息队列里,持有了message,而message持有了handler的引用,而handler又持有activity的引用。looper持有了messageQueue。threadLocal持有了looper。
threadLocal-->looper-->messageQueue-->message-->handler-->activity;
threadLocal是static修饰的,那就是说,threadLocal是GC Root,根据可达性分析可知,这些被持有的都不可回收(threadLocal-->looper-->messageQueue不可回收,message-->handler-->activity不可回收,messageQueue-->message可能不会回收,因为messageQueue有delay方法,在activity执行后,delay也许还未执行,所以message是不会被回收的),activity自然就不能被回收了。
为什么其他的内部类没有说过这个问题?
viewholder(内部类):没有另外的类持有这个内部类,所以不会有这个问题。
如何解决:
软引用+static