Choreographer源码阅读

QuibblerQuibbler 2021-12-08 约 59 分钟 2702 次阅读

Choreographer源码阅读

之前已经在很多地方看到过Choreographer的身影,必须要仔细的看一遍Choreographer源码以理解其实现原理。Choreographer请求和接受来自底层的VSYNC信号,给上层应用提供一个稳定的周期,去处理Input事件、动画、更新布局等。

1、Choreographer构造及初始化

Choreographer直译为编舞者,看完源码更能理解Choreographer为何能称之为"编舞者"。本篇基于android12-platform-release源码,将来再看可能有变化。

1.1、Choreographer构造

先从Choreographer的构造方法开始看:初始化FrameHandler、FrameDisplayEventReceiver以及CallbackQueue等重要对象。

private Choreographer(Looper looper, int vsyncSource) {
        //调用线程的Looper,所在线程必须有Looper
        mLooper = looper;
        
        //使用所在线程的Looper构造一个Handler,处理消息
        mHandler = new FrameHandler(looper);
        
        //USE_VSYNC默认为true,构造FrameDisplayEventReceiver。注册底层VSYNC回调
        mDisplayEventReceiver = USE_VSYNC
                ? new FrameDisplayEventReceiver(looper, vsyncSource)
                : null;
        mLastFrameTimeNanos = Long.MIN_VALUE;
        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
        
        //初始化几种类型的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的构造方法是private,框架必然提供了获取Choreographer实例的方法(常用设计方法)。继续往下看:

1.2、Choreographer初始化/获取

Choreographer的getInstance()方法可以获取当前线程的唯一Choreographer实例,调用该方法所在的线程必须有Looper。

/**
     * Gets the choreographer for the calling thread.  Must be called from
     * a thread that already has a {@link android.os.Looper} associated with it.
     *
     * @return The choreographer for this thread.
     * @throws IllegalStateException if the thread does not have a looper.
     */
    public static Choreographer getInstance() {
        return sThreadInstance.get();
    }

当前线程的唯一Choreographer实例借助ThreadLocal初始化并获取,ThreadLocal原理在Thread和ThreadLocal一文有详细说明。

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

如果获取Choreographer的线程为主线程,还会初始化并持有主线程的Choreographer实例mMainInstance:

private static volatile Choreographer mMainInstance;

通过getMainThreadInstance()方法可以获得主线程的Choreographer实例,不过该方法以及被标记为@hide不可使用,说明将在以后的API中删除。

/**
     * @return The Choreographer of the main thread, if it exists, or {@code null} otherwise.
     * @hide
     */
    public static Choreographer getMainThreadInstance() {
        return mMainInstance;
    }

同样的还有一个获取Choreographer实例的方法getSfInstance()需要了解一下,和前面刚提到的getInstance()方法仅两词只差:

@UnsupportedAppUsage
    public static Choreographer getSfInstance() {
        return sSfThreadInstance.get();
    }

同样,使用sSfThreadInstance存储实例。唯一区别在于VSYNC来源标志不同,使用DisplayEventReceiver中的VSYNC_SOURCE_SURFACE_FLINGER,即只要Surface Flinger正在处理帧,就发送VSYNC信号,而不是固定周期发送VSYNC信号。

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

应用开发者不可调用该方法,仅在ViewRootImpl初始化的构造方法中用到了此方法。ViewRootImpl在DecorView添加到Window中的流程一文中有提到过。

public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) {
        mContext = context;
        
        ...
        
        //初始化ViewRootImpl中的Choreographer,默认为false,使用后者: getInstance()
        mChoreographer = useSfChoreographer
                ? Choreographer.getSfInstance() : Choreographer.getInstance();
    }

再仔细看源码,会发现所有用到ViewRootImpl构造方法所传入的useSfChoreographer参数值均为false,getSfInstance()方法并没有被使用到,可能此方法也会在Android迭代的过程中被删除。

public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),
                false /* useSfChoreographer */);
    }
    
    public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session) {
        this(context, display, session, false /* useSfChoreographer */);
    }

2、Choreographer源码解析

继续往下看,Choreographer中的两个内部类:FrameHandler和FrameDisplayEventReceiver。这两个类的实例在1.1节Choreographer构造方法中初始化。

2.1、FrameHandler处理消息

FrameHandler继承自Handler,负责完成三个重要的操作:执行回调doFrame、请求VSYNC、调度Callback。所有向此FrameHandler发送的Message都用setAsynchronous(true)设置成了异步消息。

private final class FrameHandler extends Handler {
        
        //使用Choreographer所在线程的Looper处理消息
        public FrameHandler(Looper looper) {
            //这里创建的是同步Hhandler,但是后面所有消息都是异步消息
            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;
            }
        }
        
    }

2.2、FrameDisplayEventReceiver接收VSYNC信号

