理解VSync-1-软件VSync及节拍器

1. 前言

忽然有一天,我想要做一件事:去代码中去验证那些曾经被"灌输"的理论。

-- 服装学院的IT男

本篇为 VSync 系列的第一篇,主要介绍软件 VSync 和理解节拍器。

本系列为之前学习 SurfaceFlinger 整理的一些笔记,现在分享出来,希望能帮助到有需要的同学。 代码基于 Android 13 ,虽然最新源码源码中部分逻辑已经了,但总体思路还是没有变的,不影响对 VSync 整体逻辑的理解。

VSync 系列目录:

理解VSync-1-软件VSync及节拍器

1. SW-VSync简介

为什么不直接使用硬件 VSync (HW-VSYNC)?个人认为有以下两个原因:

    1. 如果 SurfaceFlinger 和每个应用都去监听硬件 VSync(HW-VSYNC) 无论从功耗上还是从模块设计上都是不妥的。
    1. 硬件(HW-VSYNC)是周期性的,因此软件层完全有可行性去模拟出一个等频率的软件 VSync(SW-VSYNC),于是有了下面这张图。

这张图来自 Google 官网(source.android.com/docs/core/g...

有一个"DispSync"可以基于 HW-VSYNC 来计算出对应的 SW-VSYNC ,这样软件层就接收 SW-VSYNC 最对应处理就好。 至于这里提到的"相位偏移","软件锁相回路 "是啥,暂时可以忽略,以黑盒的形式知道"DispSync"能算出 SW-VSYNC 即可。

这幅图中,"DispSync"计算的 SW-VSYNC 有两类:

  1. Choreographer 使用的 VSYNC
  2. SurfaceFlinger 使用的 SF_VSYNC

Choreographer 我们知道是 Java 层的一个类,控制应用绘制 UI 刷新节奏的,所以这里的第一种 VSYNC 就是给应用的。 第二种 SF_VSYNC 则是给 SurfaceFlinger 控制 Layer 合成的,

1.1 SW-VSync模型与计算

这块的理解可复杂可简单,复杂起来就是要去看模型的计算规则,是要哪些参数,相位差是多少,怎么计算的 SW-VSYNC 。既然是模拟的 HW-VSYNC 那必然还会有误差,出现误差后什么如何纠正等等。 但是如果不是从事 SurfaceFlinger 模块开发或者是刚了解这块的,建议以黑盒形式简单的了解这块。

    1. Google是以线性方程: y=bx+a 的方式来计算 SW-VSYNC
    1. 有一个反馈机制,当 SW-VSYNC 与 HW-VSYNC 差距达到阈值就开始重新采样计算纠正

1.2 SW-VSync 的分类

从上面的图可以看出 SW-VSync 分为了两类,这里再结合 Trace 看一下真正的 SW-VSync 有哪些:

    1. VSYNC-app :控制APP的UI渲染
    1. VSYNC-appsf :给APP用,感觉是用于调试的,暂时可以忽略这个
    1. VSYNC-sf : 控制SurfaceFlinger合成Layer

这里比较容易疑惑的是多出来了个 VSYNC-appsf ,这个目前也不太了解,因为 T 的代码对这块有重构,所以新增了这个 VSYNC-appsf ,感觉像是用于调试的,不过可以目前可以忽略这个,不影响后面的分析。 感兴趣的可以看[马哥的文章](blog.csdn.net/learnframew...

1.3 SW-VSync的产生

这里可能有三个问题:

    1. HW-VSync 是周期性一直存在的,那 SW-VSync 也一直存在吗?
    1. SW-VSync 是怎么分类的
    1. VSYNC-app ,VSYNC-appsf 和 VSYNC-sf 是同时触发的吗?

这三个问题将在本系列文章中一一解答,但目前需要先知道另一件事:SW-VSync 是如何产生的?

先抛开代码,举个栗子: 会看这篇文章的应该都是程序员,且在一线城市。以我所在的上海为例,每天上下班都要坐地铁,地铁的时间是很准时的,假设他的发车时间是全天固定的(早晚高峰肯定固定),以2分钟一趟为例,如果知道他的首发时间,知道某一趟到站的时间,是不是就可以计算到每一趟地铁到达你要坐的那个站的时间。

这个时间启动高德地图都能帮你算好了,所以只要打开地图,你就可以看到下一趟到站的地铁是什么时候,甚至下面几趟的都可以算好。不用去地铁站看站牌信息,在手机上就可以看到地铁到站的时间。

这个地铁到站的时间计算,和软件模型计算 SW-VSync 是类似的。

如果让程序员写一个逻辑,地铁要到站的时候就手机振动,或者发送一个Notification 通知用户,那你怎么做?

写个定时器呗,根据当前时间,和已知的下一趟地铁来的时间,计算出时间差,传给定时器,定时结束就可以做相应的事情通知用户了。

SW-VSync 的产生其实也就是通过定时器实现的,如果有应用或者 SurfaceFlinger 需要 VSync,就会发送请求,软件根据发送请求的时间,也就是当前时间(now)和它所预测的下一次 HW-VSync 时间,就能计算出还有多久 HW-VSync 要来了,假设是 10ms,那就开始设置一个 10ms 的定时任务,定时一到就触发回调,该干啥干啥。比如是 SurfaceFlinger 请求的 VSYNC 那就通知 SurfaceFlinger ,VSync 来了就可以开始干活了(合成Layer), 这个 VSync 就是 VSYNC-sf 。假设是应用请求的 VSync ,那定时结束就通知 app ,这个 VSync 就是 VSYNC-app 。 当然也可能2个同时申请了,那就都通知到,也就是既触发 VSYNC-sf 也触发 VSYNC-app 。

这里的描述,可能不是完全正确,硬扣细节肯定也是不严谨的,但是对于刚开始接触 VSync 来说,这样理解我觉得是可以先有这么一个概念,然后再通过实际的源码加深印象。

SW-VSync 是按需产生的,谁请求就给谁,都不请求就没人触发定时器了,自然也就不产生 SW-VSync 了。

一般来说肯定是 app 先请求绘制,绘制完了 SurfaceFlinger 再请求 VSync 来合成,所以绝大部分情况SurfaceFlinger 请求 VSync 的时候,定时器已经被 app 的请求触发了,所以 SurfaceFlinger 只需要等定时结束就好。

另外这里说的 app 是代表所有应用,而不是指某个应用,app 先收到 VSYNC-app ,然后再发送给具体需要的应用。

整个框架模型如下图所示:

其中 VSYNC-sf 是发送给 SurfaceFlinger 让其做 layer 的合成, VSYNC-app 则是还需要再做一次分发,只有具体的应用请求了才需要分发给这个应用。而应用端是由Choreographer来处理 Vsync 相关的事务。

这里的 VSYNC-appsf 用于测试,可以忽略,后面的分析一般也不会提到。

2. SW-VSYNC 的节拍器 VsyncDispatch 创建

前面提到 SW-VSYNC 其实目前有三个监听者: [VSYNC-app , VSYNC-appsf , VSYNC-sf] ,也大概介绍了 SW-VSYNC 的概念,那么撸代码先分析这个 3 个监听是如何注册的。

SW-VSync 模块中存在一个很重要的类,它就是号称 SW-VSYNC 的节拍器(VsyncDispatch),也有叫它信号泵的,顾名思义它会控制软件 VSync 的分发。

上面提到的三个监听都会被注册到 VsyncDispatch 子类 VSyncDispatchTimerQueue 下的 mCallbacks 对象中。

这一小节先来了解一下这个节拍器。

下面开始看代码:

开机过程中会启动 SurfaceFlinger 进程,经过一系列调用后会执行到 SurfaceFlinger::initScheduler 方法。

scss 复制代码
# SurfaceFlinger.cpp

    void SurfaceFlinger::initScheduler(const sp<DisplayDevice>& display) {
        // 避免重复初始化
        if (mScheduler) {
            mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs());
            return;
        }
        // 是一个Fps对象,其中存储了刷新率fps和刷新周期period
        const auto currRefreshRate = display->getActiveMode()->getFps();
        mRefreshRateStats = std::make_unique<scheduler::RefreshRateStats>(*mTimeStats, currRefreshRate,
                                                                        hal::PowerMode::OFF);
        // 封装了不同刷新率下的VSync配置信息
        mVsyncConfiguration = getFactory().createVsyncConfiguration(currRefreshRate);
        mVsyncModulator = sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs());

        ......
        // 重点* 1.1 创建 Scheduler
        mScheduler = std::make_unique<scheduler::Scheduler>(static_cast<ICompositor&>(*this),
                                                            static_cast<ISchedulerCallback&>(*this),
                                                            features);
        {
            auto configs = display->holdRefreshRateConfigs();
            if (configs->kernelIdleTimerController().has_value()) {
                features |= Feature::kKernelIdleTimer;
            }
            // 重点* 1.2 初始化 VsyncSchedule
            mScheduler->createVsyncSchedule(features);
            mScheduler->setRefreshRateConfigs(std::move(configs));
        }
        setVsyncEnabled(false);
        mScheduler->startTimers();

        const auto configs = mVsyncConfiguration->getCurrentConfigs();
        const nsecs_t vsyncPeriod = currRefreshRate.getPeriodNsecs();
        // 重点* 2.1 app 注册回调
        mAppConnectionHandle =
                mScheduler->createConnection("app", mFrameTimeline->getTokenManager(),
                                            /*workDuration=*/configs.late.appWorkDuration,
                                            /*readyDuration=*/configs.late.sfWorkDuration,
                                            impl::EventThread::InterceptVSyncsCallback());
        // 重点* 2.2 appSf 注册回调
        mSfConnectionHandle =
                mScheduler->createConnection("appSf", mFrameTimeline->getTokenManager(),
                                            /*workDuration=*/std::chrono::nanoseconds(vsyncPeriod),
                                            /*readyDuration=*/configs.late.sfWorkDuration,
                                            [this](nsecs_t timestamp) {
                                                mInterceptor->saveVSyncEvent(timestamp);
                                            });
        // 重点* 2.3 sf 注册回调
        mScheduler->initVsync(mScheduler->getVsyncDispatch(), *mFrameTimeline->getTokenManager(),
                            configs.late.sfWorkDuration);
        ......
    }

