系统架构全景

系统架构全景:五层楼的 Android

1.1 一张五层楼的总平面图

把 Android 想象成一栋五层楼。最上层住着**应用与运行时(ART)**,你写的每个 App 都在这里,由 Android Runtime 把字节码变成机器码执行;第二层是 **Framework 框架层**,核心是一个叫 `system_server` 的系统进程,里面运行着 ActivityManagerService、WindowManagerService、PackageManagerService 等几十个大管家,应用进程的创建、窗口的管理、包的安装都由它们裁决;第三层是**原生服务层**,一批用 C++ 写的守护进程------SurfaceFlinger 管合成显示、AudioFlinger 管音频、installd 管装包、apexd 管 APEX 模块更新------它们不依赖 Java 世界,是系统的「体力劳动者」;第四层是 **HAL 与厂商层**,硬件抽象层用 AIDL/HIDL 接口把各家芯片的差异封装成统一契约,VINTF 清单描述能力、SELinux 管住谁能访问谁;最底层是 **Linux 内核**,进程调度、内存管理、驱动、网络栈都在这里。

| 楼层 | 名称 | 代表组件 | 负责回答的问题 |

|---|---|---|---|

| 5 | 应用与运行时 | App、ART、`dalvik.vm` 参数 | 代码怎么跑起来、跑多快 |

| 4 | Framework | `system_server`:AMS、WMS、PMS | 组件怎么调度、窗口怎么摆 |

| 3 | 原生服务 | SurfaceFlinger、AudioFlinger、installd、apexd | 图形、音频、装包谁来做 |

| 2 | HAL 与厂商 | AIDL/HIDL HAL、VINTF、SELinux、VNDK | 硬件差异怎么屏蔽、边界怎么守 |

| 1 | Linux 内核 | 调度器、内存管理、Binder 驱动、驱动 | 资源怎么分、硬件怎么管 |

这张表的最大用处是**定位问题的楼层**。应用卡顿,先想主线程在等谁:等自己(5 楼)、等 Binder 返回(4 楼)、等 SurfaceFlinger 合成(3 楼)、等 GPU 驱动(1 楼)?性能分析的第一步永远不是打开工具,而是先猜楼层,再用工具验证------这能省掉八成无效挖掘。

1.2 system_server:Framework 的心脏

`system_server` 是开机后由 Zygote 孵化的第一个「正式」Java 进程,SystemUI、Launcher 之外的几乎所有系统服务都注册在这里。对性能排查最重要的几位管家:**ActivityManagerService(AMS)**决定四大组件的生命周期与进程调度;**WindowManagerService(WMS)**管理窗口层级、布局与动画,和 SurfaceFlinger 密切配合;**PackageManagerService(PMS)**负责解析 APK、管理安装与组件查询,首次开机慢的很大一部分账要记在它头上。这些服务运行在各自的 Binder 线程池里,应用的一次 `startActivity` 背后就是一次跨进程调用:你的主线程把请求发给 AMS,AMS 再通知目标进程,来回全靠 Binder。

1.3 Binder:Android 的「总机」

Binder 是 Android 的进程间通信总线,地位相当于整栋楼的电话交换机。一次同步 Binder 调用的旅程是:调用方线程通过**代理对象(proxy)**把参数打包,经 `ioctl` 进入 Binder 驱动;驱动把数据**一次拷贝**送进目标进程的接收缓冲,并从目标服务的**Binder 线程池**里唤醒一个线程执行真正的实现;结果沿原路返回。理解 Binder 对性能有三点关键:其一,**它是序列化开销**,传大对象、频繁调用都会累出毫秒级延迟,日志里的 `Binder transaction` 时长值得盯;其二,**线程池有上限**,服务端某个方法跑太久会拖垮整个服务的响应;其三,**oneway 调用会排队**,异步不等于即时,广播和事件分发里常见「发了很多、处理滞后」的背压现象。

1.4 Zygote 与应用进程的出生

每个 App 进程都不是从二进制冷启动的,而是从 **Zygote** 这个「干细胞进程」`fork` 出来的。Zygote 在开机早期就预加载了大量类库与资源,fork 时借助写时复制(COW)几乎零成本地共享给子进程;之后新进程再加载自己的 APK 字节码。这套设计的性能含义很直接:**进程本身创建很快,慢的是预加载之后的那串初始化**------`Application.onCreate`、ContentProvider 安装、首个 Activity 的创建,这正是启动优化篇(第 14 篇)要拆解的主线。Android 16 起 Zygote 的多进程模型还在演进(如独立的服务进程池),但「fork 快、初始化慢」的判断依然成立。

