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 信号期间多次调用是没有意义的- 利用 Handler 的同步屏障机制,优先处理异步消息
- Choreographer 登场
到这里,鼎鼎大名的 编舞者 —— 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 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();
}
}
这里出现了几个新面孔,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);
…
}
}
仔细分别看一下上面两个方法的注释 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();
}
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() 方法
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
最后
**一个零基础的新人,我认为坚持是最最重要的。**我的很多朋友都找我来学习过,我也很用心的教他们,可是不到一个月就坚持不下来了。我认为他们坚持不下来有两点主要原因:
他们打算入行不是因为兴趣,而是因为所谓的IT行业工资高,或者说完全对未来没有任何规划。
刚开始学的时候确实很枯燥,这确实对你是个考验,所以说坚持下来也很不容易,但是如果你有兴趣就不会认为这是累,不会认为这很枯燥,总之还是贵在坚持。
技术提升遇到瓶颈了?缺高级Android进阶视频学习提升自己吗?还有大量大厂面试题为你面试做准备!
来获取学习资料提升自己去挑战一下BAT面试难关吧
对于很多Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些知识图谱希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
不论遇到什么困难,都不应该成为我们放弃的理由!
如果有什么疑问的可以直接私我,我尽自己最大力量帮助你!
是累,不会认为这很枯燥,总之还是贵在坚持。
技术提升遇到瓶颈了?缺高级Android进阶视频学习提升自己吗?还有大量大厂面试题为你面试做准备!
来获取学习资料提升自己去挑战一下BAT面试难关吧
[外链图片转存中…(img-3vwhrKNe-1711323693145)]
对于很多Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些知识图谱希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
不论遇到什么困难,都不应该成为我们放弃的理由!
如果有什么疑问的可以直接私我,我尽自己最大力量帮助你!
最后祝各位新人都能坚持下来,学有所成。