这里一共分为两大步:

    1. Scheduler 和 VsyncSchedule 的初始化
    1. app ,appsf,sf 注册回调

app ,appsf,sf 对应到 Trace 上就是 VSYNC-app、VSYNC-apps、VSYNC-sf

调用链:

arduino 复制代码
SurfaceFlinger::initScheduler
    Scheduler::init                   -- 创建 Scheduler
    Scheduler::createVsyncSchedule    -- 创建 VsyncSchedule
    Scheduler::createConnection       -- app appsf 注册回调
    MessageQueue::initVsync           -- sf 注册回调

2.1 Scheduler 和 VsyncSchedule 的初始化

在 SurfaceFlinger::initScheduler 方法的1.1处创建 Scheduler。

随后在1.2处执行了 Scheduler::createVsyncSchedule 方法,看方法名是创建一个 VsyncSchedule 。 这里涉及到 C++17 的语法,mVsyncSchedule 是定义在头文件的变量,然后执行 emplace 时会用参数用提供的 features 参数来触发 VsyncSchedule 对象的构建。

arduino 复制代码
# Scheduler.h
    std::optional<VsyncSchedule> mVsyncSchedule;

# Scheduler.cpp
    void Scheduler::createVsyncSchedule(FeatureFlags features) {
        mVsyncSchedule.emplace(features);
    }