1.5 ART:从字节码到机器码

ART(Android Runtime)执行代码有三档:**解释执行**最慢最省空间;**JIT 即时编译**边跑边把热点编译成机器码;**AOT 预先编译**在安装或空闲时通过 dex2oat 直接把 DEX 编成原生库。纯 AOT 浪费空间,纯 JIT 首跑慢,所以现代 Android 走混合路线:先用 Profile(运行画像)标出热点,编译器优先照顾它们------Baseline Profile、Cloud Profile、Startup Profile 全家桶都建立在这个思想上,应用侧的最大可控杠杆就是提交一份高质量的 Profile,让 ART 把启动关键路径提前编译好。另一个机制是**去优化(deoptimization)**:当 JIT 的乐观假设被推翻(例如某个虚调用突然换了实现),已编译代码会退回解释执行,表现为「越跑越慢」的诡异曲线,抓 trace 时能看到 `deoptimize` 事件。

1.6 容易被忽略的机制群

这一层还散落着若干「平时看不见、出事才想起」的机制。**MessageQueue 与锁竞争**:主线程的 `Looper` 靠消息队列驱动,高并发下队列锁会成为瓶颈,社区有过 DeliQueue 等无锁化探索,Android 17 已为 target API 37 的应用启用无锁 MessageQueue------但依赖反射私有字段的测试工具可能因此失效。**cgroup**:Android 用 cgroup v1/v2 把后台进程塞进受限的 CPU 与 I/O 带宽分组,前台流畅的代价由后台任务承担。**BroadcastQueue**:有序广播按接收者串行派发,某个接收者的 `onReceive` 超时会拖累整条队列。**ContentProvider**:它在 `Application.onCreate` 之前就可能被拉起,是冷启动的隐形税,自动初始化框架滥用它会显著拖慢启动。底层边界上,**VNDK**把厂商库与系统库分开避免耦合腐烂,**SELinux**用强制访问控制堵住越权访问,**Bionic**是 Android 的 C 库(体积与线程开销都比 glibc 小)。

\[FIG ai_layers.png\|图 2 Android 五层架构与本专栏的覆盖范围\]

> 提示:本篇是全书的地基。后续每一篇出现「AMS」「SurfaceFlinger」「ART」时,都可以回到这张五层图确认它住在几楼、和谁做邻居------定位问题的第一步永远是先猜楼层。

第 2 篇 渲染系统:一帧的旅程

2.1 主线:从 VSync 到上屏

你在屏幕上看到的每一帧,都要走完一条流水线。**VSync**(垂直同步信号)由显示系统按刷新率节拍发出(60 Hz 屏每 16.7 ms 一次,120 Hz 每 8.3 ms 一次);应用的 **Choreographer** 收到信号后回调 `doFrame`,驱动 `ViewRootImpl` 遍历视图树并把绘制指令录成 **DisplayList**;接着 **HWUI 的 RenderThread** 把这些指令翻译成 OpenGL/Vulkan 命令交给 **GPU** 真正光栅化;画好的缓冲区通过 **BLAST** 缓冲队列提交给 **SurfaceFlinger**;SurfaceFlinger 收集所有窗口的缓冲区,决定哪些交给 **HWC(硬件合成器)**、哪些自己用 GPU 合成,最终送到 **Display** 上屏。整条链是经典的生产者---消费者模型:应用生产,SurfaceFlinger 消费并合成,HWC 与面板呈现。

2.2 四个时间边界与 FrameTimeline

原书给渲染链路标了**四个时间边界**,堪称卡顿定位的坐标系:`vsync-app`(应用侧帧信号到达的时刻)、`vsync-sf`(合成器侧帧信号到达)、`present`(画面真正扫描出来的时刻)与 `actual`(实际呈现)。Android 12 起系统把每个应用帧的**期望时间线(Expected Timeline)**与**实际时间线(Actual Timeline)**记录在 **FrameTimeline** 里:期望是「这帧本该几点开始、几点上屏」,实际是「它真的几点被提交、几点被呈现」。两线一对比,帧被推迟在哪一段立刻现形------是应用主线程晚了(Expected 起点就偏),还是 GPU/合成器堵了(Actual 终点偏)。Perfetto 的 FrameTimeline 轨道就是把这组数据可视化,第 11 篇会用它实操。

