Android屏幕绘制刷新之Choreograhper与VSync分析

在android中,Choreographer 和 VSYNC 是两个重要概念,它们在Android的渲染机制中扮演着重要的角色。

Choreographer 是一个Java类,用于协调输入、动画和绘制等任务的执行时机。 Choreographer 从显示子系统接收定时脉冲(如VSYNC信号),然后安排渲染下一个显示帧的部分工作。

Choreographer 接收和处理App的各种更新消息和回调,等到VSYNC到来的时候统一处理。另外,Choreographer 也负责请求和接收VSYNC信号,通过申请和接收VSYNC来驱动App刷新。 VSYNC(Vertical Synchronization)即垂直同步,是一个垂直同步信号,用于协调显示刷新和绘图操作。VSYNC信号的主要作用是控制屏幕刷新频率与图形渲染的同步,以确保画面显示平滑且没有撕裂现象。

在Android的渲染机制中,Choreographer和VSYNC紧密配合,共同确保画面的流畅显示。概括起来说是VSYNC信号触发绘制,Choreographer协调绘制,它们共同维护帧率的稳定。 VSYNC信号是触发Android系统进行下一帧绘制的关键。当VSYNC信号到来时,Choreographer会接收到这个信号,并安排相应的绘制任务。

Choreographer负责协调各种绘制任务的执行时机,确保它们在VSYNC信号到来时能够顺利进行。通过Choreographer的协调,Android系统能够充分利用VSYNC信号提供的同步机制,减少画面撕裂和卡顿现象。

VSYNC信号和Choreographer共同维护着Android系统的帧率稳定。VSYNC信号确保了屏幕刷新的同步性,而Choreographer则通过协调绘制任务来确保每一帧的绘制都能够在合适的时间点进行。这样,Android系统就能够以稳定的帧率输出画面,提升用户体验。

本文以android 13代码为例进行分析。

1,在frameworks/base/core/java/android/view/ViewRootImpl.java中

less 复制代码
final Choreographer mChoreographer;

public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session, boolean useSfChoreographer) {
		...
		mChoreographer = useSfChoreographer ? Choreographer.getSfInstance() : Choreographer.getInstance();
		...
}

从上面的代码中可以看到,在 ViewRootImpl 构造函数中创建了 Choreographer 实例。 而 ViewRootImpl 实例是在 addView 时创建的,即在 WindowManagerGlobal::addView 函数中。

arduino 复制代码
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {
		 ViewRootImpl root; //创建ViewRootImpl对象
		 IWindowSession windowlessSession = null;
		 if (windowlessSession == null) { // 对于应用来说 windowlessSession 是为null的,在ViewRootImpl的构造函数中通过WindowManagerGlobal.getWindowSession()作为windowSession
             root = new ViewRootImpl(view.getContext(), display); //创建ViewRootImpl类对象
         } else {
             root = new ViewRootImpl(view.getContext(), display,windowlessSession);
         }
		 root.setView(view, wparams, panelParentView, userId);
}

上面的代码中 WindowManagerGlobal::addView 是被 WindowManagerImpl::addView 调用的,即

less 复制代码
public final class WindowManagerImpl implements WindowManager {
	private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
	private final Window mParentWindow;
	
	public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyTokens(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
}

上面的代码中 WindowManagerImpl::addView 是被 ActivityThread::handleResumeActivity 调用的,即

java 复制代码
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,boolean isForward, String reason) {
	
		if (!performResumeActivity(r, finalStateRequest, reason)) { //调用 performResumeActivity,触发onResume
	        return;
	    }
		final Activity a = r.activity;
		View decor = r.window.getDecorView(); //获取DecorView
		ViewManager wm = a.getWindowManager(); //实际上是WindowManagerImpl
		WindowManager.LayoutParams l = r.window.getAttributes();
		wm.addView(decor, l); //调用ViewManager的addView函数,会进一步地走到创建ViewRootImpl,走到WindowManagerGlobal类的addView函数。
}

从上面的代码可以知道,在 handleResumeActivity 中执行了resume,并调用 addView 将 DecorView和WindowMangerImpl进行联系。

经过上面的分析,可以看到 Choreographer 的创建时机是在 Activity 的 onResume 执行之后,可见Android系统这么设计是出于Activity是Android应用中承载UI的容器,只有容器创建之后,才需要创建Choreographer来调度VSYNC信号,最终开启一帧帧的界面渲染和刷新。

经过上面的分析,知道了 Choreographer mChoreographer是在 ViewRootImpl 构造函数中创建的,下面分析下 Choreographer。

2,在frameworks/base/core/java/android/view/Choreographer.java中

java 复制代码
public final class Choreographer {
	