Scheduler 和 VsyncSchedule 都构建了,其中 VsyncSchedule 下面有三个重要的类。

ini 复制代码
# VsyncSchedule.h

    class VsyncSchedule {
        ......
    private:
        // 定义别名
        using TrackerPtr = std::unique_ptr<VsyncTracker>;
        using DispatchPtr = std::unique_ptr<VsyncDispatch>;
        using ControllerPtr = std::unique_ptr<VsyncController>;
        ......
        // 定义三个对象
        TrackerPtr mTracker;
        DispatchPtr mDispatch;
        ControllerPtr mController;
    }

# VsyncSchedule.cpp
    // 构建时调用
    VsyncSchedule::VsyncSchedule(FeatureFlags features)
        : mTracker(createTracker()),
            mDispatch(createDispatch(*mTracker)),
            mController(createController(*mTracker, features)) {
        if (features.test(Feature::kTracePredictedVsync)) {
            mTracer = std::make_unique<PredictedVsyncTracer>(*mDispatch);
        }
    }

可以看到 VsyncSchedule 构建触发了三个重要的对象创建:

    1. VsyncTracker -- 训练软件 VSync 模型
    1. VsyncDispatch -- 节拍器,控制软件 VSync 的回调
    1. VsyncController -- 传递HWVsync,presentFence信号

