Android 系统在 VSYNC 信号的指引下,有条不紊地进行者每一帧的渲染、合成操作,使我们可以享受稳定帧率的画面。引入 VSYNC 之前的 Android 版本,渲染一帧相关的 Message ,中间是没有间隔的,上一帧绘制完,下一帧的 Message 紧接着就开始被处理。这样的问题就是,帧率不稳定,可能高也可能低,不稳定。

Choreographer 是 Android 4.1 新增的机制,主要是配合 VSYNC ,给上层 App 的渲染提供一个稳定的 Message 处理的时机,也就是 VSYNC 到来的时候 ,系统通过对 VSYNC 信号周期的调整,来控制每一帧绘制操作的时机。目前大部分手机都是 60Hz 的刷新率,也就是 16.6ms 刷新一次,系统为了配合屏幕的刷新频率,将 VSYNC 的周期也设置为 16.6 ms,每个 16.6 ms,VSYNC 信号唤醒 Choreographer 来做 App 的绘制操作 ,这就是引入 Choreographer 的主要作用。

一、Choreographer 简介

Choreographer 通过 Choreographer + SurfaceFlinger + Vsync + TripleBuffer 这一套从上到下的机制,保证了 Android App 可以以一个稳定的帧率运行(目前大部分是 60fps),减少帧率波动带来的不适感。

1、负责接收和处理 App 的各种更新消息和回调,等到 VSYNC 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等。

2、负责请求和接收 VSYNC 信号。接收 VSYNC 事件回调(通过 FrameDisplayEventReceiver.onVsync());请求 VSYNC(FrameDisplayEventReceiver.scheduleVsync())。

二、原理解析

1、初始化

Choreographer构造方法
private Choreographer(Looper looper, int vsyncSource) {
mLooper = looper;
//初始化FrameHandler,与looper绑定
mHandler = new FrameHandler(looper);
// 初始化 DisplayEventReceiver(开启VSYNC后将通过FrameDisplayEventReceiver接收VSYNC脉冲信号)
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
// 计算一帧的时间,Android手机屏幕采用60Hz的刷新频率(这里是纳秒 ≈16000000ns 还是16ms)
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
// 初始化CallbackQueue(CallbackQueue中存放要执行的输入、动画、遍历绘制等任务)
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
// b/68769804: For low FPS experiments.
setFPSDivisor(SystemProperties.getInt(ThreadedRenderer.DEBUG_FPS_DIVISOR, 1));
}
获取Choreographer实例
    public static Choreographer getInstance() {
return sThreadInstance.get();
} // Thread local storage for the choreographer.
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 = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};

可以看出构造的实例是与线程绑定的。

2、FrameHandler

    private final class FrameHandler extends Handler {
public FrameHandler(Looper looper) {
super(looper);
} @Override
public void handleMessage(Message msg) {
switch (msg.what) {
// 如果启用VSYNC机制,当VSYNC信号到来时触发
// 执行doFrame(),开始渲染下一帧的操作
case MSG_DO_FRAME:
doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());
break;
// 请求VSYNC信号,例如当前需要绘制任务时
case MSG_DO_SCHEDULE_VSYNC:
doScheduleVsync();
break;
// 处理 Callback(需要延迟的任务,最终还是执行上述两个事件)
case MSG_DO_SCHEDULE_CALLBACK:
doScheduleCallback(msg.arg1);
break;
}
}
}

Choreographer 的所有任务最终都会发送到该 Looper 所在的线程。

3、Choreographer 初始化链

在 Activity 启动过程,执行完 onResume() 后,会调用 Activity.makeVisible(),然后再调用到 addView(), 层层调用会进入如下方法:

点击查看代码
ActivityThread.handleResumeActivity(IBinder, boolean, boolean, String) (android.app)
-->WindowManagerImpl.addView(View, LayoutParams) (android.view)
-->WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view)
-->ViewRootImpl.ViewRootImpl(Context, Display) (android.view)
public ViewRootImpl(Context context, Display display) {
......
mChoreographer = Choreographer.getInstance();
......
}

4、FrameDisplayEventReceiver