DisplayEventReceiver继承自DisplayEventReceiver,用来接收和请求VSYNC信号。DisplayEventReceiver收到信号后,底层调用dispatchVsync()方法:

// Called from native code.
    @SuppressWarnings("unused")
    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame,
            long frameTimelineVsyncId, long frameDeadline, long frameInterval) {
        onVsync(timestampNanos, physicalDisplayId, frame,
                new VsyncEventData(frameTimelineVsyncId, frameDeadline, frameInterval));
    }

进而调用onVsync()方法,该方法在DisplayEventReceiver中是空方法。

/**
     * Called when a vertical sync pulse is received.
     * The recipient should render a frame and then call {@link #scheduleVsync}
     * to schedule the next vertical sync pulse.
     *
     * @param timestampNanos The timestamp of the pulse, in the {@link System#nanoTime()}
     * timebase.
     * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
     * @param frame The frame number.  Increases by one for each vertical sync interval.
     * @param vsyncEventData The vsync event data.
     */
    public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
            VsyncEventData vsyncEventData) {
    }

Choreographer中的FrameDisplayEventReceiver内部类则实现了这个onVsync()空方法:

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);
        }
        
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
                VsyncEventData vsyncEventData) {
            try {
                
                //当前时间
                long now = System.nanoTime();
                
                //VSYNC时间比当前时间还超前
                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;
                }
                //保持VSYNC时间等相关信息
                mTimestampNanos = timestampNanos;
                mFrame = frame;
                mLastVsyncEventData = vsyncEventData;
                
                //通过obtain()方法获取可重用Message,自身作为Message的callback
                Message msg = Message.obtain(mHandler, this);
                
                //消息设置为异步消息,Choreographer中的所有Messag都是异步消息
                msg.setAsynchronous(true);
                
                //向FrameHandler发送消息
                mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
        
        //前面发送的消息到Handler,最后执行的是callback中的这个run()方法
        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
        }
    }

FrameDisplayEventReceiver同时也实现了Runnable接口,在onVsync()方法中将自身作为Message中携带的callback,根据Handler中Callback的作用一文,可以知道消息最后执行的是FrameDisplayEventReceiver实现的run()方法:

@Override
    public void run() {
        mHavePendingVsync = false;
        //执行Choreographer核心方法:doFrame()
        doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
    }

3、Choreographer核心方法:doFrame()

在run()方法中调用Choreographer的 doFrame() 方法。框架的方法都有点长,耐心的逐行去看,很容易理解。流程图先欠着,直接上代码:

void doFrame(long frameTimeNanos, int frame,
            DisplayEventReceiver.VsyncEventData vsyncEventData) {
    	//真正执行doFrame绘制该帧的时间。
        final long startNanos;
        
        //使用Native帧率。以前用的是mFrameIntervalNanos变量:Android手机屏幕是60Hz的刷新频率,就是16ms
        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) {
            
            	//mFrameScheduled在scheduleFrameLocked(long now)方法中被置位true,也就是有postCallback任务才会执行
                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");
                }
                //保持当前帧的VSYNC时间,frameTimeNanos是vsync消息要求绘制该帧的时间。
                //不会超过当前时间,在onVsync方法中会将超前的时间回退到当前时间。
                long intendedFrameTimeNanos = frameTimeNanos;
                
                //真正执行doFrame绘制该帧的时间
                startNanos = System.nanoTime();
                
                //当前帧的开始时间和来自VSYNC要求绘制该帧的时间差
                final long jitterNanos = startNanos - frameTimeNanos;
                
                //时间差大于一个时钟周期,认为发生跳帧。
                //以前API这里判断的变量是mFrameIntervalNanos,Android手机屏幕是60Hz的刷新频率,就是16ms。
                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.");
                    }
                    
                    //根据帧偏差,修正VSYNC要求绘制帧的时间。
                    frameTimeNanos = startNanos - lastFrameOffset;
                }
                
                //如果修正后的时间回溯,小于上一帧处理的时间。调用scheduleVsyncLocked()请求下一个时钟信号。
                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()请求下一个时钟信号。
                    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;
            }
            
            //执行各种Callback回调
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,
                    frameIntervalNanos);
            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        
        //打印帧log,在调试过程中,看系统log有帮助
        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.");
        }
    }

4、Choreographer回调方法doCallbacks

doFrame()方法的最后调用一系列doCallbacks()方法按顺序执行各种类型的Callback:

void doFrame(long frameTimeNanos, int frame,DisplayEventReceiver.VsyncEventData vsyncEventData) {
        ...
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
        
        //在动画之前,先处理Input输入事件
        mFrameInfo.markInputHandlingStart();
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos, frameIntervalNanos);
        
        //遍历之前处理动画,新增了一种CALLBACK_INSETS_ANIMATION
        mFrameInfo.markAnimationsStart();
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos, frameIntervalNanos);
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos,frameIntervalNanos);
        
        //执行遍历操作:measure、layout、draw
        mFrameInfo.markPerformTraversalsStart();
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos, frameIntervalNanos);
        
        //提交操作,用来修正动画启动时间
        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos, frameIntervalNanos);
        ...
    }