这3个类在整个软件 VSync 的模块中是极其重要的,其中 VsyncDispatch 就是控制软件 VSync 的回调,这个很重要,其子类为:VSyncDispatchTimerQueue 。 目前先只看这个类的创建流程。

2.2 Vsync 节拍器的创建(VsyncDispatch)

创建 VsyncDispatch 的方法为 VsyncSchedule::createDispatch ,代码如下:

arduino 复制代码
# VsyncSchedule.cpp

    VsyncSchedule::DispatchPtr VsyncSchedule::createDispatch(VsyncTracker& tracker) {
        using namespace std::chrono_literals;

        // TODO(b/144707443): Tune constants.
        constexpr std::chrono::nanoseconds kGroupDispatchWithin = 500us;
        constexpr std::chrono::nanoseconds kSnapToSameVsyncWithin = 3ms;
        // * 实际创建的是 VSyncDispatchTimerQueue
        return std::make_unique<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
                                                        kGroupDispatchWithin.count(),
                                                        kSnapToSameVsyncWithin.count());
    }
    1. 传递了四个参数,第一个参数为构建了一个 Timer 对象,第二个参数传递了 VsyncTracker ,后面两个为时间,暂时不管。
    1. 实际上返回的是 VSyncDispatchTimerQueue 对象,其是 VsyncSchedule 子类
css 复制代码
# VSyncDispatchTimerQueue.cpp
    VSyncDispatchTimerQueue::VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper> tk,
                                                    VSyncTracker& tracker, nsecs_t timerSlack,
                                                    nsecs_t minVsyncDistance)
        : mTimeKeeper(std::move(tk)),
            mTracker(tracker),
            mTimerSlack(timerSlack),
            mMinVsyncDistance(minVsyncDistance) {}

2.2.1 定时器 Timer

构建 VSyncDispatchTimerQueue 的时候传了一个 Timer 对象,Timer 是 TimeKeeper 的子类,然后被赋值给 VSyncDispatchTimerQueue 的 mTimeKeeper 变量,VsyncTracker 被赋值给 mTracker 。

这个 Timer 很重要,内部创建了一个线程,线程名为 "TimerDispatch" 专门控制软件层的 VSync ,节拍器的由来也是因为这个功能。

scss 复制代码
# Timer.cpp
    Timer::Timer() {
        reset();
        // 执行 threadMain 方法
        mDispatchThread = std::thread([this]() { threadMain(); });
    }

Timer 的父类是 TimeKeeper ,其父类是 Clock 的子类。

2.2.2 VSync 的定时的申请与结束处理

软件 VSync 的产生其实是靠一个定时器,也就是上的 Timer,既然是定时器那就必定有以下两个操作:

    1. 触发定时任务的方法
    1. 定时结束后的回调处理

这两个方法都定义在 VsyncDispatch 的子类 VSyncDispatchTimerQueue 中,对应代码如下:

javascript 复制代码
# VSyncDispatchTimerQueue.cpp

    // 申请Vsync(本质就是触发定时任务)
    ScheduleResult VSyncDispatchTimerQueue::schedule(CallbackToken token,
                                                    ScheduleTiming scheduleTiming) {
            ......// 触发定时任务
        }

    // 定时结束执行
    void VSyncDispatchTimerQueue::timerCallback() {
        ....... // 遍历 mCallbacks 发送Vsync
    }

这两个方法后面会详细介绍,应用和 SurfaceFlinger 如果想要接收到 VSync 信号则需要先申请 VSync,而申请 VSync 的流程必须要走到 VSyncDispatchTimerQueue::schedule 方法。

这个方法方法的最重要的事就是根据参数,触发一个定时任务。

定时结束后,其实就可以理解为马上要发送 SW-VSYNC 了,VSyncDispatchTimerQueue::timerCallback 方法会根据条件触发对应的 VSync 发送。

2.2.3 VSyncDispatchTimerQueue下 mCallbacks 的定义

"根据条件触发对应的 VSync 发送"是什么意思呢? 首先软件 VSync 是按需发送的,谁请求了才发给谁,所以会根据"条件"发送。 另外我们知道有 Trace 上就是 VSYNC-app、VSYNC-appsf、VSYNC-sf 三个 SW-VSYNC,也就是在 SurfaceFlinger::initScheduler 方法里看到触发了 app,appsf,sf 三个回调的注册。