	// sThreadInstance用于存储每个线程的Choreographer实例。ThreadLocal是Java中的一个类,用于为每个使用该变量的线程提供一个独立的变量副本,这样每个线程都可以独立地改变自己的副本,而不会影响其他线程。
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {// 通过匿名内部类的方式重写了ThreadLocal的initialValue()方法。这个方法会在每个线程首次访问sThreadInstance.get()时被调用,以返回该线程的Choreographer实例。
            Looper looper = Looper.myLooper(); // Looper.myLooper()用于获取当前线程的Looper实例。Looper是一个类,用于管理线程的消息队列和消息循环。
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
    		 // 使用当前线程的Looper和一个特定的垂直同步源(VSYNC_SOURCE_APP,通常代表应用层)来创建Choreographer实例。
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
    		 // 返回当前线程对应的Choreographer实例。
            return choreographer;
        }
    };
    
    private static volatile Choreographer mMainInstance;
    
    // Thread local storage for the SF choreographer.
    private static final ThreadLocal<Choreographer> sSfThreadInstance =
            new ThreadLocal<Choreographer>() {
                @Override
                protected Choreographer initialValue() {
                    Looper looper = Looper.myLooper();
                    if (looper == null) {
                        throw new IllegalStateException("The current thread must have a looper!");
                    }
                    return new Choreographer(looper, VSYNC_SOURCE_SURFACE_FLINGER);
                }
            };
			
	public static Choreographer getInstance() {
	    return sThreadInstance.get();		
	}		
			
	/**		
	 * @hide		
	 */		
	@UnsupportedAppUsage		
	public static Choreographer getSfInstance() {		
	    return sSfThreadInstance.get();		
	}
	
}

从上面的代码可以知道,每个线程都可以获得一个与其关联的Choreographer实例,从而独立地处理动画、输入事件分发等任务。对于主线程来说,它的Choreographer实例还会被特别存储,以便在需要时能够快速访问。

从上面的代码也可以看出,并不是每启动一个activity都创建一个 choreographer 实例,choreographer 的创建是通过 ThreadLocal 实现的,所以 Choreographer 是线程单例的,主线程只会创建一个 Choreographer 实例。

同样,也不是任何一个线程都可以创建Choreographer实例,只有创建了Looper的线程才能创建Choreographer实例,原因是Choreographer会通过Looper进行线程切换。

上面的代码调用了 Choreographer 的构造函数,即

ini 复制代码
private static final boolean USE_VSYNC = SystemProperties.getBoolean("debug.choreographer.vsync", true);
public static final int CALLBACK_COMMIT = 4;
private static final int CALLBACK_LAST = CALLBACK_COMMIT;
private final Looper mLooper;
private final FrameHandler mHandler;
private final FrameDisplayEventReceiver mDisplayEventReceiver;

private Choreographer(Looper looper, int vsyncSource) {
    mLooper = looper;
    mHandler = new FrameHandler(looper);
    mDisplayEventReceiver = USE_VSYNC
            ? new FrameDisplayEventReceiver(looper, vsyncSource)
            : null;
    mLastFrameTimeNanos = Long.MIN_VALUE;

    mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); // 计算一帧的时间,android手机屏幕是60HZ的刷新频率,即16ms.

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

上面的代码中分别创建了 FrameHandler 对象和 FrameDisplayEventReceiver 对象,以及 CallbackQueue 对象。

首先看下 FrameHandler 类。

java 复制代码
private static final int MSG_DO_FRAME = 0;
private static final int MSG_DO_SCHEDULE_VSYNC = 1;
private static final int MSG_DO_SCHEDULE_CALLBACK = 2;

private final class FrameHandler extends Handler {
    public FrameHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_DO_FRAME:
                doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());
                break;
            case MSG_DO_SCHEDULE_VSYNC:
                doScheduleVsync();
                break;
            case MSG_DO_SCHEDULE_CALLBACK:
                doScheduleCallback(msg.arg1);
                break;
        }
    }
}

接着看下 FrameDisplayEventReceiver 类。

java 复制代码
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
        implements Runnable {
    private boolean mHavePendingVsync; // 用于标记是否有一个待处理的VSYNC事件
    private long mTimestampNanos; // VSYNC事件的时间戳,以纳秒为单位
    private int mFrame; // VSYNC事件对应的帧号
    private VsyncEventData mLastVsyncEventData = new VsyncEventData(); // VSYNC事件的详细数据,包括帧时间线等信息

    public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
        super(looper, vsyncSource, 0);
    }

    @Override
    public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
            VsyncEventData vsyncEventData) { // onVsync 是 DisplayEventReceiver 的回调方法,当接收到VSYNC事件时被调用。
        try {
            if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                        "Choreographer#onVsync "
                                + vsyncEventData.preferredFrameTimeline().vsyncId);
            }
            
            long now = System.nanoTime();
            if (timestampNanos > now) { // 检查当前时间是否早于VSYNC事件的时间戳,如果是,则记录一条警告日志,并将时间戳设置为当前时间.
                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、mFrame和mLastVsyncEventData为最新的VSYNC事件数据。
            mTimestampNanos = timestampNanos;
            mFrame = frame;
            mLastVsyncEventData = vsyncEventData;
			  // 创建一个消息(Message),将其设置为异步,并安排在VSYNC事件的时间戳(转换为毫秒)时发送到消息队列。
            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() { // 实现自Runnable接口,当消息队列处理到这个VSYNC事件的消息时被调用。
        mHavePendingVsync = false; // 将mHavePendingVsync设置为false,表示当前没有待处理的VSYNC事件。
        doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
    }
}

