Android-高工面试真题:说说你对FPS的理解?如何检测应用的FPS

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);

}
}

  1. mTraversalScheduled 是个布尔值,防止重复调用,在一次 vsync 信号期间多次调用是没有意义的
  2. 利用 Handler 的同步屏障机制,优先处理异步消息
  3. Choreographer 登场

到这里,鼎鼎大名的 编舞者 —— Choreographer [ˌkɔːriˈɑːɡrəfər] 就该出场了(为了避免面试中出现不会读单词的尴尬,掌握一下发音还是必须的)。

通过 mChoreographer 发送了一个任务 mTraversalRunnable ,最终会在某个时刻被执行。在看源码之前,先抛出来几个问题:

  1. mChoreographer 是在什么时候初始化的?
  2. mTraversalRunnable 是个什么鬼?
  3. 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 sThreadInstance =
new ThreadLocal() {
@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();
}
}

这里出现了几个新面孔,FrameHandlerFrameDisplayEventReceiverCallbackQueue,这里暂且不表,先混个脸熟,后面会一一说到。

介绍完 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);

}
}

仔细分别看一下上面两个方法的注释 1、2、 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();
}

mDisplayEventReceiverFrameDisplayEventReceiver 对象,但它并没有 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() 方法。父类 DisplayEventReceiveronVsync() 方法是个空实现,我们再回到子类 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() 方法

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

**一个零基础的新人,我认为坚持是最最重要的。**我的很多朋友都找我来学习过,我也很用心的教他们,可是不到一个月就坚持不下来了。我认为他们坚持不下来有两点主要原因:

他们打算入行不是因为兴趣,而是因为所谓的IT行业工资高,或者说完全对未来没有任何规划。

刚开始学的时候确实很枯燥,这确实对你是个考验,所以说坚持下来也很不容易,但是如果你有兴趣就不会认为这是累,不会认为这很枯燥,总之还是贵在坚持。

技术提升遇到瓶颈了?缺高级Android进阶视频学习提升自己吗?还有大量大厂面试题为你面试做准备!

点击:Android 学习,面试文档,视频收集大整理

来获取学习资料提升自己去挑战一下BAT面试难关吧

对于很多Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些知识图谱希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

不论遇到什么困难,都不应该成为我们放弃的理由!

如果有什么疑问的可以直接私我,我尽自己最大力量帮助你!

是累,不会认为这很枯燥,总之还是贵在坚持。

技术提升遇到瓶颈了?缺高级Android进阶视频学习提升自己吗?还有大量大厂面试题为你面试做准备!

点击:Android 学习,面试文档,视频收集大整理

来获取学习资料提升自己去挑战一下BAT面试难关吧

[外链图片转存中…(img-3vwhrKNe-1711323693145)]

对于很多Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些知识图谱希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

不论遇到什么困难,都不应该成为我们放弃的理由!

如果有什么疑问的可以直接私我,我尽自己最大力量帮助你!

最后祝各位新人都能坚持下来,学有所成。

  • 30
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值