这三个最终是注册到 VSyncDispatchTimerQueue 下定义的 mCallbacks 中,这个变量的定义如下:

arduino 复制代码
# VSyncDispatchTimerQueue.h

    class VSyncDispatchTimerQueue : public VSyncDispatch {
    ......
    private:

        using CallbackMap =
            std::unordered_map<CallbackToken, std::shared_ptr<VSyncDispatchTimerQueueEntry>>;
        CallbackMap mCallbacks GUARDED_BY(mMutex);
    }

mCallbacks 的原来是一个键值对, key 为唯一标识 token,value 是 VSyncDispatchTimerQueueEntry 类型。

在 VSyncDispatchTimerQueue::timerCallback 方法中就是遍历 mCallbacks 下的元素,按需触发对应的回调,也就是触发响应的 VSYNC-app VSYNC-appsf VSYNC-sf 。

2.3 Vsync 节拍器小结

VSyncDispatchTimerQueue 相关的 UML 类图如下:

这里只是对 VSyncDispatchTimerQueue 这个类做了简单的介绍,后面的文章中还会看到其更多的东西,

    1. VSyncDispatchTimerQueue 是节拍器 VsyncDispatch 的子类
    1. VsyncDispatch 内部有个定时器(Timer)对象,维护了一个叫 "TimerDispatch" 专门控制软件层的 VSYNC
    1. 应用和 SurfaceFlinger 想要接收到 Vsync 都必须通过 VSyncDispatchTimerQueue::schedule 来申请(触发定时任务)
    1. 定时结束后触发 VSyncDispatchTimerQueue::timerCallback 方法
    1. VSyncDispatchTimerQueue 内部有个 mCallbacks 集合维护了对 SW-VSync 感兴趣的回调,也就是 应用(app)、SurfaceFlinger(sf)、和 appsf
    1. 通过 VSyncDispatchTimerQueue::registerCallback 方法将回调注册到 mCallbacks 中,这个方法定义在父类中
    1. VSyncDispatchTimerQueue 下面的 mCallbacks 里的元素是 VSyncDispatchTimerQueueEntry (这个类也很复杂,目前类图里先省略内容)

3. 补充:dump 软件 Vsync 回调集合

虽然还没分析具体代码,但是已经多次给出结论:app appsf sf 都在 VSyncDispatchTimerQueue::mCallbacks 下。 为了确认和再加深这个结论,可以使用以下命令对 SurfaceFlinger 进行 dump:

复制代码
adb shell dumpsys SurfaceFlinger >sf.txt

输出和回调相关的内容如下:

yaml 复制代码
VsyncDispatch:
	Timer:
		DebugState: Waiting
	...... // 忽略

	Callbacks:
		appSf:  
			workDuration: 16.67ms readyDuration: 27.60ms earliestVsync: -525582.81ms relative to now
			mLastDispatchTime: 525566.12ms ago
		sf:  
			workDuration: 27.60ms readyDuration: 0.00ms earliestVsync: -56083.53ms relative to now
			mLastDispatchTime: 55935.26ms ago
		app:  
			workDuration: 20.00ms readyDuration: 27.60ms earliestVsync: -32569.80ms relative to now
			mLastDispatchTime: 32553.14ms ago

可以看到 "Callbacks:" 下有三个对象,就是上面提到的 app、appsf、sf。

对应控制输出的代码如下:

scss 复制代码
# VSyncDispatchTimerQueue.cpp
    void VSyncDispatchTimerQueue::dump(std::string& result) const {
        std::lock_guard lock(mMutex);
        StringAppendF(&result, "\tTimer:\n");
        mTimeKeeper->dump(result);
        StringAppendF(&result, "\tmTimerSlack: %.2fms mMinVsyncDistance: %.2fms\n", mTimerSlack / 1e6f,
                    mMinVsyncDistance / 1e6f);
        StringAppendF(&result, "\tmIntendedWakeupTime: %.2fms from now\n",
                    (mIntendedWakeupTime - mTimeKeeper->now()) / 1e6f);
        StringAppendF(&result, "\tmLastTimerCallback: %.2fms ago mLastTimerSchedule: %.2fms ago\n",
                    (mTimeKeeper->now() - mLastTimerCallback) / 1e6f,
                    (mTimeKeeper->now() - mLastTimerSchedule) / 1e6f);
        StringAppendF(&result, "\tCallbacks:\n");
        // 内容就是 mCallbacks 集合下的内容
        for (const auto& [token, entry] : mCallbacks) {
            entry->dump(result);
        }
}