上面的代码是Android系统中的一个内部类 FrameDisplayEventReceiver 的实现,它继承自 DisplayEventReceiver 并实现了Runnable接口。这个类的主要作用是接收来自系统显示硬件的垂直同步(VSYNC)事件,并将这些事件传递给系统的消息队列(Message Queue)以进行进一步的处理。

上面分析了 Choreographer 的构造函数,下面分析下在业务逻辑中怎样与 Choreographer 建立联系的,也就是 Choreographer 是怎么跑起来的。

在ViewRootImpl 类的 setView 函数中,执行了 requestLayout 方法,并进一步执行 scheduleTraversals 函数、 performTraversals 函数,接着是measure,layout,draw等三大流程。

通过 Choreographer 来协调这个过程,并利用同步屏障来确保在遍历执行期间,消息队列中的其他同步消息不会干扰到遍历过程。

3,在frameworks/base/core/java/android/view/ViewRootImpl.java中

scss 复制代码
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
		  // 通过消息循环(Looper)的队列(Queue)发布一个同步屏障(Sync Barrier)。这个屏障用于阻塞消息队列中同步消息的处理,直到屏障被移除。mTraversalBarrier变量保存了这个屏障的引用。
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
		  // 调用Choreographer对象的postCallback方法。Choreographer是Android系统中用于协调动画、输入和绘制的一个类。
		  // 向Choreographer注册一个回调。CALLBACK_TRAVERSAL是回调的类型,表示这是一个遍历回调。
		  // mTraversalRunnable是一个实现了遍历逻辑的可运行对象(Runnable),当回调被执行时,它会运行。
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending(); // 通知渲染器有一帧即将被绘制。这通常是为了确保渲染器准备好处理新的帧。
        pokeDrawLockIfNeeded();
    }
}

在上面的代码中,通过 mTraversalScheduled 保证同时间多次修改只会刷新一次,添加同步屏障,屏蔽同步消息,保证vsync消息到时能立即执行绘制。

通过调用 mChoreographer.postCallback 方法发送一个会在下一帧执行的回调,即在下一个vsync到时会执行TraversalRunnable->doTraversal->performTraversals->绘制流程。

4,在frameworks/base/core/java/android/view/Choreographer.java中

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) {
    if (action == null) {
        throw new IllegalArgumentException("action must not be null");
    }
    if (callbackType < 0 || callbackType > CALLBACK_LAST) {
        throw new IllegalArgumentException("callbackType is invalid");
    }

    postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {

    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
		 // 将回调添加到对应类型的回调队列中。这里的mCallbackQueues是一个数组,每个元素对应一种回调类型,每个元素都是一个可以存储回调的队列。
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); // addCallbackLocked方法负责将回调按时间顺序添加到队列中。

		 // 判断回调的执行时间是否已经到了或过了。
        if (dueTime <= now) { // 立即通过scheduleFrameLocked方法安排一个帧的更新。
            scheduleFrameLocked(now);
        } else { // 如果还没到,就通过Handler发送一个消息(MSG_DO_SCHEDULE_CALLBACK)
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true); // setAsynchronous(true)表示这个消息是异步的,允许它跳过消息队列中的一些同步消息,更快地得到处理。
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }

}

上面代码中的回调类型在 Choreographer 类中定义有5个类型,即

arduino 复制代码
public static final int CALLBACK_INPUT = 0;
public static final int CALLBACK_ANIMATION = 1;
public static final int CALLBACK_INSETS_ANIMATION = 2;
public static final int CALLBACK_TRAVERSAL = 3;
public static final int CALLBACK_COMMIT = 4;

上面五种类型的任务会存在对应的CallbackQueue中,每当收到vsync信号时,Choreographer会首先处理input类型的任务,然后是animation类型,之后是traversal类型。

上面的 postCallbackDelayedInternal 函数代码中是走if分支或else分支,都会走到 scheduleFrameLocked 函数。