2.3 BufferQueue、Gralloc 与 Sync Fence

缓冲区在这条链上不是裸奔的,它有三件配套行李。**BufferQueue** 是生产者与消费者之间的定长队列,应用 `dequeue` 一块缓冲区来画、`queue` 回去待合成,SurfaceFlinger 再 `acquire` 取用、`release` 归还,四个状态流转全在 trace 里有迹可循;队列满了应用会阻塞等待,这是「 Surface 卡住」类问题的直接现场。**Gralloc**(图形内存分配器,HIDL 接口)负责分配这些缓冲区,大小、格式、是否可被硬件合成器直读都影响性能。**Sync Fence** 是跨硬件的「完工信号」:GPU 还在画时 fence 不触发,SurfaceFlinger 拿到带 fence 的缓冲区后原地等待而非空转------它让 CPU、GPU、显示控制器三台节奏不同的机器异步协作。排查「SurfaceView 黑帧」「视频抖动」类问题时,fence 的等待时长往往是突破口。

2.4 HWC:谁来做合成

一台屏幕上通常叠着多层内容:状态栏、应用窗口、输入法、视频 Surface。把它们合成最终画面有两条路:**CLIENT 合成**由 SurfaceFlinger 调 GPU 逐层混画,耗 GPU 电但什么都能画;**DEVICE 合成**由 HWC 芯片的 Overlay 硬件平面直接叠加,几乎不耗 GPU。系统会优先把「规规矩矩」的层(不旋转、不透明、格式受支持)分给硬件平面,剩下的交给 GPU。性能含义有二:其一,层数太多或某层带复杂变换(圆角、透明度动画)会导致**合成降级**,GPU 负担陡增,表现为刷微博时电量肉眼可见地掉;其二,`dumpsys SurfaceFlinger` 里能看到每层走了 DEVICE 还是 CLIENT,是渲染优化的必查项。Android 的 HDR 显示、帧率切换(90/120 Hz)也都由 HWC 与面板能力共同决定。

2.5 帧预算、帧率与 TaskSnapshot

帧预算随刷新率收紧:60 Hz 约 16.7 ms,90 Hz 约 11.1 ms,120 Hz 约 8.3 ms------**帧率翻倍不是「更流畅的奖励」,而是「工期减半的考核」**。高刷屏普及后,「主线程干同样的活」也会掉帧,这是近几年流畅性话题变难的根本原因。另外两件事值得记:一是**Frame Pacing**(帧节奏),游戏和视频场景不仅要看单帧多快,还要看帧间隔是否均匀,忽快忽慢比稳定地略慢更难受;二是 **TaskSnapshot**,系统给最近任务里的每个 Activity 存了一张启动快照,冷启动时先展示快照再渐显真实内容------它让「秒开」的感觉成立,但也解释了为什么冷启动测量必须区分「看到画面」和「可以交互」。

2.6 渲染性能的三个观测口

机制讲完,落到「怎么看」。渲染性能有三个由近及远的观测口。**开发者选项的 GPU 呈现模式**(俗称「柱状图」)把最近几百帧按耗时画成条形,红色超标一眼可见,适合在真机上做最粗的定位;**`dumpsys gfxinfo <包名> framestats`**输出最近 120 帧的逐帧分解(绘制、准备、交换各阶段毫秒数),能区分「应用画得慢」还是「合成等得久」;**SurfaceFlinger 侧的 `dumpsys SurfaceFlinger`**则站在合成器视角,列出每个 layer 的尺寸、变换与合成方式(DEVICE 还是 CLIENT),层数爆炸与合成降级在这里无处遁形。三个口各有盲区:柱状图无归因、gfxinfo 只看应用、SurfaceFlinger 不见应用内部------所以第 11 篇的 Perfetto 与 FrameTimeline 才是终局答案,这三个口是抓 trace 之前的热身。

\[FIG ai_render.png\|图 3 一帧的旅程:VSync 到上屏的流水线与时间边界\]

> 注意:动态刷新率(ARR,见第 3 篇)普及后,VSync 间隔会随内容变化,分析时不要再默认每帧 16.67 ms------先确认实际刷新率,再算帧预算,否则 jank 判定会整体漂移。

第 3 篇 输入系统:从触摸到响应