我们已经知道这个 mCallbacks 下的元素就是3个 VSyncDispatchTimerQueueEntry 实例,所以具体的 dump 要看 VSyncDispatchTimerQueueEntry 的实现。 VSyncDispatchTimerQueueEntry 定义在 VSyncDispatchTimerQueue.cpp 下面。

swift 复制代码
# VSyncDispatchTimerQueue.cpp
    void VSyncDispatchTimerQueueEntry::dump(std::string& result) const {
        std::lock_guard<std::mutex> lk(mRunningMutex);
        std::string armedInfo;
        if (mArmedInfo) {
            StringAppendF(&armedInfo,
                        "[wake up in %.2fms deadline in %.2fms for vsync %.2fms from now]",
                        (mArmedInfo->mActualWakeupTime - systemTime()) / 1e6f,
                        (mArmedInfo->mActualReadyTime - systemTime()) / 1e6f,
                        (mArmedInfo->mActualVsyncTime - systemTime()) / 1e6f);
        }

        StringAppendF(&result, "\t\t%s: %s %s\n", mName.c_str(),
                    mRunning ? "(in callback function)" : "", armedInfo.c_str());
        StringAppendF(&result,
                    "\t\t\tworkDuration: %.2fms readyDuration: %.2fms earliestVsync: %.2fms relative "
                    "to now\n",
                    mScheduleTiming.workDuration / 1e6f, mScheduleTiming.readyDuration / 1e6f,
                    (mScheduleTiming.earliestVsync - systemTime()) / 1e6f);

        if (mLastDispatchTime) {
            StringAppendF(&result, "\t\t\tmLastDispatchTime: %.2fms ago\n",
                        (systemTime() - *mLastDispatchTime) / 1e6f);
        } else {
            StringAppendF(&result, "\t\t\tmLastDispatchTime unknown\n");
        }
    }

4. 总结

本篇介绍了个人对 SW-Vsync 的理解,另外重点介绍了 VsyncDispatch 和其子类 VSyncDispatchTimerQueue 。

下一篇将详细分析 SurfaceFlinger::initScheduler 方法中三个对象(app, appsf, sf)是如何把自己注册到 VSyncDispatchTimerQueue 下的 mCallbacks 中的。

本篇涉及到的调用链如下:

arduino 复制代码
SurfaceFlinger::initScheduler
    Scheduler::init                 -- Scheduler初始化
    Scheduler::createVsyncSchedule  -- VsyncSchedule 初始化
        VsyncSchedule::emplace
            VsyncSchedule::init
                VsyncSchedule::createTracker           -- VsyncTracker
                VsyncSchedule::createDispatch          -- VsyncDispatch(节拍器)
                    Timer::Timer                       -- 定时器创建
                    VSyncDispatchTimerQueue::init  
                VsyncSchedule::createController        -- VsyncController
    Scheduler::createConnection     -- app appsf 注册回调
    MessageQueue::initVsync      -- sf 注册回调

参考:

<千里马学框架-SurfaceFlinger>

www.jianshu.com/p/5e9c558d1...

source.android.com/docs/core/g...

相关推荐
___波子 Pro Max.1 小时前
Android envsetup与Python venv使用指南
android·python
武帝为此2 小时前
【MySQL 删除数据详解】
android·数据库·mysql
顾林海2 小时前
深度解析HashMap工作原理
android·java·面试
V少年2 小时前
深入浅出DiskLruCache原理
android
鱼洗竹2 小时前
协程的挂起与恢复
android
清风~徐~来3 小时前
【Linux】进程创建、进程终止、进程等待
android·linux·运维
百锦再4 小时前
Android游戏辅助工具开发详解
android·游戏·模拟·识别·辅助·外挂
QING6185 小时前
Kotlin 类型转换与超类 Any 详解
android·kotlin·app
QING6185 小时前
一文带你了解 Kotlin infix 函数的基本用法和使用场景
android·kotlin·app
好_快5 小时前
Lodash源码阅读-take
前端·javascript·源码阅读