scss 复制代码
private void scheduleFrameLocked(long now) {
    if (!mFrameScheduled) { // 通过if(!mFrameScheduled)检查是否已经调度了一帧。
        mFrameScheduled = true;
        if (USE_VSYNC) {
            if (DEBUG_FRAMES) {
                Log.d(TAG, "Scheduling next frame on vsync.");
            }

            // If running on the Looper thread, then schedule the vsync immediately,
            // otherwise post a message to schedule the vsync from the UI thread
            // as soon as possible.
            if (isRunningOnLooperThreadLocked()) {// 判断当前线程是否是与创建 Choreographer对象时相关联的Looper.
                scheduleVsyncLocked();
            } else {// 如果不在Looper线程,则通过mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC)创建一个消息,并设置其为异步消息,然后将其发送到消息队列的前端。
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtFrontOfQueue(msg);
            }
        } else {
            final long nextFrameTime = Math.max(
                    mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
            if (DEBUG_FRAMES) {
                Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
            }
            Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, nextFrameTime);
        }
    }
}

上面的代码中调用 isRunningOnLooperThreadLocked 函数。

typescript 复制代码
// 检查当前线程是否是与 mLooper 关联的Looper线程。
// Looper.myLooper() 是一个静态方法,它返回当前线程所关联的Looper对象(如果当前线程有一个Looper在运行的话)。如果当前线程没有Looper,Looper.myLooper() 将返回 null。
private boolean isRunningOnLooperThreadLocked() {
    return Looper.myLooper() == mLooper;
}

上面代码中 mLooper 在构造函数中赋值。

arduino 复制代码
private final Looper mLooper;
private Choreographer(Looper looper, int vsyncSource) {
    mLooper = looper;
	...
}

在上面的 scheduleFrameLocked 函数中调用了 scheduleVsyncLocked 函数。

csharp 复制代码
private final FrameDisplayEventReceiver mDisplayEventReceiver; //mDisplayEventReceiver 在Choreographer的构造函数中进行赋值。

private void scheduleVsyncLocked() {
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
        mDisplayEventReceiver.scheduleVsync();
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

上面的代码调用 FrameDisplayEventReceiver 类的 scheduleVsync 函数,而 FrameDisplayEventReceiver 继承自 DisplayEventReceiver,所以代码会走到 DisplayEventReceiver 中的 scheduleVsync 函数。

5,在frameworks/base/core/java/android/view/DisplayEventReceiver.java中

csharp 复制代码
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脉冲的发送,并将 mReceiverPtr 作为参数传递,以便本地代码知道应该将vsync事件发送到哪里。
        nativeScheduleVsync(mReceiverPtr);
    }
}

在上面代码中判断了 mReceiverPtr 是否为0,它是在 DisplayEventReceiver 构造函数中赋值的。

csharp 复制代码
private long mReceiverPtr;
public DisplayEventReceiver(Looper looper, int vsyncSource, int eventRegistration) {
    if (looper == null) {
        throw new IllegalArgumentException("looper must not be null");
    }

	 // 通过调用 looper.getQueue() 获取与该 Looper 关联的消息队列,并将其存储在 mMessageQueue 成员变量中。这个消息队列将用于接收和分发vsync事件。
    mMessageQueue = looper.getQueue();
	 // 注册vsync信号监听者。nativeInit 方法的作用是初始化与vsync事件接收相关的本地(native)资源和数据结构,并返回一个指向这些资源的指针,该指针存储在 mReceiverPtr 成员变量中。
    mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
            vsyncSource, eventRegistration);
}

上面的代码调用了 nativeScheduleVsync 函数和 nativeInit 函数。

java 复制代码
private static native void nativeScheduleVsync(long receiverPtr);
private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
        MessageQueue messageQueue, int vsyncSource, int eventRegistration);

6,在frameworks/base/core/jni/android_view_DisplayEventReceiver.cpp中

scss 复制代码
static void nativeScheduleVsync(JNIEnv* env, jclass clazz, jlong receiverPtr) {
	 // 使用 reinterpret_cast 将 jlong 类型的 receiverPtr 转换为 NativeDisplayEventReceiver* 类型的指针。这是必要的,因为 jlong 只是一个通用的长整型值,而我们需要将其视为一个指向特定类型对象的指针。
    sp<NativeDisplayEventReceiver> receiver =
            reinterpret_cast<NativeDisplayEventReceiver*>(receiverPtr);
	 // 调用 NativeDisplayEventReceiver 对象的 scheduleVsync 方法。
    status_t status = receiver->scheduleVsync();
	 // 如果 scheduleVsync 方法返回的状态值不为0(表示失败),则构造一个错误消息,并使用 jniThrowRuntimeException 函数抛出一个Java层的 RuntimeException。这个异常将包含有关失败原因的详细信息,并传递给Java层以进行错误处理。
    if (status) {
        String8 message;
        message.appendFormat("Failed to schedule next vertical sync pulse.  status=%d", status);
        jniThrowRuntimeException(env, message.string());
    }
}

上面的代码中 nativeScheduleVsync 方法是一个JNI本地方法,它用于在本地层安排一个垂直同步脉冲,并在操作失败时向Java层报告错误。 jniThrowRuntimeException 是一个本地函数,用于在JNI环境中抛出一个Java层的异常。这个函数通常接受一个 JNIEnv* 指针和一个错误消息字符串作为参数。