VSYNC 的注册、申请、接收都是通过 FrameDisplayEventReceiver 这个类,FrameDisplayEventReceiver 是 DisplayEventReceiver 的子类, 有三个比较重要的方法:

  • onVsync():Vsync 信号回调,系统native方法会调用。
  • scheduleVsync():请求 Vsync 信号,当应用需要绘制时,通过 scheduledVsync() 方法申请 VSYNC 中断,来自 EventThread 的 VSYNC 信号就可以传递到 Choreographer。
  • run():执行 doFrame。

DisplayEventReceiver 是一个 abstract class,在 DisplayEventReceiver 的构造方法会通过 JNI 创建一个 IDisplayEventConnection 的 VSYNC 的监听者。

    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
private boolean mHavePendingVsync;
private long mTimestampNanos;
private int mFrame;
private VsyncEventData mLastVsyncEventData = new VsyncEventData(); public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
super(looper, vsyncSource, 0);
} // TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
// the internal display and DisplayEventReceiver#scheduleVsync only allows requesting VSYNC
// for the internal display implicitly.
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"Choreographer#onVsync " + vsyncEventData.id);
}
// Post the vsync event to the Handler.
// The idea is to prevent incoming vsync events from completely starving
// the message queue. If there are no messages in the queue with timestamps
// earlier than the frame time, then the vsync event will be processed immediately.
// Otherwise, messages that predate the vsync event will be handled first.
long now = System.nanoTime();
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;
} if (mHavePendingVsync) {
Log.w(TAG, "Already have a pending vsync event. There should only be "
+ "one at a time.");
} else {
mHavePendingVsync = true;
} mTimestampNanos = timestampNanos;
mFrame = frame;
mLastVsyncEventData = vsyncEventData;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
} @Override
public void run() {
mHavePendingVsync = false;
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}
}

可以看出,其最终是执行了run,然后调用doFrame绘制一帧。

5、Choreographer 处理一帧的逻辑

    void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
final long startNanos;
final long frameIntervalNanos = vsyncEventData.frameInterval;
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"Choreographer#doFrame " + vsyncEventData.id);
}
synchronized (mLock) {
if (!mFrameScheduled) {
traceMessage
("Frame not scheduled");
return; // no work to do
} if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
mDebugPrintNextFrameTimeDelta = false;
Log.d(TAG, "Frame time delta: "
+ ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
}
//预期执行时间
long intendedFrameTimeNanos = frameTimeNanos;
//当前时间
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
//超过的时间是否大于一帧的时间
if (jitterNanos >= frameIntervalNanos) {
final long skippedFrames = jitterNanos / frameIntervalNanos;
//掉帧超过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 % frameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (frameIntervalNanos * 0.000001f) + " ms! "
+ "Skipping " + skippedFrames + " frames and setting frame "
+ "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
}
frameTimeNanos = startNanos - lastFrameOffset;
}
// 未知原因,居然小于最后一帧的时间,重新申请VSYNC信号
if (frameTimeNanos < mLastFrameTimeNanos) {
if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
traceMessage("Frame time goes backward");
scheduleVsyncLocked();
return;
} if (mFPSDivisor > 1) {
long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
traceMessage("Frame skipped due to FPSDivisor");
scheduleVsyncLocked();
return;
}
} mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos; mLastFrameIntervalNanos = frameIntervalNanos;
mLastVsyncEventData = vsyncEventData;
} AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart();
//执行input任务
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos); mFrameInfo.markAnimationsStart();
//执行animation任务
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
//执行Insert animation任务
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
frameIntervalNanos); mFrameInfo.markPerformTraversalsStart();
//执行traversal任务,即测量绘制任务
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
//执行commit任务
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
} if (DEBUG_FRAMES) {
final long endNanos = System.nanoTime();
Log.d(TAG, "Frame " + frame + ": Finished, took "
+ (endNanos - startNanos) * 0.000001f + " ms, latency "
+ (startNanos - frameTimeNanos) * 0.000001f + " ms.");
}
}

可以看到执行任务的顺序为:

doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);

doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);

doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos, frameIntervalNanos);

doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);

doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);

我们看一下doCalbacks方法:

    void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
