Android 面试进阶指南目录java
计算机网络git
Androidgithub
即便你不知道 FPS,但你必定据说过这么一句话,在 Android 中,每一帧的绘制时间不要超过 16.67ms。那么,这个 16.67ms 是怎么来的呢?就是由 FPS 决定的。面试
FPS,Frame Per Second,每秒显示的帧数,也叫 帧率。Android 设备的 FPS 通常是 60,也即每秒要刷新 60 帧,因此留给每一帧的绘制时间最多只有 1000/60 = 16.67ms 。一旦某一帧的绘制时间超过了限制,就会发生 掉帧,用户在连续两帧会看到一样的画面。数组
监测 FPS 在必定程度上能够反应应用的卡顿状况,原理也很简单,但前提是你对屏幕刷新机制和绘制流程很熟悉。因此我不会直接进入主题,让咱们先从 View.invalidate()
提及。markdown
要探究屏幕刷新机制和 View 绘制流程,View.invalidate()
无疑是个好选择,它会发起一次绘制流程。网络
> View.java public void invalidate() {
invalidate(true);
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
......
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
// 调用 ViewGroup.invalidateChild()
p.invalidateChild(this, damage);
}
......
}
复制代码
这里调用到 ViewGroup.invalidateChild()
。app
> ViewGroup.java public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
......
ViewParent parent = this;
if (attachInfo != null) {
......
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
......
parent = parent.invalidateChildInParent(location, dirty);
......
} while (parent != null);
}
}
复制代码
这里有一个递归,不停的调用父 View 的 invalidateChildInParent()
方法,直到最顶层父 View 为止。这很好理解,仅靠 View 自己是没法绘制本身的,必须依赖最顶层的父 View 才能够测量,布局,绘制整个 View 树。可是最顶层的父 View 是谁呢?是 setContentView()
传入的布局文件吗?不是,它解析以后被塞进了 DecorView
中。是 DecorView 吗
?也不是,它也是有父亲的。异步
DecorView 的 parent 是谁呢?这就得来到 ActivityThread.handleResume()
方法中。socket
> ActivityThread.java public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
......
// 1. 回调 onResume()
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
......
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
// 2. 添加 decorView 到 WindowManager
wm.addView(decor, l);
......
}
复制代码
第二步中实际调用的是 WindowManagerImpl.addView()
方法,WindowManagerImpl
中又调用了 WindowManagerGlobal.addView()
方法。
> WindowManagerGlobal.java
// 参数 view 就是 DecorView
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
......
ViewRootImpl root;
// 1. 初始化 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view);
mRoots.add(root);
// 2. 重点在这
root.setView(view, wparams, panelParentView);
......
}
复制代码
跟进 ViewRootImpl.setView()
方法。
> ViewRootImpl.java
// 参数 view 就是 DecorView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// 1. 发起首次绘制
requestLayout();
// 2. Binder 调用 Session.addToDisplay(),将 window 添加到屏幕
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
// 3. 重点在这,注意 view 是 DecorView,this 是 ViewRootImpl 自己
view.assignParent(this);
}
}
}
复制代码
跟进 View.assignParent()
方法。
> View.java
// 参数 parent 是 ViewRootImpl
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
复制代码
还记得咱们跟了这么久在干吗吗?为了探究 View 的刷新流程,咱们跟着 View.invalidate()
方法一路追到 ViewGroup.invalidateChild()
,其中递归调用 parent 的 invalidateChildInParent()
方法。因此咱们在 给 DecorView 找爸爸 。如今很清晰了,DecorView 的爸爸就是 ViewRootImpl ,因此最终调用的就是 ViewRootImpl.invalidateChildInParent()
方法。
> ViewRootImpl.java public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
// 1. 线程检查
checkThread();
if (dirty == null) {
// 2. 调用 scheduleTraversals()
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
......
// 3. 调用 scheduleTraversals()
invalidateRectOnScreen(dirty);
return null;
}
复制代码
不管是注释 2 处的 invalite()
仍是注释 3 处的 invalidateRectOnScreen()
,最终都会调用到 scheduleTraversals()
方法。
scheduleTraversals()
在 View 绘制流程中是个极其重要的方法,我不得不单独开一节来聊聊它。
上一节中,咱们从 View.invalidate()
方法开始追踪,一直跟到 ViewRootImpl.scheduleTraversals()
方法。
> ViewRootImpl.java void scheduleTraversals() {
// 1. 防止重复调用
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 2. 发送同步屏障,保证优先处理异步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 3. 最终会执行 mTraversalRunnable 这个任务
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
复制代码
mTraversalScheduled
是个布尔值,防止重复调用,在一次 vsync 信号期间屡次调用是没有意义的到这里,鼎鼎大名的 编舞者 —— Choreographer [ˌkɔːriˈɑːɡrəfər] 就该出场了(为了不面试中出现不会读单词的尴尬,掌握一下发音仍是必须的)。
经过 mChoreographer
发送了一个任务 mTraversalRunnable
,最终会在某个时刻被执行。在看源码以前,先抛出来几个问题:
mChoreographer
是在何时初始化的?mTraversalRunnable
是个什么鬼?mChoreographer
是如何发送任务以及任务是如何被调度执行的?围绕这三个问题,咱们再回到源码中。
先来看第一个问题,这就得回到上一节介绍过的 WindowManagerGlobal.addView()
方法。
> WindowManagerGlobal.java
// 参数 view 就是 DecorView
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
......
ViewRootImpl root;
// 1. 初始化 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view);
mRoots.add(root);
root.setView(view, wparams, panelParentView);
......
}
复制代码
注释 1 处 新建了 ViewRootImpl 对象,跟进 ViewRootImpl 的构造函数。
> ViewRootImpl.java public ViewRootImpl(Context context, Display display) {
mContext = context;
// 1. IWindowSession 代理对象,与 WMS 进行 Binder 通讯
mWindowSession = WindowManagerGlobal.getWindowSession();
......
mThread = Thread.currentThread();
......
// IWindow Binder 对象
mWindow = new W(this);
......
// 2. 初始化 mAttachInfo
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
......
// 3. 初始化 Choreographer,经过 Threadlocal 存储
mChoreographer = Choreographer.getInstance();
......
}
复制代码
在 ViewRootImpl
的构造函数中,注释 3 处初始化了 mChoreographer
,调用的是 Choreographer.getInstance()
方法。
> Choreographer.java public static Choreographer getInstance() {
return sThreadInstance.get();
}
复制代码
sThreadInstance
是一个 ThreadLocal<Choreographer>
对象。
> Choreographer.java
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
// 新建 Choreographer 对象
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
复制代码
因此 mChoreographer 保存在 ThreadLocal 中的线程私有对象。它的构造函数中须要传入当前线程(这里就是主线程)的 Looper 对象。
这里再插一个题外话,主线程 Looper 是在何时建立的? 回顾一下应用进程的建立流程:
调用 Process.start()
建立应用进程
ZygoteProcess
负责和 Zygote
进程创建 socket 链接,并将建立进程须要的参数发送给 Zygote
的 socket 服务端
Zygote
服务端接收到参数以后调用 ZygoteConnection.processOneCommand()
处理参数,并 fork 进程
最后经过 findStaticMain()
找到 ActivityThread
类的 main()
方法并执行,子进程就启动了
ActivityThread
并非一个线程,但它是运行在主线程上的,主线程 Looper 就是在它的 main()
方法中执行的。
> ActivityThread.java public static void main(String[] args) {
......
// 建立主线程 Looper
Looper.prepareMainLooper();
......
// 建立 ActivityThread ,并 attach(false)
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
......
// 开启主线程消息循环
Looper.loop();
}
复制代码
Looper 也是存储在 ThreadLocal 中的。
再回到 Choreographer,咱们来看一下它的构造函数。
> Choreographer.java private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
// 处理事件
mHandler = new FrameHandler(looper);
// USE_VSYNC 在 Android 4.1 以后默认为 true,
// FrameDisplayEventReceiver 是个 vsync 事件接收器
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
// 一帧的时间,60pfs 的话就是 16.7ms
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
// 回调队列
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
复制代码
这里出现了几个新面孔,FrameHandler
、FrameDisplayEventReceiver
、CallbackQueue
,这里暂且不表,先混个脸熟,后面会一一说到。
介绍完 Choreographer 是如何初始化的,再回到 Choreographer 发送任务那块。
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
复制代码
咱们看看 mTraversalRunnable 是什么东西。
> ViewRootImpl.java
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
复制代码
没什么特别的,它就是一个 Runnable 对象,run() 方法中会执行 doTraversal()
方法。
> ViewRootImpl.java void doTraversal() {
if (mTraversalScheduled) {
// 1. mTraversalScheduled 置为 false
mTraversalScheduled = false;
// 2. 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 3. 开始布局,测量,绘制流程
performTraversals();
......
}
复制代码
再对比一下最开始发起绘制的 scheduleTraversals()
方法:
> ViewRootImpl.java void scheduleTraversals() {
// 1. mTraversalScheduled 置为 true,防止重复调用
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 2. 发送同步屏障,保证优先处理异步消息
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 3. 最终会执行 mTraversalRunnable 这个任务
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
复制代码
仔细分别看一下上面两个方法的注释 一、二、 3,仍是很清晰的。mTraversalRunnable
被执行后最终会调用 performTraversals()
方法,来完成整个 View 的测量,布局和绘制流程。
分析到这里,就差最后一步了,mTraversalRunnable 是如何被调度执行的? 咱们再回到 Choreographer.postCallback()
方法。
> Choreographer.java 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) {
......
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
// 传入的参数依次是 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null,0
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
......
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
// 1. 将 mTraversalRunnable 塞入队列
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) { // 当即执行
// 2. 因为 delayMillis 是 0,因此会执行到这里
scheduleFrameLocked(now);
} else { // 延迟执行
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
复制代码
首先根据 callbackType(这里是 CALLBACK_TRAVERSAL) 将稍后要执行的 mTraversalRunnable 放入相应队列中,其中的具体逻辑就不看了。
而后因为 delayMillis 是 0,因此 dueTime 和 now 是相等的,因此直接执行 scheduleFrameLocked(now)
方法。若是 delayMillis 不为 0 的话,会经过 FrameHandler 发送一个延时消息,最后执行的仍然是 scheduleFrameLocked(now)
方法。
> Choreographer.java private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) { // Android 4.1 以后 USE_VSYNCUSE_VSYNC 默认为 true
// 若是是当前线程,直接申请 vsync,不然经过 handler 通讯
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
// 发送异步消息
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else { // 未开启 vsync,4.1 以后默认开启
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
复制代码
开头到如今,已经好几回提到了 VSYNC
,官方也有一个视频介绍 Android Performance Patterns: Understanding VSYNC ,你们能够看一看。简而言之,VSYNC 是为了解决屏幕刷新率和 GPU 帧率不一致致使的 “屏幕撕裂” 问题。VSYNC 在 PC 端是好久以来就存在的技术,但在 4.1 以后,Google 才将其引入到 Android 显示系统中,以解决饱受诟病的 UI 显示不流畅问题。
再说的简单点,能够把 VSYNC 当作一个由硬件发出的定时信号,经过 Choreographer 监听这个信号。每当信号来临时,统一开始绘制工做。这就是 scheduleVsyncLocked()
方法的工做内容。
> Choreographer.java private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
复制代码
mDisplayEventReceiver
是 FrameDisplayEventReceiver
对象,但它并无 scheduleVsync()
方法,而是直接调用的父类方法。FrameDisplayEventReceiver
的父类是 DisplayEventReceiver
。
> DisplayEventReceiver.java
public abstract class DisplayEventReceiver {
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
// 注册监听 vsync 信号,会回调 dispatchVsync() 方法
nativeScheduleVsync(mReceiverPtr);
}
}
// 有 vsync 信号时,由 native 调用此方法
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
// timestampNanos 是 vsync 回调的时间
onVsync(timestampNanos, builtInDisplayId, frame);
}
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
}
}
复制代码
在 scheduleVsync()
方法中会经过 nativeScheduleVsync()
方法注册下一次 vsync 信号的监听,从方法名也能看出来,下面会进入 native 调用,水平有限,就不追进去了。
注册监听以后,当下次 vsync 信号来临时,会经过 jni 回调 java 层的 dispatchVsync()
方法,其中又调用了 onVsync()
方法。父类 DisplayEventReceiver
的 onVsync()
方法是个空实现,咱们再回到子类 FrameDisplayEventReceiver
,它是 Choreographer 的内部类。
> Choreographer.java
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
private long mTimestampNanos;
private int mFrame;
// vsync 信号监听回调
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
......
long now = System.nanoTime();
// // timestampNanos 是 vsync 回调的时间,不能比 now 大
if (timestampNanos > now) {
Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ " ms in the future! Check that graphics HAL is generating vsync "
+ "timestamps using the correct timebase.");
timestampNanos = now;
}
......
mTimestampNanos = timestampNanos;
mFrame = frame;
// 这里传入的是 this,会回调自己的 run() 方法
Message msg = Message.obtain(mHandler, this);
// 这是一个异步消息,保证优先执行
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
doFrame(mTimestampNanos, mFrame);
}
}
复制代码
在 onVsync()
回调中,向主线程发送了一个异步消息,注意 sendMessageAtTime()
方法参数中的时间是 timestampNanos / TimeUtils
。timestampNanos
是 vsync 信号的时间戳,单位是纳秒,因此这里作一个除法转换为毫秒。代码执行到这里的时候 vsync 信号已经发生,因此 timestampNanos
是比当前时间小的。这样这个消息塞进 MessageQueue 的时候就能够直接塞到前面了。另外 callback 是 this,因此当消息被执行时,调用的是本身的 run() 方法,run() 方法中调用的是 doFrame()
方法。
> Choreographer.java void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
if (!mFrameScheduled) {
return; // no work to do
}
......
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
// 计算超时时间
// frameTimeNanos 是 vsync 信号回调的时间,startNanos 是当前时间戳
// 相减获得主线程的耗时时间
final long jitterNanos = startNanos - frameTimeNanos;
// mFrameIntervalNanos 是一帧的时间
if (jitterNanos >= mFrameIntervalNanos) {
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
// 掉帧超过 30 帧,打印 log
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
}
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
frameTimeNanos = startNanos - lastFrameOffset;
}
......
}
try {
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
// doCallBacks() 开始执行回调
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
}
......
}
复制代码
在 Choreographer.postCallback()
方法中将 mTraversalRunnable
塞进了 mCallbackQueues[]
数组中,下面的 doCallbacks()
方法就要把它取出来执行了。
> Choreographer.java void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
final long now = System.nanoTime();
// 根据 callbackType 找到对应的 CallbackRecord 对象
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
......
}
try {
for (CallbackRecord c = callbacks; c != null; c = c.next) {
// 执行 callBack
c.run(frameTimeNanos);
}
} finally {
......
}
}
复制代码
根据 callbackType
找到对应的 mCallbackQueues
,而后执行,具体流程就不深刻分析了。callbackType
共有四个类型,分别是 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT 。
> Choreographer.CallbackRecord public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
复制代码
到此为止,mTraversalRunnable
得以被执行,View.invalidate()
的整个流程就走通了。总结一下:
从 View.invalidate()
开始,最后会递归调用 parent.invalidateChildInParent()
方法。这里最顶层的 parent 是 ViewRootImpl
。ViewRootImpl 是 DecorView 的 parent,这个赋值调用链是这样的 ActivityThread.handleResumeActivity -> WindowManagerImpl.addView() -> WindowManagerGlobal.addView() -> ViewRootImpl.setView() -> View.assignParent()
。
ViewRootImpl.invalidateChildInParent()
最终调用到 scheduleTraversals()
方法,其中创建同步屏障以后,经过 Choreographer.postCallback()
方法提交了任务 mTraversalRunnable
,这个任务就是负责 View 的测量,布局,绘制。
Choreographer.postCallback()
方法经过 DisplayEventReceiver.nativeScheduleVsync()
方法向系统底层注册了下一次 vsync 信号的监听。当下一次 vsync 来临时,系统会回调其 dispatchVsync()
方法,最终回调 FrameDisplayEventReceiver.onVsync()
方法。
FrameDisplayEventReceiver.onVsync()
方法中取出以前提交的 mTraversalRunnable
并执行。这样就完成了整个绘制流程。
监测当前应用的 FPS 很简单。每次 vsync 信号回调中,都会执行四种类型的 mCallbackQueues
队列中的回调任务。而 Choreographer
又对外提供了提交回调任务的方法,这个方法就是 Choreographer.getInstance().postFrameCallback()
。简单跟进去看一下。
> Choreographer.java public void postFrameCallback(FrameCallback callback) {
postFrameCallbackDelayed(callback, 0);
}
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
......
// 这里的类型是 CALLBACK_ANIMATION
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
复制代码
和 View.invalite()
流程中调用的 Choreographer.postCallback()
基本一致,仅仅只是 callback 类型不一致,这里是 CALLBACK_ANIMATION
。
我直接给出实现代码 :FpsMonitor.kt
object FpsMonitor {
private const val FPS_INTERVAL_TIME = 1000L
private var count = 0
private var isFpsOpen = false
private val fpsRunnable by lazy { FpsRunnable() }
private val mainHandler by lazy { Handler(Looper.getMainLooper()) }
private val listeners = arrayListOf<(Int) -> Unit>()
fun startMonitor(listener: (Int) -> Unit) {
// 防止重复开启
if (!isFpsOpen) {
isFpsOpen = true
listeners.add(listener)
mainHandler.postDelayed(fpsRunnable, FPS_INTERVAL_TIME)
Choreographer.getInstance().postFrameCallback(fpsRunnable)
}
}
fun stopMonitor() {
count = 0
mainHandler.removeCallbacks(fpsRunnable)
Choreographer.getInstance().removeFrameCallback(fpsRunnable)
isFpsOpen = false
}
class FpsRunnable : Choreographer.FrameCallback, Runnable {
override fun doFrame(frameTimeNanos: Long) {
count++
Choreographer.getInstance().postFrameCallback(this)
}
override fun run() {
listeners.forEach { it.invoke(count) }
count = 0
mainHandler.postDelayed(this, FPS_INTERVAL_TIME)
}
}
}
复制代码
大体逻辑是这样的 :
count
用于统计回调次数Choreographer.getInstance().postFrameCallback(fpsRunnable)
注册监听下一次 vsync信号,提交任务,任务回调只作两件事,一是 count++
,二是继续注册监听下一次 vsync 信号 。count
值并清空。来张 GIF 感觉一下,源码已经上传到个人开源项目 Mamba 。
目前也有不少开源项目实现了 FPS 监测或者流畅度检测的功能。
滴滴开源的 DoraemonKit 的作法和上面介绍的是一致的(没错,我就是仿照它写的,/goutou),能够看一下 PerformanceDataManager.startMonitorFrameInfo()
方法的实现。
腾讯开源的 Matrix 虽然也是在 Choreographer 上动手脚,但作的更加完全,它能够监听到 CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL 三种事件各自精确的耗时,重点关注 LooperMonitor
和 UIThreadMonitor
这两个类。具体原理这里就再也不分析了,能够给你们推荐一篇文章, Matrix-TraceCanary的设计和原理分析手册 。
这一期的文章到这就结束了。以 如何监测应用的 FPS 为引子,重点讲述了屏幕刷新机制和 Choreographer 的工做流程,中间也掺杂了一些其余内容。后续的文章也将延续此风格,尽可能涵括更多的知识点,敬请期待。
最后留下一个简单的问题,为何会发生掉帧? 欢迎在评论区进行交流。
我是秉心说,下期再见。