3.1 一条八站的传送带

手指碰到屏幕之后,事件要穿过一条清晰的传送带:**触摸屏驱动与 evdev** 在内核里产生原始事件;**EventHub** 从 `/dev/input` 节点批量读取并做设备管理;**InputReader** 把原始采样转换成标准化的 `MotionEvent`/`KeyEvent`,并处理校准、多点分离;随后事件进入**监听器队列**做逐层加工(下一节细讲);**InputDispatcher** 为事件找到目标窗口、处理焦点与 ANR 计时;经 **InputChannel/InputTransport** 这条进程间管道送达目标进程;应用的 **InputEventReceiver**(跑在主线程的 Looper 上)接收并分发给 **ViewRootImpl**,最终落到 DecorView 与你的 `onTouch` 回调。

3.2 Android 17 的监听器顺序

Android 17 上,InputReader 与 InputDispatcher 之间的事件加工队列是固定的七站:`InputReader → UnwantedInteractionBlocker → InputFilter → PointerChoreographer → InputProcessor → Metrics 与 InteractionReporter → InputDispatcher`。各自分工:**UnwantedInteractionBlocker** 做手掌误触拒绝;**InputFilter** 是策略过滤器(无障碍、手势拦截都在这里);**PointerChoreographer** 把原始输入升格成指针事件并做显示坐标侧的 choreography;**InputProcessor** 做动作分类(原书特别澄清:旧资料里的 `InputClassifier.cpp` 在 `android-17.0.0_r1` 已不存在,分类逻辑在 `InputProcessor.cpp`,且**不是**「所有输入都经过 AI 分类」);Metrics 与交互报告器收集遥测(部分构建可能省略)。无障碍服务、输入法、手势导航的怪异行为,多数能在这个队列里找到对应的一站。

3.3 六个时间边界:别把「收到回调」当成「响应完成」

端到端输入延迟要标出**六个时刻**:①硬件或内核产生事件;②EventHub 与 InputReader 读取转换;③InputDispatcher 选好目标并发出;④目标进程的 InputConsumer/InputEventReceiver 收到;⑤应用处理完并提交包含视觉变化的缓冲区;⑥SurfaceFlinger 与 HWC 完成呈现。两个最常见的测量误区都在这里:`MotionEvent.getEventTime()` 接近第①时刻,拿它到自己的回调结束做差,只覆盖了「输入到应用」,**不含渲染与上屏**;反过来,onTouch 里改完 UI 就宣布「响应完成」,其实第⑥时刻还没发生。MOVE 事件还会被批量传输、在 VSYNC 前夕由 ViewRootImpl 统一消费------批处理省线程唤醒,却改变每个采样点的消费时刻,做重采样与预测坐标分析时要分清原始采样时间、预测目标时间与呈现时间。

3.4 新面孔与背压

近几个版本,输入系统有几张新面孔值得记住。**Rust 编写的 InputFilter** 用内存安全语言重写了过滤管线,内置防连击(bounce keys)、慢键(slow keys)、粘滞键(sticky keys)等无障碍能力,是 Android 系统组件 Rust 化的代表案例。**Predictive Back**(Android 13 引入、14 默认手势)把返回手势做成「可预览、可取消」:手指滑动过程中系统持续回调预览动画,松手才提交------它要求动画能前进也能后退,给应用动画设计立了新规矩。**ARR(Adaptive Refresh Rate,自适应刷新率)**在显示时序侧调整 VSYNC 间隔,输入链路的测量基准随之浮动。**InputDispatcher 的背压**机制则负责讨债:应用主线程迟迟不消费输入,dispatcher 会停止派发并可能触发输入类 ANR(第 8 篇展开)。

3.5 输入设备家族与派生事件

传送带上的「货物」不止手指一种。**触摸**是绝对主力:多点触控由 InputReader 做多点分离与跟踪,MOVE 事件携带批处理采样,速度估算交给 `VelocityTracker`(惯性滑动的物理来源);**手写笔**走更讲究的协议,支持压感、倾斜与悬浮(hover)事件,低延迟笔迹还依赖显示侧的预测与相位对齐;**键盘与鼠标**(实体或蓝牙)在 Android 上同样有完整支持:按键有按下、重复、抬起三态,焦点窗口决定谁接收;**游戏手柄**与旋转方向盘把传感器语义翻译成标准按键。**输入法(IME)**是特殊一站:它不产生原始输入,而是作为输入系统的「座上宾」接管文本框的输入流------InputMethodManager 与 InputDispatcher 之间有专门的焦点与注入通道,「输入法弹起把界面顶歪」「候选词卡顿」这类客诉,责任边界要在这层划分。PointerChoreographer 负责的指针图标(触摸点、光标形态)也是这条链的末端产品。一句话:输入系统不是一个「触摸屏驱动」那么简单,它是多种设备、多个消费者的总调度室。