4.1、几种回调队列

Choreographer定义了几种类的CALLBACK:

@UnsupportedAppUsage
    private final CallbackQueue[] mCallbackQueues;

    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;
    
    private static final int CALLBACK_LAST = CALLBACK_COMMIT;

CallbackQueue队列在Choreographer构造方法中初始化:

private Choreographer(Looper looper, int vsyncSource) {
        ...
        //队列数组大小为5: 0 1 2 3 4
        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
        
        //分别初始化这5个不同类型的回调队列
        for (int i = 0; i <= CALLBACK_LAST; i++) {
            mCallbackQueues[i] = new CallbackQueue();
        }
        ...
    }

4.2、doCallbacks()回调

看一下执行的doCallbacks()回调方法,这个方法在Animator属性动画源码中也有详细的解析。

void doCallbacks(int callbackType, long frameTimeNanos, long frameIntervalNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            final long now = System.nanoTime();
            
            //根据Callbacktype从mCallbackQueues取出对应的CallbackQueue,再从中拿出CallbackRecord 
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            
            //没有需要执行的CallbackRecord直接返回
            if (callbacks == null) {
                return;
            }
            
            //标记为:正在执行Callback
            mCallbacksRunning = true;

            //callbackType类型为CALLBACK_COMMIT特殊处理
            if (callbackType == Choreographer.CALLBACK_COMMIT) {
                final long jitterNanos = now - frameTimeNanos;
                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 {
            //挨个执行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));
                }
                c.run(frameTimeNanos);
            }
        } finally {
            synchronized (mLock) {
                mCallbacksRunning = false;
                do {
                    final CallbackRecord next = callbacks.next;
                    recycleCallbackLocked(callbacks);
                    callbacks = next;
                } while (callbacks != null);
            }
        }
    }

4.3、举例

前面已经对Choreographer机制有了比较系统的认识。再举几个例子,开发中哪些地方都直接或者间接的使用到了Choreographer。

①CALLBACK_INPUT:在ViewRootImpl的scheduleConsumeBatchedInput()方法中:

void scheduleConsumeBatchedInput() {
        if (!mConsumeBatchedInputScheduled && !mConsumeBatchedInputImmediatelyScheduled) {
            mConsumeBatchedInputScheduled = true;
            //postCallback类型CALLBACK_INPUT
            mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
                    mConsumedBatchedInputRunnable, null);
        }
    }

②CALLBACK_ANIMATION:在View的postOnAnimation(Runnable action)方法中:

public void postOnAnimation(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            //postCallback类型CALLBACK_ANIMATION
            attachInfo.mViewRootImpl.mChoreographer.postCallback(
                    Choreographer.CALLBACK_ANIMATION, action, null);
        } else {
            getRunQueue().post(action);
        }
    }

        ③CALLBACK_INSETS_ANIMATION:在ViewRootInsetsControllerHost中的postInsetsAnimationCallback(Runnable r)方法中:

@Override
    public void postInsetsAnimationCallback(Runnable r) {
        mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION, r,
                null /* token */);
    }

        ④CALLBACK_TRAVERSAL:在ViewRootImpl中的scheduleTraversals()方法中:

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //postCallback类型CALLBACK_TRAVERSAL
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

        ⑤CALLBACK_COMMIT:在AnimationHandler的postCommitCallback(runnable)方法中:

@Override
        public void postCommitCallback(Runnable runnable) {
            //postCallback类型CALLBACK_COMMIT
            mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
        }

看完Choreographer的源码,了解其实现原理:接收底层VSYNC信号,为上层应用提供稳定刷新机制;接受应用各种回调任务,然后根据底层VSYNC信号统一处理。如此承上启下运作,实现输入事件分发、动画刷新、布局绘制等一系列开发者所熟悉的场景。

分析绘制性能可以用Systrace抓取trace(或AndroidStudio自带的Profiler抓取的运行trace):


阅读Choreographer源码,对分析日常的卡顿、定位绘制过程中所遇到的问题很有帮助。

相关源码:

        googlesource/platform/frameworks/base/core/java/android/hardware/display/

        googlesource/platform/frameworks/base/services/core/java/com/android/server/display

        googlesource/platform/frameworks/base/services/core/java/com/android/server/display/VirtualDisplayAdapter

        https://source.android.google.cn/devices/graphics

        https://developer.android.google.cn/reference/android/view/Choreographer?hl=en

        https://developer.android.google.cn/reference/android/view/Choreographer.FrameCallback?hl=en

        https://developer.android.google.cn/ndk/reference/group/choreographer?hl=en

        

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w