// We use "now" to determine when callbacks become due because it's possible
// for earlier processing phases in a frame to post callbacks that should run
// in a following phase, such as an input event that causes an animation to start.
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true; // Update the frame time if necessary when committing the frame.
// We only update the frame time if we are more than 2 frames late reaching
// the commit phase. This ensures that the frame time which is observed by the
// callbacks will always increase from one frame to the next and never repeat.
// We never want the next frame's starting frame time to end up being less than
// or equal to the previous frame's commit frame time. Keep in mind that the
// next frame has most likely already been scheduled by now so we play it
// safe by ensuring the commit time is always at least one frame behind.
if (callbackType == Choreographer.CALLBACK_COMMIT) {
final long jitterNanos = now - frameTimeNanos;
Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos);
if (jitterNanos >= 2 * frameIntervalNanos) {
final long lastFrameOffset = jitterNanos % frameIntervalNanos
+ frameIntervalNanos;
if (DEBUG_JANK) {
Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+ " ms which is more than twice the frame interval of "
+ (frameIntervalNanos * 0.000001f) + " ms! "
+ "Setting frame time to " + (lastFrameOffset * 0.000001f)
+ " ms in the past.");
mDebugPrintNextFrameTimeDelta = true;
}
frameTimeNanos = now - lastFrameOffset;
mLastFrameTimeNanos = frameTimeNanos;
}
}
}
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
c.run(frameTimeNanos);
}
} finally {
synchronized (mLock) {
mCallbacksRunning = false;
do {
final CallbackRecord next = callbacks.next;
recycleCallbackLocked(callbacks);
callbacks = next;
} while (callbacks != null);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

CallbackRecord是一个链表,从上面可以看出其遍历了链表,并且执行了CallbackRecord里面的run方法。

6、CallbackQueue与CallbackRecord

private final CallbackQueue[] mCallbackQueues;

    private final class CallbackQueue {
private CallbackRecord mHead;
.
.
.
}

mCallbackQueues是一个CallbackQueue的数组,里面保存着CallbackQueue对象,而CallbackQueue里面持有CallbackRecord对象。

CallbackQueue保存了通过postCallback()添加的任务,目前定义的任务类型有:

  • CALLBACK_INPUT:优先级最高,和输入事件处理有关。
  • CALLBACK_ANIMATION:优先级其次,和 Animation 的处理有关
  • CALLBACK_INSETS_ANIMATION:优先级其次,和 Insets Animation 的处理有关
  • CALLBACK_TRAVERSAL:优先级最低,和 UI 绘制任务有关
  • CALLBACK_COMMIT:最后执行,和提交任务有关(在 API Level 23 添加)

    并且这些任务都是按顺序添加进去的,当收到VSYNC信号时候,会按照顺序执行这些任务。

CallbackRecord的数据结构如下

    private static final class
CallbackRecord {
public CallbackRecord next;
public long dueTime;
public Object action; // Runnable or FrameCallback
public Object token; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
// 通过postFrameCallback()或postFrameCallbackDelayed()添加的任务会执行这里
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
}

可以看出CallbackRecord其实是一个链表结构,它的run方法会调用传进来的Runnable的run方法或者FrameCallback的doFrame方法。

7、添加任务

    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;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}

里面会调用mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        @UnsupportedAppUsage
public void addCallbackLocked(long dueTime, Object action, Object token) {
CallbackRecord callback = obtainCallbackLocked(dueTime, action, token);
CallbackRecord entry = mHead;
if (entry == null) {
mHead = callback;
return;
}
if (dueTime < entry.dueTime) {
callback.next = entry;
mHead = callback;
return;
}
while (entry.next != null) {
if (dueTime < entry.next.dueTime) {
callback.next = entry.next;
break;
}
entry = entry.next;
}
entry.next = callback;
}

其实就是取出数组里对应任务链表,按照时间添加到链表中的对应位置。

三、调用栈

1、Animation 回调调用栈

一般接触的多的是调用 View.postOnAnimation() 的时候,会使用到 CALLBACK_ANIMATION,View 的 postOnAnimation() 方法源码如下:

public void postOnAnimation(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
// 判断AttachInfo是否为空
if (attachInfo != null) {
// 如果不为null,直接调用Choreographer.postCallback()方法
attachInfo.mViewRootImpl.mChoreographer.postCallback(
Choreographer.CALLBACK_ANIMATION, action, null);
} else {
// 否则加入当前View的等待队列
getRunQueue().post(action);
}
}

另外 Choreographer 的 FrameCallback 也是用的 CALLBACK_ANIMATION,Choreographer 的 postFrameCallbackDelayed() 方法源码如下:

public void postFrameCallbackDelayed(Choreographer.FrameCallback callback, long delayMillis) { if (callback == null) { throw new IllegalArgumentException("callback must not be null"); } postCallbackDelayedInternal(CALLBACK_ANIMATION, callback, FRAME_CALLBACK_TOKEN, delayMillis);

2、ViewRootImpl 调用栈

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
} final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
} void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
} performTraversals(); if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}