对应上面的 NativeDisplayEventReceiver 类,它继承自 DisplayEventReceiver。

arduino 复制代码
class NativeDisplayEventReceiver : public DisplayEventDispatcher {
public:
    NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
                               const sp<MessageQueue>& messageQueue, jint vsyncSource,
                               jint eventRegistration);

    void dispose();

protected:
    virtual ~NativeDisplayEventReceiver();

private:
    jobject mReceiverWeakGlobal;
    sp<MessageQueue> mMessageQueue;

    void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
                       VsyncEventData vsyncEventData) override;
    void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
    void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
                             nsecs_t vsyncPeriod) override;
    void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                    std::vector<FrameRateOverride> overrides) override;
    void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
};

再看下 nativeInit 函数。

scss 复制代码
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject messageQueueObj,
                        jint vsyncSource, jint eventRegistration) {
	 // 使用 android_os_MessageQueue_getMessageQueue 函数从 messageQueueObj 中获取 MessageQueue 的本地对象指针,并将其存储在 messageQueue 变量中。
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    if (messageQueue == NULL) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
    }

	 // 使用 new NativeDisplayEventReceiver 创建一个新的 NativeDisplayEventReceiver 对象,并传入环境指针、弱引用对象、消息队列、vsync来源和事件注册信息。
    sp<NativeDisplayEventReceiver> receiver =
            new NativeDisplayEventReceiver(env, receiverWeak, messageQueue, vsyncSource,
                                           eventRegistration);
    status_t status = receiver->initialize(); // 调用 receiver 对象的 initialize 方法进行初始化。
	 // 如果 initialize 方法返回的状态值不为0(表示失败),则构造一个错误消息,并使用 jniThrowRuntimeException 函数抛出一个Java层的 RuntimeException 异常。
    if (status) {
        String8 message;
        message.appendFormat("Failed to initialize display event receiver.  status=%d", status);
        jniThrowRuntimeException(env, message.string());
        return 0;
    }

	 // 调用 receiver->incStrong 方法,并传入一个类信息对象(gDisplayEventReceiverClassInfo.clazz),这通常是为了在本地层保留一个对 NativeDisplayEventReceiver 对象的强引用,以防止它被意外销毁。
	 // 这个步骤是必要的,因为 NativeDisplayEventReceiver 对象需要在本地层被长时间使用,而Java层的弱引用可能不足以防止它被垃圾回收。
    receiver->incStrong(gDisplayEventReceiverClassInfo.clazz); // retain a reference for the object
	 // 使用 reinterpret_cast<jlong> 将 NativeDisplayEventReceiver 对象的指针转换为 jlong 类型,并返回。
	 // 这个 jlong 值随后可以在Java层作为句柄使用,以引用本地层的 NativeDisplayEventReceiver 对象。
    return reinterpret_cast<jlong>(receiver.get());
}

上面的 nativeInit 和 nativeScheduleVsync 都需要进行注册。

arduino 复制代码
static const JNINativeMethod gMethods[] = {
        /* name, signature, funcPtr */
        {"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;II)J",
         (void*)nativeInit},
        {"nativeDispose", "(J)V", (void*)nativeDispose},
        // @FastNative
        {"nativeScheduleVsync", "(J)V", (void*)nativeScheduleVsync},
        {"nativeGetLatestVsyncEventData", "(J)Landroid/view/DisplayEventReceiver$VsyncEventData;",
         (void*)nativeGetLatestVsyncEventData}};
		 
int register_android_view_DisplayEventReceiver(JNIEnv* env) {
    int res = RegisterMethodsOrDie(env, "android/view/DisplayEventReceiver", gMethods,
                                   NELEM(gMethods));
	...
}

综合上面的分析可以看出,scheduleVsyncLocked 方法调用了 FrameDisplayEventReceiver 的 scheduleVsync 方法。 而 FrameDisplayEventReceiver 继承至 DisplayEventReceiver,它会调用 DisplayEventReceiver 的 scheduleVsync()方法,最终将调用 nativeScheduleVsync 的JNI方法。去底层获取申请获取vsync垂直同步信号。

下面继续分析在获取到Vsync信号后的执行流程。

SurfaceFlinger 进程中请求到 VSync 信号,并且封装成 Event 事件后回调给 EventThread 进行分发,EventThread 将数据通过文件描述符 mSendFd 关联的 socket 管道发送到一段指定大小的缓冲区中。

现在应用进程端需要通过文件描述符 mReceiveFd 关联的 socket 管道从指定的缓冲区中读取出数据。