\[FIG ai_input.png\|图 4 输入传送带:从内核到应用回调的八站与六个时间边界\]

> 提示:排查「触控不灵」类客诉时,先在六个时刻里定位:慢在③④之间多为系统调度或跨进程延迟,慢在⑤是应用主线程,慢在⑥是渲染链路。分段计时永远比只测端到端总分有用。

手势导航时代还带来一个看不见的输入消费者:Predictive Back 返回预测。手指从屏幕边缘往回拖的一瞬,系统就把「可能要返回」通知给应用,应用提前渲染返回动画------输入管线的第一段感知,直接决定了转场的跟手度。

第 4 篇 内存管理:七块田与回收队

还有一个常被漏掉的输入源:**输入法(IME)**。软键盘本身也是 InputReader 的客户端------按键先变成 KeyEvent 进输入管线,再由 IME 翻译成 commitText 走 InputConnection 进应用,是「输入系统」与「编辑系统」的接缝,卡顿常发生在接缝处。游戏手柄与电视遥控则是多输入源并存的代表:一套代码要同时容忍触摸、键、轴三类事件,派生事件的统一坐标系就靠 PointerChoreographer 这层换算。

4.1 先统一口径:内存的七种「田」

Android 谈内存,第一件事是分清口径------同一块 RAM 在不同报表里名字不同。原书按内存域(memory domain)划分:**Java Heap** 是 ART 管理的对象堆;**Native Heap** 是 C/C++ 代码 `malloc`/`new` 出来的;**Graphics** 是图形缓冲区(Gralloc/dma-buf,GPU 与应用共享);**Code** 是代码与库映射;**Stack** 是线程栈;**System** 是系统分配;剩下归 **Unknown**。统计口径上:**VSS** 是进程虚拟地址空间(含未驻留的,虚胖);**RSS** 是实际驻留物理内存(共享库在每个进程重复计);**PSS** 把共享页按使用人数均摊(跨进程加总最合理);**USS** 是进程独占部分;**SwapPss** 是被换出到 zram 压缩区的部分。看内存请先问「哪个域、哪个口径」,否则「降了 200 MB」可能是把共享库从 RSS 算成了 USS 的统计魔术。

| 口径 | 一句话含义 | 什么时候用 |

|---|---|---|

| Java Heap | ART 管理的 Kotlin/Java 对象 | 泄漏、GC 抖动 |

| Native Heap | C/C++ 原生分配 | 图片解码、第三方 so |

| Graphics | 图形缓冲区(可跨进程共享) | 渲染、相机、视频 |

| PSS | 均摊共享页后的物理占用 | 跨进程比较、线上上报 |

| SwapPss | zram 压缩交换部分 | 低内存设备的真实压力 |

4.2 低内存杀手:lmkd、PSI 与回收链路

当内存吃紧,Android 有一条完整的「减负流水线」。用户态的 **lmkd**(低内存守护进程)依据内核的 **PSI(Pressure Stall Information,压力停顿信息)**判断系统是否卡顿,再按进程的 `oom_score_adj`(越后台分越高)从最不重要的开始杀:先杀缓存进程,再杀服务,绝不动前台。内核侧则在杀进程之前先自救:**kswapd** 后台线程异步回收页缓存,回收不及时就由触发分配的线程自己**直接回收(direct reclaim)**------这就是卡顿现场里「主线程莫名卡在内存分配」的来源;**内存整理(compaction)**收拾碎片好分配大块连续内存;**zram** 把不活跃页压缩后进交换区,小内存设备的救命稻草但吃 CPU。**LMK + zram + 后台限制**共同构成 Android「不死机但会杀后台」体验的政策根源,也解释了为什么「内存够用」的旗舰机照样杀后台------那是策略,不是 bug。

4.3 缓存进程与冻结