四、总结

  1. Choreographer 是线程单例的,而且必须要和一个Looper绑定,因为其内部有一个Handler需要和Looper绑定,一般是 App 主线程的Looper` 绑定。
  2. DisplayEventReceiver 是一个 abstract class,其 JNI 的代码部分会创建一个IDisplayEventConnection 的 VSYNC 监听者对象。这样,来自 AppEventThread 的 VSYNC 中断信号就可以传递给 Choreographer 对象了。当 VSYNC 信号到来时,DisplayEventReceiver 的 onVsync() 方法将被调用。
  3. DisplayEventReceiver 还有一个 scheduleVsync` 函数。当应用需要绘制UI时,将首先申请一次 Vsync 中断,然后再在中断处理的 onVsync 函数去进行绘制。
  4. Choreographer 定义了一个 FrameCallback 接口,每当 VSYNC 到来时,其 doFrame() 函数将被调用。这个接口对 Android Animation 的实现起了很大的帮助作用。
  5. Choreographer 的主要功能是,当收到 VSYNC 信号时,去调用使用者通过 postCallback() 设置的回调函数。目前一共定义了五种类型的回调,它们分别是:
  6. CALLBACK_INPUT:处理输入事件处理有关
  7. CALLBACK_ANIMATION:处理 Animation 的处理有关
  8. CALLBACK_INSETS_ANIMATION:处理 Insets Animation 的相关回调
  9. CALLBACK_TRAVERSAL:处理和 UI 等控件绘制有关
  10. CALLBACK_COMMIT:处理 Commit 相关回调
  11. ListView 的 Item 初始化(obtain\setup) 会在 Input 里面也会在 Animation 里面,这取决于 CALLBACK_INPUT、CALLBACK_ANIMATION 会修改 View 的属性,所以要先与 CALLBACK_TRAVERSAL 执行。
  12. 每次执行完callback后会自动移除。

参考:https://henleylee.github.io/posts/2020/f831dca5.html,https://androidperformance.com/2019/10/22/Android-Choreographer/#/Choreographer-自身的掉帧计算逻辑