当 socket 管道的 mSendFd 端有数据写入时,就会把与之对应的 mReceiveFd 有关的 Request 取出并收集起来,待 Native 层的 Message 处理完后,循环遍历收集起来的 Request,取出每个 Request 并调用其 callback 回调的 handleEvent() 方法。 该回调就是向 Looper 中添加 fd 时作为 LooperCallback 回调一并传入的 DisplayEventDispatcher 实现类,查看实现类的 handleEvent() 方法。

也就是当SurfaceFlinger进程通过Socket通知App进程VSYNC信号到达之后,App进程的 handleEvent 方法将会被调用,最终通过JNI调用到Java层的 FrameDisplayEventReceiver#dispatchVsync方法。

7,在frameworks/native/libs/gui/DisplayEventDispatcher.cpp中

arduino 复制代码
int DisplayEventDispatcher::handleEvent(int, int events, void*) {
	nsecs_t vsyncTimestamp;
    PhysicalDisplayId vsyncDisplayId;
    uint32_t vsyncCount;
    VsyncEventData vsyncEventData;
    if (processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, &vsyncCount, &vsyncEventData)) {
        ALOGV("dispatcher %p ~ Vsync pulse: timestamp=%" PRId64
              ", displayId=%s, count=%d, vsyncId=%" PRId64,
              this, ns2ms(vsyncTimestamp), to_string(vsyncDisplayId).c_str(), vsyncCount,
              vsyncEventData.preferredVsyncId());
        mWaitingForVsync = false;
        mLastVsyncCount = vsyncCount;
        dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount, vsyncEventData);
    }
    
    if (mWaitingForVsync) {
        const nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
        const nsecs_t vsyncScheduleDelay = currentTime - mLastScheduleVsyncTime;
        if (vsyncScheduleDelay > WAITING_FOR_VSYNC_TIMEOUT) {
            ALOGW("Vsync time out! vsyncScheduleDelay=%" PRId64 "ms", ns2ms(vsyncScheduleDelay));
            mWaitingForVsync = false;
            dispatchVsync(currentTime, vsyncDisplayId /* displayId is not used */,
                          ++mLastVsyncCount, vsyncEventData /* empty data */);
        }
    }
    
    return 1; // keep the callback
}

上面的代码中调用 dispatchVsync 函数,在 DisplayEventDispatcher 类没有重写 dispatchVsync 方法,但其子类 NativeDisplayEventReceiver 重写了这个函数。

8,在frameworks/base/core/java/android/view/DisplayEventReceiver.java中

arduino 复制代码
class NativeDisplayEventReceiver : public DisplayEventDispatcher {
public:
    NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
                               const sp<MessageQueue>& messageQueue, jint vsyncSource,
                               jint eventRegistration);

    void dispose();

protected:
    virtual ~NativeDisplayEventReceiver();

private:
    jobject mReceiverWeakGlobal;
    sp<MessageQueue> mMessageQueue;

    void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
                       VsyncEventData vsyncEventData) override;
    void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
    void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
                             nsecs_t vsyncPeriod) override;
    void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                    std::vector<FrameRateOverride> overrides) override;
    void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
};

从上面代码可以看出,NativeDisplayEventReceiver 继承自 DisplayEventDispatcher 类。

下面看下 dispatchVsync 函数。

scss 复制代码
void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId,
                                               uint32_t count, VsyncEventData vsyncEventData) {
    JNIEnv* env = AndroidRuntime::getJNIEnv();

	 // 通过jniGetReferent函数和mReceiverWeakGlobal(一个弱引用到Java层对象的全局引用)获取Java层接收器的本地引用,并将其封装在ScopedLocalRef智能指针中。
    ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
    if (receiverObj.get()) { // 检查是否成功获取到Java层的接收器对象。如果没有(即receiverObj.get()返回nullptr),则不进行后续操作。
        ALOGV("receiver %p ~ Invoking vsync handler.", this);
		 // 调用 createJavaVsyncEventData 函数,将C++层的vsyncEventData转换为Java层的对象javaVsyncEventData。这是为了能在Java层以适当的方式处理VSync事件数据。
        jobject javaVsyncEventData = createJavaVsyncEventData(env, vsyncEventData);
		 // 通过JNI调用Java层接收器对象的 dispatchVsync 方法,传递时间戳、显示ID、计数器和转换后的VSync事件数据作为参数。
		 // 这里的gDisplayEventReceiverClassInfo.dispatchVsync是Java层DisplayEventReceiver类中dispatchVsync方法的JNI函数ID,它事先通过JNI的注册机制与Java方法关联。
        env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync,
                            timestamp, displayId.value, count, javaVsyncEventData);
        ALOGV("receiver %p ~ Returned from vsync handler.", this);
    }
	 // 检查并处理在调用Java方法时可能抛出的异常。这是为了确保任何在JNI调用中发生的异常都能被捕获并适当处理,避免程序崩溃。
    mMessageQueue->raiseAndClearException(env, "dispatchVsync");
}

