对线面试官系列:Android Handler面试连环问题解析,
先来一个自己画的Handler机制整体流程图,本文不会带着你走一遍源码,只会对重点需要注意的地方以及一些细节的处理做出解释,让你更好的了解Handler机制整体的运作
。
- Handler通过sendMessage()发送Message到MessageQueue队列;
- Looper通过loop(),不断提取出达到触发条件的Message,并将Message交给target来处理;
- 经过dispatchMessage()后,交回给Handler的handleMessage()来进行相应地处理。
- 将Message加入MessageQueue时,处往管道写入字符,可以会唤醒loop线程;
- 如果MessageQueue中没有Message,并处于Idle状态,则会执行IdelHandler接口中的方法,往往用于做一些清理性地工作。
下边放几个需要注意的Handler知识点:
- Handler 的背后有 Looper、MessageQueue 支撑,Looper 负责消息分发,MessageQueue 负责消息管理;
- 在创建 Handler 之前一定需要先创建 Looper;
- Looper 有退出的功能,但是主线程的 Looper 不允许退出;
- 异步线程的 Looper 需要自己调用 Looper.quit(); 退出;
- Runnable 被封装进了 Message,可以说是一个特殊的 Message;
- Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,
也可以说成 Looper 所在的线程,并不是创建 Handler 的线程,Handler新建时持有的Looper在哪个线程,最后Handler.handleMessage()就在哪个线程执行
; - 使用内部类的方式使用 Handler 可能会导致内存泄露,即便在 Activity.onDestroy 里移除延时消息,必须要写成静态内部类;
为什么需要使用Handler
因为Android系统不允许在非UI线程更新UI
,因为如果多个线程同时改变View的状态会造成最终View状态的不确定性,如果给每个View的操作都上锁的话那么势必会造成性能的损耗,所以干脆规定只能在UI线程去更新UI,而Handler就是用来进行线程切换操作的。
使用方法
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
//TODO 定义消息处理逻辑.
}
};
Looper.loop();
}
}
主线程中可以使用Handler的原因是在ActivityThread中程序的入口main方法中调用了Looper.prepare();和Looper.loop();
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
Looper
- Looper.prepare() Looper.prepare()在每个线程只允许执行一次,该方法给当前线程通过TL绑定一个线程所属的唯一一个实例。
private static void prepare(boolean quitAllowed) {
//看当前线程是否已通过TL绑定对应的实例,有的话抛异常,所以prepare方法只允许调用一次
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//创建Looper对象,并通过TL建立与线程的绑定关系
sThreadLocal.set(new Looper(quitAllowed));
}
- ThreadLocal 我们看一下ThreadLocal.set方法
public void set(T value) {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//获取当前线程所属的ThreadLocalMap实例,键值对结构
if (map != null)
map.set(this, value); //以当前ThreadLocal作为键,Looper作为值建立绑定关系
else
createMap(t, value);
}
}
ThreadLocal.get方法
public T get() {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//获取当前线程所属的ThreadLocalMap实例,键值对结构
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//通过当前ThreadLocal作为键取出对应的值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
为什么要选择ThreadLocal建立绑定关系?
因为我们是要让每一个线程都有且只有一个唯一的Looper实例,这时就可以使用ThreadLocal给每个线程绑定一个唯一实例的特性很方便的建立绑定关系。如果不采用ThreadLocal去实现,那么只能使用一个LooperManager管理类然后通过其中的Map去统一管理,那么这样无疑是很麻烦的
。ThreadLocal只是作为主键,如果是Thread作为主键,那么很显然一个线程只能与一个对应的对象建立绑定关系,这显然是非常不合理的。
- Looper.loop() loop()进入循环模式,主要进行了
如下几点
:
- 获取当前线程的Looper实例
- 通过Looper获取MessageQueue实例
- 开启死循环并在其中调用MessageQueue的next方法不断轮询MessageQueue的头结点
public static void loop() {
final Looper me = myLooper(); //获取TLS存储的Looper对象 -->sThreadLocal.get()
final MessageQueue queue = me.mQueue; //获取Looper对象中的消息队列
Binder.clearCallingIdentity();
//确保在权限检查时基于本地进程,而不是调用进程。
final long ident = Binder.clearCallingIdentity();
for (;;) { //进入loop的主循环方法
Message msg = queue.next(); //可能会阻塞
if (msg == null) { //没有消息则退出循环,调用Looper.quit()方法后返回空的message,随即退出
return;
}
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg); //用于分发Message
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
final long newIdent = Binder.clearCallingIdentity();
msg.recycleUnchecked();
}
}
- Looper.quit()
用于终止loop循环,主线程中不允许调用(能调用的话相当于主程序退出了,应用就直接挂掉了),子线程中要退出时需要主动调用,否则会造成子线程中一直处于死循环状态无法退出
。调用后会改变MessageQueue中的mQuitting标志位,next方法中如果检测到mQuitting为true则直接返回null,loop方法中检测到message是null则直接return终止死循环从而结束逻辑使得线程可以退出。
public void quit() {
mQueue.quit(false); //全部消息移除
}
public void quitSafely() {
mQueue.quit(true); //只移除没有执行的消息
}
如何做到延迟发送消息
- 会根据Message发送时的时间戳确定Message在MessageQueue中的位置。
- 放入Message时会根据
msg.when
这个时间戳进行顺序的排序,如果非延迟消息则msg.when为系统当前时间,延迟消息则为系统当前时间+延迟时间(如延迟发送3秒则为SystemClock.uptimeMillis() + 3000) - 将Message放入MessageQueue时会以msg.when对msg进行排序确认当前msg处于单链表中的位置,分为几种情况: (1)头结点为null(代表MessageQueue没有消息),Message直接放入头结点。 (2) 头结点不为null时开启死循环遍历所有节点,退出死循环的条件是: 1.遍历出的节点的next节点为null(说明当前链表已经遍历到了末尾,将放入的Message放入next节点). 2.遍历出的节点的when大于放入message的when(说明当前message是一个比放入message延迟更久的消息,将放入的Message放入当前遍历的Message节点之前).
- 当我们发送消息的时候其实最后都会调用到
sendMessageAtTime
这个方法,这个方法其实最终会把你的Handler对象赋值给Message实体,我们最终发送消息都是发送的Message实体,然后调用MessageQueue的enqueueMessage方法
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
- MessageQueue 是一个单链表结构,其中的Message节点是以Message放入MessageQueue的时间去进行顺序确定的(小的在前大的再后),这样就完成了消息的延迟发送
boolean enqueueMessage(Message msg, long when) {
// 每一个普通Message必须有一个target
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) { //正在退出时,回收msg,加入到消息池
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) { //p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该分支并将加入的message放入头结点
msg.next = p;
mMessages = msg;
needWake = mBlocked; //当阻塞时需要唤醒
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { //开启死循环遍历message
prev = p;
p = p.next;
if (p == null || when < p.when) { //退出条件为当前message的下一个节点为null或者当前节点的message执行时间大于你放入message的执行时间
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; //进行赋值
prev.next = msg;
}
//消息没有退出,我们认为此时mPtr != 0
if (needWake) {
nativeWake(mPtr); //往管道中写数据,唤醒阻塞,nativePollOnce方法阻塞解除
}
}
return true;
}
MessageQueue的机制
-
Message的入列和出列其实是一个很典型的**生产者-消费者模型**,其中使用了epoll机制,当没有消息的时候会进行阻塞释放CPU时间片避免死循环造成性能的浪费。
虽然是不断循环取出头结点的Message进行分发处理但是如果没有消息时它是阻塞在 nativePollOnce这个native方法中的
,当我们enqueue插入Message时会触发nativeWake这个方法去唤醒
,从而nativePollOnce阻塞解除继续遍历MessageQueue取出头结点去处理。 - Looper.loop()在一个线程中调用next()不断的取出消息,另外一个线程则通过enqueueMessage向队列中插入消息,
所以在这两个方法中使用了synchronized (this) {}同步机制,其中this为MessageQueue对象,不管在哪个线程,这个对象都是同一个
,因为Handler中的mQueue指向的是Looper中的mQueue,这样防止了多个线程对同一个队列的同时操作(如增加的同时正在轮询获取Message,有可能造成MessageQueue中最终结果的不确定性)。
Message next() {
final long ptr = mPtr;
if (ptr == 0) { //当消息循环已经退出,则直接返回
return null;
}
int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
//nextPollTimeoutMillis 为-1,一直阻塞,在调用nativeWake(enqueue Message或Looper.quit()退出Looper)时会被唤醒解除阻塞
//nextPollTimeoutMillis 为0,不阻塞
//nextPollTimeoutMillis 为>0,阻塞到对应时间后解除,如为10000则阻塞十秒后解除,用于处理延迟消息
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,则退出循环。
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 {
// 获取一条消息,并返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
//设置消息的使用状态,即flags |= FLAG_IN_USE
msg.markInUse();
return msg; //成功地获取MessageQueue中的下一条即将要执行的消息
}
} else {
//没有消息,阻塞
nextPollTimeoutMillis = -1;
}
//消息正在退出,返回null
if (mQuitting) {
dispose();
return null;
}
//当消息
Message分发的三个的优先级
当遍历出Message后Message会获取其中的Handler并调用Handler的dispatchMessage进行分发,这时也会有三个优先级。
- Message的回调方法:message.callback.run(),优先级最高; 对应handler.post(new Runnable)的方式发送消息
- Handler的回调方法:Handler.mCallback.handleMessage(msg),优先级仅次于1; 对应新建Handler时传进CallBack接口 Handler handler=new Handler(new Handler.Callback()....(
通常我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息,场景如:Hook ActivityThread.mH,在 ActivityThread 中有个成员变量 mH ,它是个 Handler,又是个极其重要的类,几乎所有的插件化框架都使用了这个方法。)
- Handler的默认方法:Handler.handleMessage(msg),优先级最低。 对应新建Handler并复写handleMessage方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) { //callback是一个runnable对象
handleCallback(msg);
} else {
if (mCallback != null) { //mCallback是新建Handler时传进去的Callback接口
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg); //默认空实现,一般我们会自己复写实现这个方法
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
- 将 Runnable post 到主线程执行(很多第三方框架都使用的这种方式方便的完成主线程的切换,这也是为什么有handler.post(new Runnable)这种方式去发送消息)。
- 利用 Looper 判断当前线程是否是主线程。
public final class MainThread {
private MainThread() {
}
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
public static void run(@NonNull Runnable runnable) {
if (isMainThread()) {
runnable.run();
}else{
HANDLER.post(runnable);
}
}
public static boolean isMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}
Handler线程调度的实质
Handler的实质其实就是共享内存
,我们看一个例子。
public class Demo {
List mList= new ArrayList()<Message>;
public static void main(String[] args) {
//子线程
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000l);
mList.add(new Message());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//主线程开启死循环不断遍历list取头结点
for (;;) {
//主线程中处理
Message message = mList.get(0);
if (message != null) {
//处理
}
}
}
}
我们为了将数据最终从子线程切换到主线程中去其实只要拿到mList这个实例即可,这个mList对应的其实就是MessageQueue,而我们要获取MessageQueue只要获取对应的Looper即可,当我们Handler新建的时会根据Handler所在线程获取到其线程正在轮询消息的Looper对象,Handler中的mQueue指向的是其所在线程的Looper中的mQueue(当然也可以手动指定一个其他线程的Looper,不指定的话默认为当前线程的Looper),由此便可在发送Message时将任务放到Looper所在线程中处理。
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper(); //threadLocal.get获取线程对应的Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; //通过Looper获取MessageQueue
mCallback = callback;
mAsynchronous = async;
}
Looper.loop是一个死循环,拿不到需要处理的Message就会阻塞,那在UI线程中为什么不会导致ANR?
首先我们来看造成ANR的原因:
- 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)。
- 当前的事件正在处理,但没有及时完成。
我们再来看一下APP的入口ActivityThread的main方法:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我们知道Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper的控制之下,如果它停止了,应用也就停止了。真正的阻塞是因为轮询出message后在处理message消息的时候由于执行了耗时操作导致了ANR,而不是死循环导致的阻塞,没有消息处理的时候消息队列是阻塞在nativePollOnce方法中的,**这个方法使用的是epoll管道机制,Linux底层执行后会释放CPU避免不断死循环造成的CPU浪费。**
Handler 引起的内存泄露原因以及最佳解决方案
当我们用Handler发送延时消息时,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。
这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。 解决该问题的最有效的方法是:将 Handler 定义成静态的内部类(静态内部类不会持有外部类引用,但是静态内部类调用不到外部类的非静态属性和方法,所以我们需要在内部类中使用弱引用持有Activity,使用弱引用调用到Activity中的方法),并及时移除所有消息。 泄漏时的引用链 Activity->Handler->Message->MessageQueue ,延迟消息会一直在MessageQueue中等待处理,在等待的过程中有可能会造成内存泄漏。
示例代码如下:
private static class SafeHandler extends Handler {
private WeakReference<HandlerActivity> ref;
public SafeHandler(HandlerActivity activity) {
this.ref = new WeakReference(activity);
}
@Override
public void handleMessage(final Message msg) {
HandlerActivity activity = ref.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
并且再在 Activity.onDestroy() 前移除消息,加一层保障:
@Override
protected void onDestroy() {
safeHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
这样双重保障,就能完全避免内存泄露了。 注意:单纯的在 onDestroy 移除消息并不保险,因为 onDestroy 并不一定执行(如报异常)。
IdleHandler 是什么
主要是为了去充分的利用阻塞时间,让原先处于阻塞的这段时间不进行阻塞而是去执行IdleHandler中的任务。
从下边MessageQueue的源码可知道,IdleHandler即在MessageQueue应该被阻塞之前去调用(当然前提是你要讲自定义的IdleHandler加入到集合中)
。 IdleHandler接口表示当MessageQueue发现当前没有更多消息可以处理的时候,
则顺便干点别的事情的callback函数(即如果发现idle了, 那就找点别的事干). callback函数有个boolean的返回值, 表示是否keep. 如果返回false, 则它会在调用完毕之后从mIdleHandlers中移除. IdleHandler 可以用来提升提升性能,主要用在我们希望能够在当前线程消息队列空闲时做些事情(譬如UI线程在显示完成后,如果线程空闲我们就可以提前准备其他内容)的情况下,**不过最好不要做耗时操作**
。
Message next() {
final long ptr = mPtr;
if (ptr == 0) { //当消息循环已经退出,则直接返回
return null;
}
int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
//nextPollTimeoutMillis 为-1,一直阻塞,在调用nativeWake(enqueue Message或Looper.quit()退出Looper)时会被唤醒解除阻塞
//nextPollTimeoutMillis 为0,不阻塞
//nextPollTimeoutMillis 为>0,阻塞到对应时间后解除,如为10000则阻塞十秒后解除,用于处理延迟消息
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//当消息Handler为空时,查询MessageQueue中的下一条异步消息msg,则退出循环。
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 {
// 获取一条消息,并返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
//设置消息的使用状态,即flags |= FLAG_IN_USE
msg.markInUse();
return msg; //成功地获取MessageQueue中的下一条即将要执行的消息
}
} else {
//没有消息,阻塞
nextPollTimeoutMillis = -1;
}
//消息正在退出,返回null
if (mQuitting) {
dispose();
return null;
}
//如果当前MessageQueue头结点为空(没有消息要处理了)或者当前系统时间<消息触发时间
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
//看是否加入了idleHandler
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
//没有idle handlers 需要运行,则循环并等待。
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
//只有第一次循环时,会运行idle handlers,执行完成后,重置pendingIdleHandlerCount为0.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; //去掉handler的引用
boolean keep = false;
try {
//如果queueIdle()返回false则当前idlehandler只能运行一次
keep = idler.queueIdle(); //idle时执行的方法
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
//queueIdle返回false,移除idlehandler
mIdleHandlers.remove(idler);
}
}
}
//重置idle handler个数为0,以保证不会再次重复运行
pendingIdleHandlerCount = 0;
//当调用一个空闲handler时,一个新message能够被分发,因此无需等待可以直接查询pending message.
nextPollTimeoutMillis = 0;
}
}
IdleHandler在FrameWork中的使用
ActivityThread.GcIdler 是在内存不足时,强行 GC;比如在进程进入后台时执行GC
final GcIdler mGcIdler = new GcIdler();
public void processInBackground() {
mH.removeMessages(H.GC_WHEN_IDLE);
mH.sendMessage(mH.obtainMessage(H.GC_WHEN_IDLE));
}
/...
case GC_WHEN_IDLE:
scheduleGcIdler();
break;
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true;
Looper.myQueue().addIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
void unscheduleGcIdler() {
if (mGcIdlerScheduled) {
mGcIdlerScheduled = false;
Looper.myQueue().removeIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
return false;
}
}
void doGcIfNeeded() {
mGcIdlerScheduled = false;
final long now = SystemClock.uptimeMillis();
//Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
// + "m now=" + now);
if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
//Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
BinderInternal.forceGc("bg");
}
}
通过IdleHandler优化页面启动速度
参考链接:yq.aliyun.com/articles/66…
思路:在Activity启动时会执行到handleResumeActivity这个Message中,在这个消息中首先会执行OnResume,接着会将DecorView添加到WindowManager中从而创建出ViewRootImpl,最后ViewRootImpl执行performTraversals开始View的测绘。从这里可以看出如果我们在OnResume中进行一些操作的话是会影响Activity启动速度的,所以我们可以通过IdleHandler将原先在OnResume中执行的任务放到IdleHandler中去处理从而加快页面展示。
@Override
protected void onResume() {
super.onResume();
Looper.myQueue().addIdleHandler(() -> {
initializeData();
return false;
});
Looper.myQueue().addIdleHandler(() -> {
resumeData();
return true;
});
}
img
同步屏障
同步屏障是由系统发送,一般用于刷新UI(如16ms刷新一次界面)。当设置了同步屏障之后,next函数将会忽略所有的同步消息,返回异步消息。换句话说就是,设置了同步屏障之后,Handler只会处理异步消息。再换句话说,同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。
- 如何判断是否为同步屏障消息?****当Message的target为null时(Message不持有Handler)则当前消息为异步消息,也就是同步屏障。
Android应用框架中为了更快的响应UI刷新事件在ViewRootImpl.scheduleTraversals中使用了同步屏障
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//设置同步障碍,确保mTraversalRunnable优先被执行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//内部通过Handler发送了一个异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
作者:lvzishen
转载来源于:https://juejin.cn/post/6844903795445465095
如有侵权,请联系删除!