Choreographer原理的更多相关文章

  1. 90%的开发者都不知道的UI本质原理和优化方式

    前言 很多开发者在工作中一直和UI打交道,所以认为UI非常的简单! 事实上对于90%的开发者来说,不知道UI的本质原理. 虽然在开发中,我们在接到产品的UI需求之后,可以走捷径照抄大型APP代码,但是 ...

  2. 【转】Android系统开篇

    版权声明:本站所有博文内容均为原创,转载请务必注明作者与原文链接,且不得篡改原文内容.另外,未经授权文章不得用于任何商业目的. 一.引言 Android系统非常庞大.错综复杂,其底层是采用Linux作 ...

  3. Android动画原理分析

    最近在Android上做了一些动画效果,网上查了一些资料,有各种各样的使用方式,于是乘热打铁,想具体分析一下动画是如何实现的,Animation, Animator都有哪些区别等等. 首先说Anima ...

  4. View 动画 Animation 运行原理解析

    这次想来梳理一下 View 动画也就是补间动画(ScaleAnimation, AlphaAnimation, TranslationAnimation...)这些动画运行的流程解析.内容并不会去分析 ...

  5. 属性动画 ValueAnimator 运行原理全解析

    最近下班时间都用来健身还有看书了,博客被晾了一段时间了,原谅我~~~~ 提问环节 好,废话不多说,之前我们已经分析过 View 动画 Animation 运行原理解析,那么这次就来学习下属性动画的运行 ...

  6. Android的DataBinding原理介绍

    Activity在inflate layout时,通过DataBindingUtil来生成绑定,从代码看,是遍历contentView得到View数组对象,然后通过数据绑定library生成对应的Bi ...

  7. Android系统显示原理

    Android的显示过程可以概括为:Android应用程序把经过测量.布局.绘制后的surface缓存数据,通过SurfaceFlinger把数据渲染到屏幕上,通过Android的刷新机制来刷新数据. ...

  8. React-Native系列Android——Touch事件原理及状态效果

    Native原生相比于Hybrid或H5最大长处是具有流畅和复杂的交互效果,触摸事件便是当中重要一项,包括点击(Click).长按(LongClick).手势(gesture)等. 以最简单常见的点击 ...

  9. Android DecorView 与 Activity 绑定原理分析

    一年多以前,曾经以为自己对 View 的绘制已经有了解了,事后发现也只是懂了些皮毛而已.经过一年多的实战,Android 和 Java 基础都有了提升,时机成熟了,是时候该去总结 View 的绘制流程 ...

  10. View Animation 运行原理解析

    Android 平台目前提供了两大类动画,在 Android 3.0 之前,一大类是 View Animation,包括 Tween animation(补间动画),Frame animation(帧 ...

随机推荐

  1. elementUI日期选择器,对日期格式进行处理

    使用elementUI日期选择器中,获取不同格式的时间 <el-form-item label="归零时间:" prop="zeroing"> &l ...

  2. Fabric网络升级(一)

    原文来自这里. 本章节主要介绍如何从之前的版本或其他长期支持版本升级至最新版. 从2.1升级到2.2 Fabric v2.1和v2.2都是稳定版,以bug修复和其它形式的代码加固位置.因此,升级不需要 ...

  3. 『Echarts』简介

    目录 一.前言 二.『Echarts』简介 1. 什么是『Echarts』 三.数据可视化 四.『Echarts』 1.『Echarts』的作用 2.『Echarts』能绘制哪些图表 3.『Echar ...

  4. 淘宝一面:“说一下 Spring Boot 自动装配原理呗?”

    本文已经收录进 Github 95k+ Star 的Java项目JavaGuide .JavaGuide项目地址 : https://github.com/Snailclimb/JavaGuide . ...

  5. ElasticSearch深度解析入门篇:高效搜索解决方案的介绍与实战案例讲解,带你避坑

    ElasticSearch深度解析入门篇:高效搜索解决方案的介绍与实战案例讲解,带你避坑 1.Elasticsearch 产生背景 大规模数据如何检索 如:当系统数据量上了 10 亿.100 亿条的时 ...

  6. 【三】强化学习之PaddlePaddlle-Notebook、&pdb、ipdb 调试---及PARL框架

    相关文章: [一]飞桨paddle[GPU.CPU]安装以及环境配置+python入门教学 [二]-Parl基础命令 [三]-Notebook.&pdb.ipdb 调试 [四]-强化学习入门简 ...

  7. MySQL【一】基本使用----超详细教学

    相关文章: win10下MySQL安装教程(MySql-8.0.26超级详细)_丨汀.的博客-CSDN博客 1.RDBMS(Relational Databases Management System ...

  8. 机器学习算法(四): 基于支持向量机的分类预测(SVM)

    机器学习算法(四): 基于支持向量机的分类预测 本项目链接:https://www.heywhale.com/home/column/64141d6b1c8c8b518ba97dcc 1.相关流程 支 ...

  9. Axure谷歌浏览器扩展程序下载及安装方法(免FQ)

    在用Axure在chrome查看原型时,没有安装Axure谷歌浏览器插件时无法显示会有提示信息,如果未FQ按照提示是无法直接安装扩展程序的,这里提供插件下载地址并教大家如何安装插件. 平时在使用谷歌浏 ...

  10. Oracle 19c RAC 自动应用RU补丁简明版

    环境:Oracle RAC(GI 19.3 + DB 19.3) 本文应用补丁信息, 19.16 RU: p34130714_190000_Linux-x86-64.zip 本文主要演示使用opatc ...