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. How to Die ( Since 10.10 )

    以后再也不要相信 sqrt 的精度!对 long long 级别的数取 sqrt 会炸精度! 对于区间差分 \([l,r]\) 的问题,一定要注意是否会出现 \(l>r\) 的情况!(\(|A| ...

  2. vue2-vue3监听子组件的生命周期的两种方式

    1.生命周期 生命周期是指:vue实例从创建到销毁这一系列过程.vue官网生命周期如下图所示: vue的生命周期有多少个 beforeCreate, created, beforeMount, mou ...

  3. vue中v-show你不知道的用法 created computed mounted的执行顺序

    我们都知道,v-show的值是一个布尔类型的. 我通过这个值进行显示或者隐藏. 但是有些时候,这个值是true还是false,我们需要去进行计算 此时我们就可以使用v-show="XXX() ...

  4. 玩一玩 VictoriaLogs

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 下载 see: https://github.com/Vi ...

  5. 【JS 逆向百例】猿人学系列 web 比赛第二题:js 混淆 - 动态 cookie,详细剖析

    逆向目标 猿人学 - 反混淆刷题平台 Web 第二题:js 混淆,动态 cookie 目标:提取全部 5 页发布日热度的值,计算所有值的加和 主页:https://match.yuanrenxue.c ...

  6. easyui 使用不同的url以获取不同数据源信息

    转载 https://www.bbsmax.com/A/kjdw1x06JN/ https://blog.csdn.net/lixinhui199/article/details/50724081 参 ...

  7. 使用rider调试lua

    emmylua1.3.5及以上版本支持rider调试,但emmylua的新版本只支持rider2020及以上版本,所以如果想用rider来调试lua,就要升级rider为2020,emmylua插件从 ...

  8. C/C++ 命名空间引用知识

    标准命名空间 命名空间的使用 #define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; // 命名空 ...

  9. Spring一套全通6—注解编程

    百知教育 --- Spring系列课程 --- 注解编程 第一章.注解基础概念 1. 什么是注解编程 指的是在类或者方法上加入特定的注解(@XXX),完成特定功能的开发. @Component pub ...

  10. 每日一道Java面试题:说一说Java中的异常

    写在开头 任何一个程序都无法保证100%的正常运行,程序发生故障的场景,我们称之为:异常,在Java中对于异常的处理有一套完善的体系,今天我们就来一起学习一下. 老样子,用一段简单的代码开始今天的学习 ...