没有合适的资源?快使用搜索试试~ 我知道了~
android 系统
资源推荐
资源详情
资源评论
面试官:说说卡顿问题
卡顿这个话题,相信大部分两年或以上工作经验的同学都应该能说出个大概。一般的
回答可能类似这样:
卡顿是由于主线程有耗时操作,导致 View 绘制掉帧,屏幕每 16 毫秒会刷新一次,每
秒会刷新 60 次,人眼能感觉到卡顿的帧率是每秒 24 帧。所以解决卡顿的办法就是:
耗时操作放到子线程、View 的层级不能太多、要合理使用 include、ViewStub 标签
等等这些,来保证每秒画 24 帧以上。
如果问稍微深一点,卡顿的底层原理是什么?如何理解 16 毫秒刷新一次?假如界面
没有更新操作,View 会每 16 毫秒 draw 一次吗?
接下来将从源码角度分析屏幕刷新机制,深入理解卡顿原理,以及介绍卡顿监控的几
种方式,希望对你有帮助。
一、屏幕刷新机制
从 View#requestLayout 开始分析,因为这个方法是主动请求 UI 更新,从这里分析
完全没问题。
1. View#requestLayout
protected ViewParent mParent;
...
public void requestLayout() {
...
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout(); //1
}
}
主要看注释 1,这里的 mParent.requestLayout(),最终会调用 ViewRootImpl 的
requestLayout 方法。你可能会问,为什么是 ViewRootImpl?因为根 View 是
DecorView,而 DecorView 的 parent 就是 ViewRootImpl,具体看
ViewRootImpl 的 setView 方法里调用 view.assignParent(this);,可以暂且先认为
就是这样的,之后整理 View 的绘制流程的时候会详细分析。
2. ViewRootImpl#requestLayout
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//1 检测线程
checkThread();
mLayoutRequested = true;
//2
scheduleTraversals();
}
}
注释 1 是检测当前是不是在主线程
2.1 ViewRootImpl#checkThread
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException( "Only the
original thread that created a view hierarchy can touch its
views.");
}
}
这个异常很熟悉吧,我们平时说的子线程不能更新 UI,会抛异常,就是在这里判断的,
ViewRootImpl#checkThread
接着看注释 2
2.2 ViewRootImpl#scheduleTraversals
void scheduleTraversals() {
//1、注意这个标志位,多次调用 requestLayout,要这个标志位 false
才有效
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 2. 同步屏障
mTraversalBarrier =
mHandler.getLooper().getQueue().postSyncBarrier();
// 3. 向 Choreographer 提交一个任务
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL
, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
//绘制前发一个通知
notifyRendererOfFramePending();
//这个是释放锁,先不管
pokeDrawLockIfNeeded();
}
}
主要看注释的 3 点:
注释 1:防止短时间多次调用 requestLayout 重复绘制多次,假如调用
requestLayout 之后还没有到这一帧绘制完成,再次调用是没什么意义的。
注释 2: 涉及到 Handler 的一个知识点,同步屏障:往消息队列插入一个同步屏障
消息,这时候消息队列中的同步消息不会被处理,而是优先处理异步消息。这里很好
理解,UI 相关的操作优先级最高,比如消息队列有很多没处理完的任务,这时候启动
一个 Activity,当然要优先处理 Activity 启动,然后再去处理其他的消息,同步屏障
的设计堪称一绝吧。 同步屏障的处理代码在 MessageQueue 的 next 方法:
Message next() {
...
for (;;) {
...
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) { //如果
msg 不为空并且 target 为空
// Stalled by a barrier. Find the next
asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !
msg.isAsynchronous());
}
...
}
逻辑就是:如果 msg 不为空并且 target 为空,说明是一个同步屏障消息,进入 do
while 循环,遍历链表,直到找到异步消息 msg.isAsynchronous()才跳出循环交给
Handler 去处理这个异步消息。
回到上面的注释 3:
mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL,
mTraversalRunnable, null);,往 Choreographer 提交一个任务
mTraversalRunnable,这个任务不会马上就执行,接着看
3. Choreographer
看下 mChoreographer.postCallback
3.1 Choreographer#postCallback
public void postCallback(int callbackType, Runnable action,
Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
public void postCallbackDelayed(int callbackType, Runnable
action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException("action must not
be null");
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is
invalid");
}
postCallbackDelayedInternal(callbackType, action, token,
delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
//1.将任务添加到队列
mCallbackQueues[callbackType].addCallbackLocked(dueTime,
action, token);
//2. 正常延时是 0,走这里
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
//3. 什么时候会有延时,绘制超时,等下一个 vsync?
Message msg =
mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
入参 callbackType 这里传的是 Choreographer.CALLBACK_TRAVERSAL,后面
会说到,最终调用了 postCallbackDelayedInternal 方法。
注释 1:将任务添加到队列,不会马上执行,后面会用到。
注释 2: scheduleFrameLocked,正常的情况下 delayMillis 是 0,走这里,
看下面分析。
注释 3:什么情况下会有延时,TextView 中有调用到,暂时不管。
3.2. Choreographer#scheduleFrameLocked
// Enable/disable vsync for animations and drawing. 系统属性参
数,默认 true
private static final boolean USE_VSYNC =
SystemProperties.getBoolean(
剩余20页未读,继续阅读
资源评论
Keven_Chen123
- 粉丝: 0
- 资源: 5
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功