上面的代码调用了 DisplayEventReceiver 类中 dispatchVsync 方法。

arduino 复制代码
private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
        VsyncEventData vsyncEventData) {
    onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData);
}

在取到底层垂直同步信号之后,会回调 DisplayEventReceiver 的 dispatchVsync 方法并把时间传回,dispatchVsync 接着调用 onVsync 方法。

arduino 复制代码
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
        VsyncEventData vsyncEventData) {
}

由于 Choreographer 类的内部类 FrameDisplayEventReceiver 继承自 DisplayEventReceiver,即 private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {}, 所以代码会走到 FrameDisplayEventReceiver 的函数 onVsync。

9,在frameworks/base/core/java/android/view/Choreographer.java中

java 复制代码
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
	@Override
    public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
            VsyncEventData vsyncEventData) { // onVsync 是 DisplayEventReceiver 的回调方法,当接收到VSYNC事件时被调用。
		
			mTimestampNanos = timestampNanos;
            mFrame = frame;
            mLastVsyncEventData = vsyncEventData;
			  // 创建一个消息(Message),将其设置为异步,并安排在VSYNC事件的时间戳(转换为毫秒)时发送到消息队列。
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
			
	}
	
	@Override
	public void run() { // 实现自Runnable接口,当消息队列处理到这个VSYNC事件的消息时被调用。
	    mHavePendingVsync = false; // 将mHavePendingVsync设置为false,表示当前没有待处理的VSYNC事件。
	    doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
	}
	
}

在上面的代码中,通过 Message 的 obtain() 方法获取到 Message,是由 FrameHandler 发送消息并执行获取消息时传入的回调。

由于获取消息时传入的回调是 FrameDisplayEventReceiver 本身,并且实现了 Runnable 接口,因此会回调 FrameDisplayEventReceiver的run() 方法。 在 run 方法中,调用 doFrame 函数。

ini 复制代码
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.preferredFrameTimeline().vsyncId);
        }
        FrameData frameData = new FrameData(frameTimeNanos, vsyncEventData);
        synchronized (mLock) {
            if (!mFrameScheduled) {
                traceMessage("Frame not scheduled");
                return; // no work to do
            }

            if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
                mDebugPrintNextFrameTimeDelta = false;至此,从ActivityThread.handleResumeActivity(mWM.addView)到Choreographer去底层驱动请求获取上一帧的绘制时间就跟踪结束了。获取Vsync垂直同步信号后,后面就是回调到Choreographer来了。
                Log.d(TAG, "Frame time delta: "
                        + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
            }

            long intendedFrameTimeNanos = frameTimeNanos; // 设置将要处理的当前帧的时间戳
            startNanos = System.nanoTime(); // 记录实际开始执行当前 frame 的时间
            final long jitterNanos = startNanos - frameTimeNanos; // 计算时间差值
            if (jitterNanos >= frameIntervalNanos) { // 时间差值大于等于帧间隔,即发生了跳帧
                long lastFrameOffset = 0;
                if (frameIntervalNanos == 0) {
                    Log.i(TAG, "Vsync data empty due to timeout");
                } else {
                    lastFrameOffset = jitterNanos % frameIntervalNanos; // 计算实际开始当前 frame 的时间戳与帧间隔的偏移值
                    final long skippedFrames = jitterNanos / frameIntervalNanos;
                    if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                        Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                                + "The application may be doing too much work on its main "
                                + "thread.");
                    }
					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; // 修正偏移值,下一帧的时间戳开始值要减去计算得出的偏移值
                frameData.updateFrameData(frameTimeNanos);
			}
			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");
            	 // 当前帧的时间小于上一个帧的时间,可能是由于先前跳帧的缘故,申请并等待下一次 VSync 信号到来
                scheduleVsyncLocked();
                return;
            }
            
            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    traceMessage("Frame skipped due to FPSDivisor");
                    scheduleVsyncLocked();// 由于 FPSDivisor 导致跳帧,继续申请并等待下一次 VSync 信号到来
                    return;
                }
            }
			// 记录当前 Frame 信息
            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos,
                    vsyncEventData.preferredFrameTimeline().vsyncId,
                    vsyncEventData.preferredFrameTimeline().deadline, startNanos,
                    vsyncEventData.frameInterval);
            // 收到 VSync 信号当前帧调度完,mFrameScheduled 标志位重置为 false,以便下一轮请求使用
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos; // 记录上一次 Frame 渲染的时间
            mLastFrameIntervalNanos = frameIntervalNanos; // 记录上一次 Frame 渲染的帧间隔
            mLastVsyncEventData = vsyncEventData; // 记录上一次 VSync 事件信息
		}
		// 动画锁定为当前线程的固定值
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
        // 按照事件类型的优先级执行 CallBack
        mFrameInfo.markInputHandlingStart();
        // 输入事件,首先执行
        doCallbacks(Choreographer.CALLBACK_INPUT, frameData, frameIntervalNanos);
        
        mFrameInfo.markAnimationsStart();
        // 动画事件,在 CALLBACK_INSETS_ANIMATION 之前执行
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameData, frameIntervalNanos);
        // 插入更新动画事件,INPUT 和 ANIMATION 后面执行,TRAVERSAL 之前执行
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameData,
                frameIntervalNanos);
        
        mFrameInfo.markPerformTraversalsStart();// 处理布局和绘制事件,在处理完上述异步消息之后运行
        
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameData, frameIntervalNanos);
        
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameData, 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 函数。