被杀之前,后台应用还有两级缓冲。第一级是**缓存 LRU**:退到后台的进程变成 cached 进程挂在 LRU 表上,内存紧张时被 lmkd 收割,所以你切回微信偶尔要「重新启动」。第二级是 **Cached App Freezer**(应用冻结,Android 11 引入):把 cached 进程整个冻进 cgroup freezer,不再消耗 CPU 调度------它省电,但给「推送延迟」「后台任务不执行」的客诉提供了新来源:任务没被杀,只是被冻僵了,解冻要靠下一次交互触发。Android 14 起对缓存进程数量也加了更严的限制,后台保活的传统手艺(双进程互拉、前台服务伪装)在新版本上基本失效。

4.4 新硬件规则:16KB 页、MTE 与终结者线程

两条硬件级规则正在改变内存问题形态。其一,**16 KB 页**(Android 15 起支持、新设备普及):系统页大小从 4 KB 提到 16 KB,同样的数据占用页数变少、TLB 命中率提高,但**未按 16 KB 对齐编译的原生库会直接装不上**------纯 so 的应用要重建,混编工程要逐库核对。其二,**MTE(Memory Tagging Extension,内存标签扩展)**:Armv9 用硬件标签捕捉越界与释放后访问,是 Native 内存安全的「测谎仪」级工具,配合 GWP-ASan 的抽样保护,让一类过去只能靠玄学复现的野指针错误有了确定性现场。最后补一个 Java 世界的坑:**FinalizerDaemon** 是 ART 跑 `finalize()` 的守护线程,依赖终结器释放资源(文件句柄、Bitmap 时代的像素)既慢又不可靠------终结器队列一积压,内存看着占用高、对象却「死不透」,现代代码应一律改用 try-with-resources 或 Cleaner 显式管理。

4.5 内存的观测口

分清了田与账,还得知道田在哪看。系统侧:**`dumpsys meminfo <包名>`**按域(Java/Native/Graphics......)输出进程的 PSS 明细,`dumpsys meminfo` 不带包名则给整机总览与 lmkd 阈值;**`ActivityManager.getMemoryInfo()`**给应用一个「系统还剩多少」的粗信号,`onTrimMemory()` 回调则让应用按级别(UI 隐藏、RUNNING_LOW、RUNNING_CRITICAL)主动释放缓存------用好它是免费的体面。**procfs 一族**(`/proc/self/status` 的 VmRSS、`smaps` 的逐映射明细)适合脚本化采样;logcat 里 lmkd 的 `Kill` 记录直接告诉你「谁、何时、因为什么分被杀」。应用内:**Android Studio Profiler** 实时曲线、heap dump 看 Java 域;**heapprofd**(第 11 篇)采样 Native 分配。内存监控的黄金组合是「域报表 + 退出原因 + 压力信号」三件套:知道自己占多少、上次怎么死的、系统紧不紧。

\[FIG ai_memory.png\|图 5 内存的七种「田」与低内存减负流水线\]

> 注意:「内存大」不等于「内存有问题」。排查时先看 SwapPss 与 direct reclaim 事件:SwapPss 高说明真实压力大,direct reclaim 出现在主线程说明分配被卡------这两组信号比单纯的 PSS 曲线更有诊断价值。

相关推荐
hai_android5 小时前
Android 嵌套滑动冲突:内部拦截 vs 外部拦截实战
android·android studio·android jetpack
木子欢儿7 小时前
Zabbix 7.4 Alpine 版 Docker Compose 部署
android·运维·docker·容器·zabbix
素师良码8 小时前
第9篇:Android驱动踩坑实录:DSI RX FIFO前置脏0、数据错位复盘
android
夜雪一千8 小时前
如何在MySQL中实现字段级加密
android·数据库·mysql
夜雪一千8 小时前
MySQL 如何存储经纬度坐标:选型、建表、空间查询实战
android·mysql·adb
JMchen8 小时前
集成第三方 C/C++ 库到 Android NDK 项目:OpenCV 与 FFmpeg 实战指南
android·opencv·cmake
有毒的教程10 小时前
Dev-C++ 使用扩展库两种方式:DevPak一键安装 / 手动配置(重点)
android·c++·adb
mmsx10 小时前
Android 测绘数值基础:测量单位系统与七四参数、高程拟合
android
郑州光合科技余经理10 小时前
出海外卖平台:支付对接为什么要和下单收款分开
android·java·开发语言·前端·后端·php·ai编程