java 复制代码
void doCallbacks(int callbackType, FrameData frameData, long frameIntervalNanos) {
    CallbackRecord callbacks;
    long frameTimeNanos = frameData.mFrameTimeNanos;
    synchronized (mLock) {
		 // 获取正在运行的 Java 虚拟机的当前时间,单位为纳秒,以此来确定回调何时执行,因为帧中的前期处理阶段可能会发布应在下一阶段运行的回调,例如导致动画启动的输入事件
        final long now = System.nanoTime();
		 // 从数组链表 mCallbackQueues 中获取指定类型的链表
        callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                now / TimeUtils.NANOS_PER_MS);
        if (callbacks == null) {
            return;
        }
        mCallbacksRunning = true;
	}
	try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
    	 // 遍历 callbacks 链表获取 CallbackRecord 对象
        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));
            }
    		 // 执行 CallbackRecord 对象的 run() 方法
            c.run(frameData);
        }
    } finally {
        synchronized (mLock) {
            mCallbacksRunning = false;
    		 // 回收 callbacks,置空其内部 CallbackRecord 保存的信息,重组并赋值给 mCallbackPool 链表
            do {
                final CallbackRecord next = callbacks.next;
                recycleCallbackLocked(callbacks);
                callbacks = next;
            } while (callbacks != null);
        }
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

上面的代码调用 CallbackRecord 的run方法。

arduino 复制代码
private static final class CallbackRecord {
    public CallbackRecord next;
    public long dueTime;
    /** Runnable or FrameCallback or VsyncCallback object. */
    public Object action;
    /** Denotes the action type. */
    public Object token;

    void run(FrameData frameData) {
        if (token == VSYNC_CALLBACK_TOKEN) { // 根据 token 判断当前回调类型
            ((VsyncCallback) action).onVsync(frameData);
        } else {
            run(frameData.getFrameTimeNanos());
        }
    }
}

上面的run方法调用重载函数run方法。

java 复制代码
private static final class CallbackRecord {
	@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 并执行其 run() 方法
            ((Runnable)action).run();
        }
    }
}

在Choreographer的 postCallback 方法中,提交一个任务 TraversalRunnable,将任务 TraversalRunnable 和执行时间一起被封装成 CallbackRecord,因此这里执行的就是 TraversalRunnable 的 run() 方法。

10,在frameworks/base/core/java/android/view/ViewRootImpl.java中

scss 复制代码
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
		  // 通过消息循环(Looper)的队列(Queue)发布一个同步屏障(Sync Barrier)。这个屏障用于阻塞消息队列中同步消息的处理,直到屏障被移除。mTraversalBarrier变量保存了这个屏障的引用。
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
		  // 调用Choreographer对象的postCallback方法。Choreographer是Android系统中用于协调动画、输入和绘制的一个类。
		  // 向Choreographer注册一个回调。CALLBACK_TRAVERSAL是回调的类型,表示这是一个遍历回调。
		  // mTraversalRunnable是一个实现了遍历逻辑的可运行对象(Runnable),当回调被执行时,它会运行。
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending(); // 通知渲染器有一帧即将被绘制。这通常是为了确保渲染器准备好处理新的帧。
        pokeDrawLockIfNeeded();
    }
}

上面的代码在postCallback时第二个参数传递 mTraversalRunnable。

java 复制代码
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

上面的代码调用 doTraversal 函数。

scss 复制代码
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
		  // 通过消息循环(Looper)的队列(Queue)移除之前发布的同步屏障(Sync Barrier)。
		  // 这个屏障是在scheduleTraversals方法中发布的,用于阻塞消息队列中同步消息的处理,直到遍历任务开始执行。现在遍历任务即将开始,所以移除这个屏障以允许其他同步消息继续被处理。
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals(); // 执行遍历任务。这个方法包含了测量、布局和绘制的实际逻辑

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

调用 ViewRootImpl 的 performTraversals 方法,开始View的三大流程(measure、layout、draw)。 至此,通过 Choreographer 协调 vsync 垂直同步信号在界面绘制刷新中执行的流程分析完毕。

相关推荐
Eastsea.Chen2 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年9 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿12 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神13 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛13 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法13 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter15 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快16 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl16 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
麦田里的守望者江16 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin