ANDROID · SDK · ENGINEERING

Android 智能硬件 SDK技术 Wiki

Android 应用开发、设备通信与软硬件一体 SDK 技术笔记

72 个技术主题12 个专题章节4 组代码与设计练习
学习重点与阅读说明

本文档围绕 Android 基础、Kotlin 与 Jetpack、网络通信、BLE 与 Wi-Fi 配网、SDK 设计和性能稳定性展开。阅读目标是理解基础机制,掌握方案取舍,并通过工程案例和运行证据验证结论。

阅读按三条主线展开:Android 基础与异步机制、性能问题的定位闭环、设备通信 SDK 的状态与异常管理。座舱多屏交互和冷启动优化作为贯穿案例;Kotlin、MVVM 和设备通信结合小型验证项目理解。

文档包含 72 个技术主题、4 组代码与设计练习、工程案例、综合场景和两天学习安排。P0 表示优先掌握,P1 表示在基础稳定后补齐;优先级依据知识依赖与工程场景设置。技术版本核对日期为 2026 年 9 月 22 日。

先掌握 P0,再补齐 P1

01
阅读方法

知识结构与阅读方法

技术领域与实践要求

技术领域实践要求优先阅读
软硬件一体 SDK 开发与维护API 清晰,状态正确,异常可恢复,集成成本可控Q40 至 Q56
四大组件与 Android 基础生命周期、线程、进程、View 机制说得准确Q01 至 Q24
Jetpack Kotlin MVVM能说明状态归属与生命周期,能读写关键代码Q25 至 Q32
OkHttp Retrofit JSON会区分分层职责,处理超时、鉴权和弱网Q33 至 Q39
BLE Wi-Fi Socket能讲完整通信链路,区分连接成功与业务成功Q40 至 Q49
启动 卡顿 内存 功耗优化能从指标和证据定位原因,并验证收益和副作用Q57 至 Q64
兼容性 线上稳定性 团队协作能设计复现矩阵、观测信息、灰度和回滚Q55 至 Q56 及 Q65 至 Q72

怎样理解一个技术主题

先明确结论,再理解关键机制,最后检查边界和工程场景。例如学习屏幕旋转时的生命周期,先确认旧实例销毁、新实例创建的顺序,再分析重建原因以及数据保存方式。

每个主题分三遍阅读:第一遍看“核心结论”,建立概念;第二遍理解机制和反例;第三遍结合“扩展说明”,用一个具体场景或小段代码验证。不要仅凭读懂文字判断是否掌握。

工程实践背景

贯穿案例来自华为 OD 鸿蒙智能座舱应用开发中的多屏互动模块,主要涉及 Android 原生应用设计与交付,主力语言是 Java。冷启动专项通过 Perfetto 分析主线程、首帧和初始化依赖,结合异步 Inflate、任务后台化、按需加载和初始化时序调整,将响应时延从 380ms 优化到 240ms,启动完成时延从 1200ms 优化到 700ms。案例重点是复杂场景下的性能分析、模块设计和系统联调方法。

指标定义与验证边界见 Q65。Kotlin、BLE 和 Wi-Fi 配网的实验结论应标明验证条件,区分文档学习、小型实验与生产环境中的实际结果。

建议的实践顺序

第一,能够画出“界面事件、ViewModel、Repository、设备 SDK、设备回包、状态更新”的完整路径。第二,能够解释 BLE 从扫描到业务就绪的全过程,以及 TCP 为什么需要组帧。第三,为优化案例记录测量起止点、定位证据、方案取舍与回归结果。

02
8 个技术主题

四大组件与生命周期

Q01四大组件分别负责什么P0
核心结论

Activity 承载用户界面和交互入口;Service 承载不依赖某个页面的工作或对外提供绑定接口;BroadcastReceiver 接收广播事件;ContentProvider 通过统一接口提供数据访问与共享。四大组件都不是“天然的后台线程”。

机制说明:进程负责隔离与资源承载,线程负责调度执行,组件是 Android 管理应用行为的入口。普通 Service 的生命周期回调默认在主线程;Provider 的远程访问接口可能在 Binder 线程并发执行,需要同步保护。

扩展说明:Service 一定是独立进程吗?不是,默认与应用其他组件处于同一进程;只有相应进程配置等条件才会改变。做 BLE SDK 也不必为了“后台”强行新建进程。

Q02Activity 从打开到关闭的生命周期P0
核心结论

典型首次打开是 onCreate → onStart → onResume。完全进入后台通常是 onPause → onStop。未销毁时重新回来通常是 onRestart → onStart → onResume。正常 finish 时一般经历 onPause → onStop → onDestroy;系统直接杀进程时不能依赖 onDestroy 一定执行。S01

机制说明:onCreate 做一次性页面初始化;onStart 对应可见阶段;onResume 对应前台交互阶段。生命周期回调表示状态变化,不是精确的屏幕呈现时间点。onResume 返回也不代表首帧已经显示。

扩展说明:A 打开一个不透明 B 时呢?典型顺序是 A.onPause,B.onCreate、onStart、onResume,然后 A.onStop。透明页面、多窗口等场景可能不同,不能把单一场景当成所有窗口的规则。

Q03旋转屏幕或配置变化时具体发生什么P0
核心结论

默认需要重建且旧页面处于前台时,旧 Activity 通常执行 onPause → onStop → onDestroy;新实例执行 onCreate → onStart → onResume。系统可能保存并恢复状态;onSaveInstanceState 的精确相对时机与版本等条件有关,不应背成一条所有版本都相同的固定顺序。

机制说明:系统需要根据新的尺寸、方向、语言、密度等配置重新选择资源。若正确声明并自行处理相关 configChanges,可能收到 onConfigurationChanged 而不为这些变化重建,但应用必须自己完成资源和布局适配。

扩展说明:跨屏一定重建吗?不一定,取决于配置差异、应用声明和系统策略。即使没有重建,窗口尺寸和布局约束变化也会触发重新测量与布局,所以画面仍可铺满新屏幕。

Q04四种常见启动模式有什么区别P0
核心结论

standard 通常新建实例;singleTop 仅在目标实例已处于目标任务栈顶时复用;singleTask 在匹配的任务与实例条件下复用,并通常移除其上方 Activity;singleInstance 具有更强的任务隔离约束。复用时新 Intent 通常交给 onNewIntent。S02

机制说明:启动模式解决的是实例和任务栈行为,实际结果还受 Intent flags、taskAffinity、启动来源等影响。FLAG_ACTIVITY_NEW_TASK 参与任务选择,不等于“开新线程”,也不保证降低启动时延。较新平台还有 singleInstancePerTask,可作为扩展了解。

扩展说明:onNewIntent 里直接读取 getIntent 可以吗?如果后续代码要从 getIntent 读取新参数,应调用 setIntent(intent) 或明确使用传入参数,避免继续拿旧 Intent。

Q05startService 和 bindService 有什么区别P0
核心结论

startService 建立启动状态,适合独立于某次绑定而持续执行的工作;bindService 让客户端获得服务接口并建立绑定关系,适合交互式调用。只有绑定状态的服务在最后一个客户端解绑后通常会被销毁;同时被启动的服务,还需要结束启动状态。S03

机制说明:首次启动一般是 onCreate → onStartCommand,多次启动可多次调用 onStartCommand。首次绑定一般是 onCreate → onBind,解绑涉及 onUnbind。两种使用方式可以组合,生命周期要一起判断。bindService 不等于跨进程,跨进程时才涉及相应 IPC。

扩展说明:长时间设备连接用什么?看业务是否需要用户可感知的持续任务,再考虑符合限制的前台服务或伴生设备能力。普通 Service 不保证进程常驻;前台服务也要满足启动条件、类型和权限要求。S04

Q06BroadcastReceiver 为什么不能直接做长任务P1
核心结论

广播接收器适合快速处理事件,不适合在 onReceive 中执行长时间 I/O。默认回调通常在主线程,耗时操作既可能阻塞界面,也可能超过广播处理时限。goAsync 可以在回调返回后短暂完成工作,但仍有时限,必须调用 PendingResult.finish。

机制说明:“切到后台线程”不等于广播任务就能无限运行。可延迟、要求最终完成的任务考虑 WorkManager;实时且持续的任务根据系统后台规则选择合适机制。动态注册要与拥有者生命周期对应,跨应用广播还需考虑导出策略和权限校验。

扩展说明:为什么退出页面后还收到广播?可能注册对象的生命周期比页面长,或没有注销。先确认接收者拥有者,再检查注册与清理是否成对。

Q07ContentProvider 为什么会拖慢启动P0
核心结论

进程启动时,系统可能在 Application.onCreate 之前安装并初始化该进程声明的 Provider。第三方 SDK 如果在 Provider.onCreate 做数据库、磁盘或复杂初始化,就会拉长启动关键路径。

机制说明:处理顺序是查合并后的 Manifest,确认哪个 SDK 注入 Provider,用 Trace 验证开销,再查它是否支持关闭自动初始化和显式初始化。AndroidX App Startup 能管理初始化依赖,但具体初始化工作仍需控制成本。S05

扩展说明:可以删掉 Provider,再自己 new 一次吗?不能默认这么做。Provider 的系统注册、Context、权限和生命周期不是普通对象初始化;只有供应方支持的接入方式才可靠。不能臆造一个 SDK.init 方法代替它。

Q08Context 为什么容易引发内存泄漏P0
核心结论

Activity Context 与页面生命周期关联。单例或长期任务持有它,可能让页面及整棵 View 树在退出后仍然可达。长生命周期对象在适用时使用 Application Context,确实需要窗口和主题的 UI 操作则使用对应页面 Context。

机制说明:不是把所有 Context 都替换成 Application 就结束了。Dialog、主题相关布局和窗口操作具有自己的要求;SDK 应区分纯业务能力和 UI 能力,避免为通信保存整个 Activity。

扩展说明:WeakReference 能彻底修复吗?不能。它不能替代取消任务、移除监听、关闭连接等资源管理;对象随时可能消失,也会让代码更复杂。

03
8 个技术主题

View 绘制与触摸事件

Q09View 的 measure layout draw 分别做什么P0
核心结论

measure 决定测量宽高,layout 决定实际位置与边界,draw 把内容绘制出来。父容器向下传约束,子 View 在约束内给出尺寸,父容器再安排子 View 的位置。自定义 View 常重写 onMeasure 和 onDraw,自定义 ViewGroup 还要安排子项测量与 onLayout。

机制说明:measure 与 layout 不必永远只执行一次。复杂嵌套、权重或约束可能增加测量次数。View 在构造函数和 onCreate 中可能尚未完成布局,直接读 getWidth 往往得到 0。

扩展说明:何时获取稳定尺寸?根据用途用 onSizeChanged、布局监听等,并处理后续尺寸变化。post 常能延后执行,但不应把它当作所有场景“尺寸一定就绪”的严格保证。

Q10MeasureSpec 三种模式怎么理解P0
核心结论

EXACTLY 表示父容器指定尺寸;AT_MOST 表示不能超过给定上限;UNSPECIFIED 表示本轮测量没有给出同类上限约束。MeasureSpec 同时包含模式和大小,不能只读取大小而忽略模式。

机制说明:wrap_content 不等于自动知道内容宽高。自定义 View 要计算期望尺寸,加上 padding,再结合父约束调用 resolveSize 等方法。match_parent 也不能脱离父容器当前的测量条件简单理解。

扩展说明:自定义进度控件 wrap_content 占满父布局怎么办?检查是否直接使用父容器提供的最大尺寸,忽略了自己的期望尺寸;同时检查最小尺寸和 padding 的计算。

Q11invalidate 和 requestLayout 的区别P0
核心结论

invalidate 表示内容需要重绘;requestLayout 表示尺寸或布局可能变化,需要重新评估测量和布局。例如只改进度条颜色通常重绘即可,改文本大小或布局尺寸通常需要重新布局。

机制说明:频繁 requestLayout 会增加布局工作,但影响范围取决于具体 View 树和框架优化。大量小更新应考虑合并。setSelected、setMargins 等涉及已使用中的 UI 状态和布局变更,应在主线程完成;修改 LayoutParams 后要通过正确途径触发布局更新。

扩展说明:子线程能否画 UI?可以后台计算数据、生成适合离屏处理的结果,但不能任意并发修改页面 View 树。postInvalidate 也只是安排重绘,不让其他 View 操作自动变安全。

Q12一个自定义 View 怎样做得正确又流畅P0
核心结论

先定义尺寸与交互契约,再实现测量、绘制和触摸处理;避免在 onDraw 中反复创建对象或执行 I/O。画笔、路径和尺寸相关结果在合适时机复用,尺寸变化时再重新计算。

机制说明:以圆形设备进度条为例:onMeasure 处理 wrap_content;onSizeChanged 计算圆心和半径;onDraw 画背景轨道与进度;setProgress 更新值后重绘。还要处理 padding、颜色状态、无障碍描述和恢复状态。

扩展说明:为什么手指操作了却没有点击语义?自己处理触摸时,确认在符合点击条件后调用 performClick,并正确处理 ACTION_CANCEL、多指切换和移动阈值。

Q13触摸事件是怎样分发的P0
核心结论

在典型 View 层级中,事件从窗口进入 Activity,再沿 ViewGroup 向目标子 View 分发。dispatchTouchEvent 负责调度;ViewGroup 的 onInterceptTouchEvent 决定是否拦截给自己处理;onTouchEvent 负责处理事件。返回 true 通常表示在该层接受或消费事件。S06

机制说明:一次手势通常从 DOWN 开始,经多个 MOVE,到 UP 或 CANCEL 结束。子 View 在 DOWN 阶段接住事件,通常才能成为后续事件的目标。父容器中途拦截时,原目标子 View 会收到 CANCEL。

扩展说明:OnTouchListener 和 onTouchEvent 谁先?在满足启用等条件时,监听器可先处理;监听器返回 true 后通常不会再进入该 View 的 onTouchEvent。不要让监听器无条件消费所有事件,导致点击逻辑失效。

Q14横向滑动控件放进纵向列表怎样处理冲突P0
核心结论

先用移动距离和方向判断用户意图,再决定由父容器还是子控件处理。父层可在 onInterceptTouchEvent 中按方向拦截;子层也可在合适时机调用 requestDisallowInterceptTouchEvent,协调父层拦截行为。

机制说明:不能只比较某一次 MOVE 的绝对值,还要考虑 touchSlop、多指、边界和 CANCEL。支持嵌套滚动的控件可通过 NestedScrolling 机制分配滚动距离及 fling,通常比强行禁止所有父层拦截更稳。

扩展说明:子控件到边缘后怎么让父容器接手?需要定义边界交接规则,并正确结束或转交当前手势,不能让两个控件同时认为自己独占同一段事件。

Q15常见 ViewGroup 与 RecyclerView 怎样选择P1
核心结论

FrameLayout 适合简单叠放;LinearLayout 适合单方向线性排列;ConstraintLayout 适合用约束表达较复杂的相对关系;RecyclerView 适合可滚动、可复用的集合。选型看结构和实际测量成本,不按“某容器永远最快”下结论。

机制说明:RecyclerView 复用 ViewHolder 减少重复创建,但 onBindViewHolder 仍然可能很重。图片缩放和解码、列表差异计算、局部刷新、避免绑定时同步查询,都影响流畅性。DiffUtil 要正确区分“同一条数据”和“内容相同”。

扩展说明:快速滑动时图片错位为什么?异步请求返回后,ViewHolder 可能已经绑定其他条目。应取消旧请求或验证当前绑定标识,并在绑定时清理旧展示状态。

Q16VSync 不停发生 应用为什么不会一直 doFrameP0
核心结论

显示系统有自己的刷新节奏,应用则在存在待处理帧工作时通过 Choreographer 等机制申请帧调度。典型 doFrame 在主线程处理输入相关回调、动画、遍历等工作;静止且没有待处理帧任务时,不需要每个刷新周期都执行一次完整绘制。

机制说明:60Hz、90Hz、120Hz 的周期约为 16.67ms、11.11ms、8.33ms,但具体帧截止时间还要结合流水线和系统调度分析。doFrame 结束不是像素已经出现在屏幕上,之后还涉及 RenderThread、GPU、合成和呈现。S07

扩展说明:主线程空闲就一定不卡吗?不一定,还可能是 RenderThread、GPU、SurfaceFlinger 或调度链路问题。定位时关联 FrameTimeline 与实际线程活动,不能只盯一个 doFrame 耗时。

04
8 个技术主题

Java 并发与异步机制

Q17Handler 的工作机制是什么P0
核心结论

Handler 将 Message 或 Runnable 放进关联线程的 MessageQueue;该线程的 Looper 循环取出到期任务,再交给对应 Handler 分发执行。任务在哪个线程执行,取决于 Handler 绑定哪个 Looper,不取决于是谁调用 sendMessage 或 post。S08

机制说明:Message 是任务载体,MessageQueue 管理任务及执行时机,Looper 驱动处理,Handler 负责投递和分发。主线程的 Looper 由框架准备;普通线程需要自己的消息循环才适合绑定 Handler。

扩展说明:主线程 Handler.post 一个耗时任务是不是异步了?它只是排队后执行,仍占用主线程。postDelayed 的延时是最早可执行条件之一,不保证在指定毫秒精确开始;前面的任务可能造成延误。

Q18Looper 的循环为什么不会把 CPU 跑满P0
核心结论

消息循环不是无休止执行空转代码。没有可执行消息时,底层会等待消息或相关事件,线程进入等待状态;新任务到来或等待时间到期后再被唤醒。真正的卡顿往往是某次任务执行太久,后续输入和绘制任务排队。

机制说明:判断主线程时要区分 Running、Runnable 与等待状态。Running 表示正在使用 CPU;Runnable 表示想运行但还没得到 CPU;等待可能是消息队列正常空闲,也可能是等锁、Binder 或 I/O。它们需要不同优化手段。

扩展说明:堆栈显示 nativePollOnce 就说明问题在那里吗?不一定,采样时主线程可能已经空闲。需结合问题发生时间和完整 Trace,避免把采样位置当作根因。

Q19同步屏障与 IdleHandler 分别解决什么问题P0
核心结论

同步屏障让队列中的同步消息暂时不能越过该位置,异步消息仍可被调度,框架借此协调部分高优先级工作。IdleHandler 则在消息队列暂时没有到期工作时执行空闲任务,回调仍在该 Looper 所在线程。

机制说明:这里的“异步消息”指队列调度属性,不代表另开线程。IdleHandler 在主线程执行时仍必须很短;繁忙状态下执行时机不确定,不能放首屏强依赖,也不能把 100ms 工作搬进去就宣称消除了主线程开销。

扩展说明:IdleHandler 返回 true 和 false 呢?true 表示保留,以便后续空闲时再次回调;false 表示执行后移除。要警惕持有 Activity、长期重复任务和遗漏清理。

Q20线程池怎样创建才合理P0
核心结论

根据任务性质、并发上限、队列容量、拒绝策略和生命周期设计线程池。CPU 密集任务避免线程数过多;阻塞 I/O 可以允许更多并发,但必须受到连接数、内存与下游承载能力限制。没有一个线程数公式适合所有 SDK。S09

机制说明:ThreadPoolExecutor 典型处理顺序是先增加到核心线程数,再尝试入队;队列满后才在最大线程数范围内扩容,最终可能拒绝。无界队列可能积压内存,也会让最大线程数难以发挥预期作用。

代码练习一 有界任务池:以下是机制示例,线程数和容量必须经实际压测决定。由 SDK 实例拥有并负责关闭,不要每次请求都创建新池。

Java
ExecutorService ioPool = new ThreadPoolExecutor(
    2, 4, 30, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(64),
    new ThreadPoolExecutor.AbortPolicy());
try {
    ioPool.execute(() -> performIo());
} catch (RejectedExecutionException e) {
    reportBusy(); // 按约定线程通知调用方
}
// 由拥有者在资源释放阶段调用 ioPool.shutdown()

扩展说明:CallerRunsPolicy 一定好吗?不一定。如果主线程提交任务,拒绝时就可能在主线程直接执行耗时任务。SDK 应有明确的忙碌错误或背压策略,而不是悄悄改变线程语义。

Q21volatile synchronized Atomic 有什么区别P0
核心结论

volatile 主要提供可见性和相关有序性,不能让 count++ 这种读改写整体变成原子操作;synchronized 提供互斥与可见性,适合保护一组必须一致的操作;原子类提供特定变量的原子更新,适合计数、标记和简单状态更新。

机制说明:多个变量之间有约束时,分别使用 Atomic 并不自动保证组合一致。例如“连接状态为 READY 时,session 必须存在”是整体约束,可以通过锁、不可变快照或单线程状态机维护。

扩展说明:设备 SDK 更倾向哪种并发设计?优先减少共享可变状态,把连接和命令状态集中到一个串行执行器;跨线程数据用不可变对象传递,再在必要处精确同步。

Q22死锁和数据竞争怎样排查P0
核心结论

数据竞争是多个线程在缺少正确同步时访问共享可变数据;死锁是线程之间形成循环等待而无法继续。排查时保留线程栈、锁拥有者和等待链,再结合日志与 Trace 还原发生顺序。

机制说明:常见危险是持有锁时执行同步 Binder、网络请求或外部回调。外部代码可能回调当前 SDK 或需要同一把锁,形成重入或交叉等待。更稳妥的是锁内只修改必要状态,复制结果后在锁外通知。

扩展说明:增加 synchronized 能修复所有并发 bug 吗?不能,锁范围过大可能把竞争变成阻塞或死锁。要先说清共享状态、所有者和允许的状态迁移。

Q23IPC Binder AIDL Parcel 分别是什么P0
核心结论

IPC 是进程间通信的总称;Binder 是 Android 常用的 IPC 机制;AIDL 用接口定义生成调用两端的适配代码;Parcel 是 Binder 等场景中组织传输数据的容器。不同进程的通信都属于 IPC,但不一定使用 Binder。

机制说明:远程同步 Binder 调用会等待服务端处理,主线程发起就可能阻塞 UI。远程服务端调用通常由 Binder 线程池处理;同进程本地接口调用可能直接在调用线程执行。AIDL 并不让线程安全问题自动消失。

扩展说明:自定义对象可以传吗?可以按接口支持的形式进行 Parcelable 等序列化设计,但不是任意 Java 对象都能原样发送。大数据不适合全部塞进事务,应考虑文件描述符、共享内存或分块协议。

Q24页面退出为什么后台任务仍然执行P0
核心结论

Activity 生命周期与普通线程、线程池任务的生命周期是两套机制。页面销毁不会自动停止你创建的任务。任务如果没有取消条件,还可能继续持有旧页面并回调已经失效的 View。

机制说明:为任务明确拥有者:页面级工作随页面或 View 生命周期结束,ViewModel 工作通常随对应 ViewModel 清理,设备会话工作随会话关闭。取消要传递到真正的底层请求或阻塞 I/O,并在结果回调前检查会话或请求是否仍有效。

扩展说明:Future.cancel(true) 就一定停了吗?不一定,它通常发出中断请求,任务和底层操作需要响应中断。对 Socket 等资源,还应按其 API 设计关闭和退出路径。

05
8 个技术主题

Kotlin Jetpack 与 MVVM

Q25Java 开发者应先掌握哪些 Kotlin 语法P0
核心结论

优先掌握可空类型、val 与 var、数据类、密封类型、扩展函数、lambda 和协程。重点是它们怎样表达状态与约束,而不是背语法糖。

写法或概念需要说清的含义易错点
val 与 var引用不可重新赋值与可重新赋值val 指向的可变对象仍可被修改
String? 与 ?.显式可空类型与安全调用平台类型和外部数据仍需校验
?: 与 !!空值备用分支与非空断言!! 可能直接触发空指针异常
== 与 ===结构相等与引用相同不要套用 Java 的 == 语义
data class自动生成值对象的常用方法copy 默认是浅拷贝
sealed 类型表达有限类别的状态或结果不是随意塞进所有业务分支
扩展函数给调用语法增加可读性按接收者静态类型解析 不是真正增加虚方法

扩展说明:Kotlin 能彻底消灭空指针吗?不能,!!、Java 平台类型、初始化问题等仍可能产生 NPE。它让空值约束更显式,但边界输入仍要验证。S10

Q26协程是不是线程 suspend 会自动切后台吗P0
核心结论

协程是可挂起、可恢复的任务,不等于一个独立线程。suspend 表示函数允许在协程中挂起,不表示自动切换后台线程,也不保证内部操作不会阻塞。执行线程由调度器与当前上下文等决定。S11

机制说明:Dispatchers.Main 常用于 UI,Default 用于 CPU 计算,IO 用于适合阻塞方式执行的 I/O。withContext 可切换执行上下文;delay 挂起协程而不占着线程睡眠,Thread.sleep 会阻塞当前线程。Retrofit 的异步挂起接口与自己写的同步文件读取要区别看待。

扩展说明:两个 suspend 调用顺序写出来会并行吗?通常不会,仍是顺序执行。需要明确的并发结构,例如 coroutineScope 中启动 async,再 await;同时定义失败与取消策略。

Q27协程怎样取消 怎样处理异常P0
核心结论

结构化并发让子任务受父作用域管理。取消通常是协作式的,挂起点或主动检查会响应取消;CPU 长循环要检查 isActive 或 ensureActive。捕获异常时应保留 CancellationException 的取消语义,不能把取消一律显示为“请求失败”。S12

机制说明:普通父子 Job 中,一个子任务失败可能影响同级任务;SupervisorJob 等机制改变失败传播关系,但不代表异常自动被处理。CoroutineExceptionHandler 是未处理异常的兜底机制,不是恢复当前失败协程的替代品。

扩展说明:callback API 包一层 suspend 就结束了吗?还要关联取消与底层 cancel,防止多次恢复,并处理“取消与回调同时到来”的竞争。长期事件流通常更适合 callbackFlow,并在 awaitClose 释放注册。

Q28ViewModel 到底解决什么问题P0
核心结论

ViewModel 用于保存屏幕级 UI 状态并组织相关业务逻辑。它让 Activity 或 Fragment 负责显示和接收操作,并在配置变化重建时保留同一作用域下的状态,避免无意义地重建页面数据。S13

机制说明:想象“设备列表页面”是显示器,ViewModel 是这块页面当前的工作台:正在加载、设备列表、错误信息、选中设备都可由它管理。旋转时显示器换了一个,工作台可以继续用。普通成员变量跟旧页面一起丢失;静态变量则边界过大,难清理、难隔离。

扩展说明:为什么不把所有数据都放 ViewModel?它不是数据库,也不是任意页面共享的全局仓库,更不负责直接持有 View。长连接归属和持久化应根据业务生命周期放在 Repository、会话管理或数据层。

Q29ViewModel 能跨旋转 跨页面 跨进程死亡吗P0
核心结论

配置变化时,正确通过 ViewModelProvider 等机制获取的 ViewModel 通常可以保留;跨页面是否共享取决于 ViewModelStoreOwner 的作用域;进程死亡后原 ViewModel 对象一定不在了,需要重建并恢复数据。

机制配置变化后系统回收进程后适合保存
普通页面字段通常丢失丢失临时显示对象
ViewModel同作用域下可保留原对象丢失页面状态与相关逻辑
SavedStateHandle可恢复满足系统状态恢复条件时可恢复小型参数 选中项 查询条件
Room 文件 DataStore可重新读取可重新读取需要持久保存的数据

扩展说明:SavedStateHandle 能替代数据库吗?不能,它依赖系统保存状态机制及任务恢复条件;用户主动结束任务等情况不能当作必然恢复。不要存大图、完整海量列表或活动连接,只保存重建所需的小型参数。S14

Q30MVVM 中每一层具体做什么P0
核心结论

View 显示状态并转发用户操作;ViewModel 把用户操作转为业务调用并产出 UI 状态;Repository 组织数据来源和访问策略;网络、本地数据库、设备 SDK 等执行具体数据操作。MVVM 的价值是明确职责和依赖方向,而不仅是创建几个同名类。

机制说明:点击刷新时,View 调用 refresh,ViewModel 更新为 Loading,Repository 获取设备数据,ViewModel 产出 Content 或 Error,View 根据状态渲染。View 不需要知道 BLE UUID,通信层也不需要知道 TextView。

扩展说明:Model 就是一个 Java Bean 吗?不应这样理解。广义 Model 包括业务数据与数据处理能力,实体类只是其中一部分。简单页面不必为了层数强行制造无用的转发类。

Q31LiveData StateFlow SharedFlow 怎样选择P0
核心结论

LiveData 是生命周期感知的可观察数据容器;StateFlow 适合表达有当前值的状态,订阅者能获得最新状态;SharedFlow 更适合可配置重放与缓冲的广播式流。Flow 还包括按收集启动的冷流,不能把所有 Flow 都当成同一类行为。S15

机制说明:View 页面收集 Flow 时要绑定生命周期。Fragment 通常使用 viewLifecycleOwner.lifecycleScope 和 repeatOnLifecycle,避免 View 销毁后继续更新旧绑定。StateFlow 会根据相等性合并状态,不能原地修改同一个可变对象后期待每次都发射。

扩展说明:SharedFlow 默认就能保证 Toast 事件不丢不重吗?不能。没有订阅者时的行为受 replay、缓冲等配置影响;导航、支付结果等重要结果更适合形成可确认的状态与消费协议,而不是只发一个瞬时事件。

Q32怎样写一个能解释清楚的 MVVM 示例P0
核心结论

状态只从 ViewModel 对外流出,UI 通过明确方法提交操作。下面用设备列表演示:阻止同时刷新,单独传播取消,错误转换为可显示状态,页面只负责渲染。Device 与 Repository 是业务接口,示例省略 imports 和 ViewModel Factory。

代码练习二 状态与生命周期:这是结构示例,需要已有 AndroidX Lifecycle、协程依赖以及业务模型。Repository.loadDevices 必须具备主线程可安全调用的挂起实现,阻塞 I/O 由其内部调度。

Kotlin
sealed interface DeviceUiState {
    data object Loading : DeviceUiState
    data class Content(val items: List<Device>) : DeviceUiState
    data class Error(val message: String) : DeviceUiState
}
class DeviceViewModel(private val repo: DeviceRepository) : ViewModel() {
    private val _state = MutableStateFlow<DeviceUiState>(DeviceUiState.Loading)
    val state: StateFlow<DeviceUiState> = _state.asStateFlow()
    private var refreshJob: Job? = null
    init { refresh() }
    fun refresh() {
        if (refreshJob?.isActive == true) return
        refreshJob = viewModelScope.launch {
            _state.value = DeviceUiState.Loading
            try {
                _state.value = DeviceUiState.Content(repo.loadDevices())
            } catch (e: CancellationException) {
                throw e
            } catch (e: Exception) {
                _state.value = DeviceUiState.Error("加载失败 请重试")
            }
        }
    }
}
// Fragment.onViewCreated 内 viewModel 已通过合适的 Factory 获取
viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.state.collect { state -> render(state) }
    }
}

扩展说明:停止收集后后台任务一定停止吗?不一定。这里暂停的是 UI 收集,viewModelScope 中已启动的刷新有自己的生命周期;是否取消取决于业务需求。退出并清理 ViewModel 时,其作用域会被取消。S16

Jetpack 常用组件的职责补充

组件解决什么问题使用边界
Lifecycle感知拥有者的生命周期状态监听和收集应绑定正确拥有者
Room为 SQLite 提供结构化访问支持持久化设备记录 注意迁移 事务与线程
WorkManager调度可延后且需要可靠执行的工作适合日志补传 不保证某秒立即运行 也不是长连接容器
DataStore保存配置或小型结构化数据不代替适合关系查询的大型数据库
Navigation管理页面导航与相关状态理解导航图与 ViewModel 作用域
Paging分页加载与展示大量数据不应一次把海量设备历史装进内存

Room 的事务保证一组数据库操作整体一致,不会自动覆盖外部设备动作。WorkManager 的“可靠执行”也不等于业务天然恰好一次,重试任务仍然需要幂等设计。选组件先看任务性质,再看框架能力。

06
7 个技术主题

网络请求与接口联调

Q33OkHttp 和 Retrofit 各自负责什么P0
核心结论

OkHttp 是执行 HTTP 通信的客户端,处理连接、请求响应和连接复用等;Retrofit 把声明式接口与注解转换为 HTTP 调用,并通过 Converter 等机制处理数据转换。常见组合中 Retrofit 使用 OkHttp 完成底层 HTTP 请求。S17

机制说明:Repository 决定业务从哪里取数据;Retrofit 接口表达路径、方法和参数;OkHttp 执行传输;JSON Converter 把响应映射成对象。它们是不同层次,不是三个可随意互换的名字。

扩展说明:每次请求都 new OkHttpClient 可以吗?通常应复用配置合适的客户端,共享连接池等资源。对于不同网络绑定、证书策略或独立隔离需求,再明确区分客户端实例及资源归属。

Q34OkHttp 拦截器怎样工作P0
核心结论

拦截器按链式结构处理请求和响应。应用拦截器适合统一业务头、业务日志或逻辑层处理;网络拦截器观察实际网络交换,对重定向、重试和缓存场景的触发方式与应用拦截器不同。chain.proceed 继续交给后续链路。S18

机制说明:请求可能经历缓存判断、连接选择或建立、发送、接收以及重试或重定向。不要把一条业务调用假定为只发生一次网络交换,也不要把某个版本的内部类顺序当作永久 API 契约。

扩展说明:能在拦截器里读取响应体做日志吗?响应体通常具有一次消费语义。完整读取可能消耗内容、占用大量内存,还会暴露敏感字段。使用有界、脱敏且不破坏后续消费的方案,并按 API 要求关闭资源。

Q35网络超时和重试怎样设计P0
核心结论

区分连接超时、读写超时和整次调用的总时限;重试必须同时受错误类型、操作幂等性、次数和总时间预算限制。网络超时只能说明客户端没及时拿到结果,不能证明服务端没有执行。

机制说明:查询类操作常容易设计为可重试;支付、设备启动动作、固件升级确认等可能产生重复副作用。对这些操作应使用业务请求 ID、幂等键、设备端去重或结果查询。退避加入随机抖动,避免大量客户端同时重试。

扩展说明:PUT 一定安全、POST 一定不能重试吗?HTTP 语义中的幂等性要结合实际接口实现。POST 也可以通过幂等协议安全重试,不能只看方法名。读取超时也不意味着整次调用只允许这么长时间。

Q36多个请求同时收到 401 怎么刷新 tokenP0
核心结论

使用集中管理的刷新流程,让同一轮过期只触发必要的一次刷新,其余请求等待或复用新 token。重试前确认当前 token 是否已更新,并限制认证重试次数,避免无限 401 循环。

机制说明:不要让每个请求各自刷新,也不要让刷新请求再次进入同一套失效循环。刷新失败要形成明确的登录失效结果。并发控制时避免持锁做长时间网络等待,尤其不能让所有刷新任务等在已被占满的同一执行资源上。

扩展说明:记录完整请求头便于排查可以吗?Authorization、Cookie、设备凭据需要脱敏。线上排查通常记录请求 ID、阶段耗时、错误分类和必要的非敏感元数据即可。

Q37JSON 解析为什么容易线上崩溃P0
核心结论

服务端可能返回缺字段、显式 null、类型变化或非预期枚举值;客户端模型如果把这些情况当作不可能,就会在解析或使用数据时失败。应在传输模型与业务模型之间做校验和容错转换。

机制说明:Kotlin 的非空声明不是对外部 JSON 的万能验证器,具体表现还取决于序列化库与配置。大 JSON 解析、复杂映射或数据库写入都可能耗时,不能因为网络使用异步接口就默认后处理对主线程安全。

扩展说明:接口加了新字段就一定不兼容吗?通常可通过忽略未知字段等策略兼容;删字段、改类型、改变业务含义更危险。还要在开启混淆的 release 包验证反射模型、泛型信息和必要保留规则。

Q38HTTP HTTPS TCP Socket 之间是什么关系P1
核心结论

HTTP 定义请求响应等应用层语义;HTTPS 表示受 TLS 等机制保护的 HTTP;TCP 提供可靠有序的字节流;Socket 是程序使用网络通信能力的一类接口。常见 HTTP/1.1 和 HTTP/2 使用 TCP,HTTP/3 使用基于 UDP 的 QUIC。

机制说明:一次新建 HTTPS 连接可能涉及 DNS、连接建立、TLS 握手,再发送 HTTP 请求。连接复用、HTTP/2 多路复用等减少重复成本,但不能掩盖服务端慢或大响应处理慢。

扩展说明:设备自签名证书不通过,直接信任全部证书可以吗?不能作为正式方案。应按产品信任模型配置证书或可信身份验证,验证主机或设备身份;本地网络也不是天然安全边界。

Q39接口很慢时怎样分清责任P0
核心结论

把总时间拆成排队、DNS、连接、TLS、请求发送、服务端等待、响应读取、解析和 UI 更新。先确认慢在哪一段,再决定查客户端、网络还是服务端,不能只拿一个“请求总耗时”下结论。

机制说明:用请求 ID 串起客户端事件、服务端日志和用户场景;对比冷连接与复用连接、Wi-Fi 与蜂窝、前后台、不同设备。还要看连接池、并发限制、响应体大小和主线程是否在处理返回数据。

扩展说明:服务器说 50ms 返回,用户却等了 2 秒怎么解释?2 秒可能包含连接建立、排队、传输、重试、解析和 UI 阻塞。需要统一时钟或关联标识,并先统一双方测量起止点。

07
10 个技术主题

BLE Wi-Fi 与 Socket 通信

Q40BLE 的 Central Peripheral GATT 和 UUID 是什么P0
核心结论

BLE 是低功耗蓝牙。Central 与 Peripheral 描述连接中的角色,常见是手机作为 Central、设备作为 Peripheral;GATT 描述如何组织与访问数据。Service 是一组功能,Characteristic 是可读写或可通知的数据项,Descriptor 描述特征的附加信息,UUID 用于标识这些对象。

机制说明:可以把设备看作功能目录:某个 Service 是“设备控制”,某个 Characteristic 是“命令输入”,另一个是“状态上报”。GATT Client 与 Server 是数据访问角色,不能与 Central 与 Peripheral 在概念上完全画等号。

扩展说明:BLE 连接一定要先配对吗?不一定,是否需要配对、绑定和链路加密取决于特征权限与产品安全协议。扫描到设备、完成连接、完成身份认证也不是同一个阶段。

Q41从扫描到收到 BLE 数据的完整流程P0
核心结论

先确认硬件能力、蓝牙开关和权限;按条件扫描并选择设备;connectGatt 发起连接;连接回调成功后发现服务;找到指定特征和描述符;按协议完成通知订阅、鉴权或握手,最后才进入业务可用状态。S19S20

机制说明:常见通知订阅既需要本地 setCharacteristicNotification,也需要向对应 CCCD 描述符写入启用 Notification 或 Indication 的值,并等待写入结果。仅建立连接或仅调用本地启用接口,都不保证会收到设备上报。

扩展说明:Notification 与 Indication 有什么不同?Indication 在 ATT 层有确认机制,Notification 没有这种逐条 ATT 确认;但无论哪种,都不能把传输层成功当作“设备业务已经执行完成”。关键操作需要业务响应。

Q42为什么 BLE SDK 常用状态机和串行操作队列P0
核心结论

连接、服务发现、特征读写等操作是异步完成的,回调到达时间可能晚于用户下一步操作。状态机限定当前允许做什么,队列控制可并发范围,让初始化和命令按协议有序推进。

机制说明:常见状态有 Idle、Scanning、Connecting、Discovering、Subscribing、Authenticating、Ready、Disconnecting。一次 API 返回“已受理”不等于完成;对需要串行的 GATT 操作,应等对应回调或明确失败后再推进。每一阶段都要有超时、取消和清理入口。

代码练习三 连接与命令状态设计:下面是设计规则,不是可直接调用的 Android API。实现时把状态修改集中到一个串行执行器。

设计示例
connect
  创建新的 sessionId 并进入 Connecting
onConnected
  校验当前 GATT 实例与会话 然后进入 Discovering
onServicesReady
  排队订阅与握手 全部完成后进入 Ready
sendCommand
  仅 Ready 接收 建立 requestId 超时与业务确认条件
onDisconnectOrClose
  结束会话 失败回调未完成请求 释放 GATT 与监听

扩展说明:某次写超时后直接发下一条可以吗?要谨慎。系统回调不一定带应用自定义请求 ID,迟到回调可能被误认成下一条的结果。无法可靠区分时,应结束或重建会话;业务回包则通过协议序号匹配,不能只在本地发明一个 ID 就假定设备会返回它。

Q43BLE 为什么要分包 MTU 是不是固定 20 字节P0
核心结论

不是固定 20 字节。常见默认 ATT MTU 为 23,普通写入或通知的 ATT 开销常为 3 字节,因此常见有效载荷为 20;协商更大 MTU 后可增加,但仍受具体操作、属性长度和设备实现等限制。

机制说明:以实际协商结果和协议上限决定分包大小,不要根据 requestMtu 的入参直接假定成功。Android 14 起首次 GATT 客户端请求 MTU 时有请求 517 及后续请求处理的行为变化,但不表示所有设备都实际协商到 517,也不表示任意 514 字节业务包都能发送。S21

扩展说明:应用层如何分包?可定义消息序号、分片编号、总长度与校验信息,接收端有界缓存并设组包超时。丢包、重传和去重要按可靠性需求实现;CRC 主要检测错误,不提供身份认证。

Q44蓝牙权限怎么按版本处理P0
核心结论

必须一起看设备系统版本、应用 targetSdk 和具体 API。对现代目标版本,Android 12 起常用 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT;需要广播能力时再考虑 BLUETOOTH_ADVERTISE。旧系统扫描还需按适用规则处理定位权限与相关设置。S19

适用条件重点处理不能忽略的边界
旧系统 BLE 扫描旧蓝牙权限与相应定位授权API 级别 前后台和定位开关有差异
Android 12 及以上且 targetSdk 至少 31按功能申请 SCAN CONNECT ADVERTISE清单声明和运行时授权都要处理
扫描不推导位置可按事实使用 neverForLocation部分 BLE beacon 可能被过滤
用户拒绝 撤销权限或关闭蓝牙输出明确状态并允许恢复不能只在初始化时检查一次

扩展说明:SDK 可以自己弹权限框吗?纯通信 SDK 更适合报告缺少的能力并提供接入说明,由宿主在合适交互时机申请。若 SDK 提供 UI 封装,也要明确 Activity 生命周期和重复请求处理。

Q45BLE 扫不到 连不上 连接后没数据分别怎么查P0
核心结论

扫不到先查广播、权限、蓝牙状态、过滤条件和扫描频率;连不上查信号、设备占用、连接状态与会话清理;连接后没数据查服务发现、UUID、特征属性、CCCD、握手及协议内容。每个阶段都记录独立结果,避免只输出“蓝牙失败”。

机制说明:状态 133 一类错误不能直接归为唯一原因。保留机型、系统、固件、阶段、GATT 状态、耗时和连接代次,进行受控重试并对比设备端日志。避免循环频繁扫描和重连,也不要依赖隐藏 refresh API 作为通用解法。

扩展说明:设备已经发通知,应用没收到怎么办?核对发给哪个连接、CCCD 是否成功、目标 UUID、加密状态,以及回调是否到达但被线程阻塞或会话校验丢弃。优先用时间线把问题缩到一层。

Q46Wi-Fi 配网常见有哪些方式P0
核心结论

常见有 BLE 辅助配网、设备 SoftAP 配网和厂商特定的广播或组播配网方案。它们的目标通常是把路由器凭据安全地交给设备,让设备连接目标网络,再验证设备真正上线。

方式典型流程主要关注点
BLE 辅助配网手机经 BLE 下发凭据 设备入网并回报身份确认 凭据保护 设备支持情况
SoftAP 配网手机连设备热点 经局域网发送配置无互联网网络 路由选择 切回原网络
厂商广播或组播方案在约定网络条件下编码传递配置路由器兼容性 安全性 丢包与环境限制

机制说明:下发成功不代表配网成功,还要分别确认凭据接收、接入路由器、获取地址,以及业务要求的云端注册或本地可达。Wi-Fi Direct 是点对点连接能力,不能直接等同于让设备接入家庭路由器。

扩展说明:配网失败如何提示?按阶段给出权限拒绝、找不到设备、认证失败、网络不支持、设备离线等可行动结果,避免所有情况只显示“请重试”。

Q47手机连接设备热点后 为什么仍然请求不到设备P0
核心结论

设备热点可能没有互联网,系统仍可能把默认互联网流量放到蜂窝或其他网络。应使用获准连接后得到的 Network,为设备通信创建或绑定对应 Socket,必要时连 DNS 也明确使用该网络,而不是只看到 Wi-Fi 图标就认为路由正确。S22

机制说明:Android 10 起的 WifiNetworkSpecifier 常用于应用发起的本地设备连接,涉及系统确认;WifiNetworkSuggestion 则向系统提供网络建议,不能当作强制立即切网的命令。进程整体 bindProcessToNetwork 会影响其他请求,应慎用并正确恢复。

权限补充:Android 13 起相关 Wi-Fi 管理 API 使用 NEARBY_WIFI_DEVICES,但 startScan、getScanResults 等仍可能要求定位授权。Android 17 上面向 API 37 及以上的应用还需要按局域网保护规则处理 ACCESS_LOCAL_NETWORK 或适用的系统中介选择流程,不能只记 INTERNET 一个权限。S23S24

扩展说明:onAvailable 就能告诉用户配网成功吗?不能,它说明相应网络可用,还要验证设备服务可达以及设备最终的入网结果;结束时移除 NetworkCallback 并释放会话。

Q48TCP 为什么有粘包 半包 怎样解析P0
核心结论

TCP 提供有序字节流,不保留应用每次 write 的消息边界。一次 read 可能只读到半条业务消息,也可能读到多条消息拼在一起。应用必须自定义边界,例如固定长度、分隔符或长度字段。S25

机制说明:长度字段方案可以在消息头写入 payload 长度,先读足头部,校验长度,再读足正文。必须限制最大长度,并处理 EOF、超时和非法格式,避免根据不可信长度无限申请内存。

代码练习四 读取长度前缀消息:示例约定 4 字节大端有符号长度,正文上限 1MiB;在后台 I/O 执行。真实协议还需要业务类型、版本、序号与必要的完整性验证。

Java
static byte[] readFrame(DataInputStream in) throws IOException {
    int length = in.readInt();
    if (length < 0 || length > 1024 * 1024) {
        throw new IOException("Invalid frame length");
    }
    byte[] body = new byte[length];
    in.readFully(body);
    return body;
}

扩展说明:读取中间超时后继续调 readFrame 吗?这个简化实现不能安全地从头恢复。若已读了一部分,必须保留解析状态继续,或关闭连接重新建立边界。还要设整帧总期限,避免对端每次少发几个字节却永不完成。

Q49Socket 如何设计心跳 断线重连和重复命令处理P0
核心结论

用心跳或协议活动判断会话活性,连续超时达到策略条件后断开;重连采用有上限的退避与随机抖动;用请求序号和业务幂等规则处理重复消息。网络切换后应重新确认连接、身份和状态,不能只把旧 Socket 标为已连接。

机制说明:心跳周期需要平衡故障发现速度与功耗,不能所有场景每秒发一次。TCP 的可靠传输不代表“设备命令只执行一次”;连接断开时,有些命令可能已经执行,只是响应没回到手机。

扩展说明:ACK 与业务结果有什么区别?ACK 可能仅表示收到、校验通过或进入队列,业务结果才表明执行成功。协议需要定义每种确认的语义,客户端分别管理发送超时、确认超时与业务完成超时。

08
7 个技术主题

SDK 架构与工程交付

Q50如果让你设计一个设备 SDK 你会怎样分层P0
核心结论

对外提供清晰的设备操作接口,内部区分会话管理、业务命令、协议编解码、传输实现和平台适配。BLE、Socket 或模拟设备通过传输接口接入,业务调用方无需直接处理 UUID、字节数组和系统版本分支。

机制说明:例如 DeviceClient 暴露 connect、observeState、execute 和 close;SessionManager 维护连接代次;CommandDispatcher 关联请求与结果;Codec 校验并编解码;Transport 负责收发和连接事件。各层依赖接口,测试时可以替换成模拟传输。

扩展说明:为什么不只做一个 BluetoothUtil?简单工具类只能降低调用成本,不能自然管理复杂会话、取消、重试和状态一致性。设备一多、协议一升级,隐含全局状态会成为主要维护风险。

Q51SDK 的公开 API 需要明确哪些契约P0
核心结论

明确参数约束、调用线程、回调线程、操作前置状态、并发规则、取消含义、超时范围和资源归属。调用方应知道方法返回代表“受理”“传输完成”还是“业务成功”,以及是否可能同步回调。

机制说明:如果允许用户注入 Executor,说明回调在该执行器上的顺序保障和拒绝策略;如果固定主线程回调,说明重型数据处理应在哪里完成。终态回调通常应保证至多一次,取消和结果竞争时也要遵守同一规则。

扩展说明:SDK 可以回调前自己吞掉所有异常吗?内部要避免异常破坏状态,但不能把真实失败都伪装为成功。对调用方回调抛异常的处理也要明确,避免一条错误回调破坏整个队列。

Q52SDK 初始化与释放如何保证可靠P0
核心结论

初始化尽量轻量、显式且幂等;明确进程和实例作用域,避免重复初始化创建多份线程、连接和监听。释放时先禁止新操作,再结束当前会话和待完成请求,取消任务、注销监听、关闭连接,最后按约定释放自有执行资源。

机制说明:幂等是重复调用不会产生额外的错误副作用,不代表可以忽略配置冲突。若第二次 init 的参数与第一次不一致,应明确拒绝或按受支持方式重配。共享线程池与宿主传入资源不能被 SDK 随意关闭。

扩展说明:多进程应用中单例还只有一份吗?通常每个进程各有一份。要确认哪些进程需要初始化,必要时把服务边界设计清楚。用户切换、账号切换与设备切换也要隔离相应会话状态。

Q53组件化 模块化与依赖倒置怎样落地P0
核心结论

模块化通过清晰边界拆分代码和依赖;组件化进一步强调业务能力的独立组合。依赖倒置让高层业务依赖抽象接口,具体实现可以替换。例如业务依赖 DeviceTransport,BleTransport 与 SocketTransport 实现它。

机制说明:可将工程拆为 sdk-api、sdk-core、transport-ble、transport-socket 和 sample-app。api 不暴露内部实现类;core 依赖接口;Android 平台能力隔离在适合的模块。模块数量应由边界决定,不能只为“架构好看”拆空壳。

扩展说明:接口 继承 多态为什么让替换更容易?接口规定能力,具体类提供实现,多态让调用点通过同一契约使用不同实现。真正可替换还要求行为契约一致,例如同样的取消和错误语义。

Q54AAR 发布和兼容性需要注意什么P1
核心结论

AAR 可以包含类、Android 资源、Manifest 及原生库等。发布时明确 minSdk、依赖版本与传递依赖、资源前缀、Manifest 合并影响、混淆规则和二进制兼容性。最好通过受控的 Maven 坐标与版本管理降低接入歧义。

机制说明:删除方法、改签名、暴露第三方内部类型、修改资源名都可能影响宿主。consumer ProGuard 规则只保留必要入口与反射内容,不应把整个包全部 keep。含 .so 时还需检查 ABI、原生符号和目标平台兼容要求。

扩展说明:编译成功就代表 SDK 升级成功吗?不代表。还应验证实际 release 包、混淆、资源合并、运行期 API 可用性和关键设备协议兼容,覆盖旧宿主与新 SDK 的组合。

Q55多机型 多系统 多固件怎样设计验证P0
核心结论

围绕影响行为的维度建立测试矩阵:系统与 targetSdk、芯片和机型、设备固件、协议能力、前后台、权限、网络和蓝牙状态。优先覆盖高风险边界与主流组合,而不是只增加随机测试设备数量。

机制说明:重点场景包括首次授权、拒绝后重试、权限撤销、蓝牙中途关闭、连接时切后台、旋转重建、手机网络切换、设备重启、升级后协议变化、多次 connect 与 close。协议层还要注入半包、重复包、超时、乱序业务响应和非法长度。

扩展说明:模拟器能验证全部蓝牙问题吗?不能,真实射频环境、蓝牙栈、硬件和固件差异必须实机验证。单元测试适合验证状态机与解析器,实机联调用于验证平台和设备交互。

Q56SDK 出现线上问题 怎样做到可定位可回滚P0
核心结论

先记录版本、环境、会话与阶段结果,再通过脱敏日志和事件时间线判断问题所在层。发布前定义观测指标和停止条件,灰度时监控连接成功率、命令完成率、时延、崩溃及资源增长,异常时回滚受控配置或 SDK 版本。

机制说明:连接成功率必须定义分母与排除规则,例如用户主动取消不应混入协议失败而不说明。用 sessionId 和 requestId 串联阶段,日志保留错误码、耗时、重试次数及必要上下文,避免记录 Wi-Fi 密码、token 或完整敏感 payload。

交付补充:CI 至少覆盖关键协议与状态机测试、静态检查和样例应用的 release 构建。新增问题门禁可结合代码变更与规则范围控制,避免历史存量问题淹没当前变更;但差异报告不能替代完整的集成与兼容性回归。

扩展说明:抓不到线上复现时怎么办?先补足最小诊断信息并比较异常分群,再用相同系统、机型、固件与网络条件复现。没有证据时先提出可验证假设,不把“系统 bug”当结论。

09
8 个技术主题

性能与稳定性优化

Q57启动速度应该测什么P0
核心结论

至少区分首帧可见与主要内容可用。Android 的 TTID 衡量初始显示,TTFD 结合 reportFullyDrawn 等机制表达完整显示;团队定义的“响应时延”“启动完成时延”必须明确自己的起止点,不能未经核对就与 TTID、TTFD 画等号。S26

机制说明:冷启动涉及进程与应用初始化;温启动和热启动复用的资源不同。onCreate、onResume、doFrame 结束都不是天然的实际呈现终点。以手指按下作为起点的端到端指标还包含输入链路,需与系统启动指标区分。

扩展说明:如何验证首帧?关联正确窗口的帧事件、FrameTimeline 与呈现信息,并结合录屏或高速相机验证用户可见效果。240fps 相机每帧约 4.17ms,测量结果有帧粒度和动作判定误差。

Q58Perfetto Trace 具体怎样定位卡顿或启动慢P0
核心结论

先圈定异常时间段和目标帧,再看主线程是否运行、等待或未获调度;沿耗时切片、锁等待、Binder 和 I/O 找原因,同时看 RenderThread、GPU 与合成链路。Trace 用于建立因果证据,不只是截图一个“很长的块”。S07

机制说明:主线程长时间 Running 可能是计算或布局;Runnable 很久可能是 CPU 竞争;同步 Binder 等待需要继续看远端;磁盘或锁等待需要找资源拥有者。为关键初始化和业务阶段添加 Trace 标记,有助于对齐自己的代码与系统事件。

扩展说明:Actual Timeline 很长就等于主线程一直执行吗?不是,它表达帧相关时间线,不能当作 CPU 执行时长。必须关联对应应用帧、显示帧与线程调度信息,判断错过的是哪个阶段。

Q59冷启动优化优先做哪些事情P0
核心结论

先减少首屏必须等待的工作,再缩短这些工作的执行时间。梳理初始化依赖,把非必要 SDK 初始化、磁盘访问、复杂数据准备和非首屏布局移出关键路径;只有真正独立的任务才适合并行。

机制说明:常用方式包括精简 Provider 与 Application 初始化、延迟初始化、按需加载 ViewStub、控制布局复杂度和图片解码、正确使用异步 Inflate。后台化要同步检查线程竞争、首次使用等待和错误处理,防止把卡顿从启动搬到下一次点击。

扩展说明:开十个线程能不能更快?不一定,任务可能存在依赖,设备 CPU 与 I/O 资源也有限。并发过高会增加调度、GC、内存与锁竞争,最终让首屏更慢。

Q60AsyncLayoutInflater 为什么能优化 会改变生命周期吗P0
核心结论

它把符合条件的布局创建工作放到后台执行,减少主线程被 Inflate 占用的时间;它不会重排 Android 规定的 Activity 生命周期。页面可能先进入 resumed,而内容布局稍后才创建并在主线程接入,因此内容显示时间与生命周期回调完成时间会分离。S27

机制说明:不是所有 View 都能后台创建。构造中依赖线程 Looper、创建 Handler 或非线程安全行为,都可能使异步创建不适用并回退主线程。回调默认路径与显式 callback Executor 的行为也需按所用版本区分;真正操作页面和挂载 View 必须回到主线程。

扩展说明:布局为什么一块块出现?可能分批 inflate 和挂载、数据分批绑定,或占位内容先显示。可先准备整体结构,再一次挂载,或定义明确的骨架屏策略;并在回调时检查页面是否已销毁、当前请求是否已过期。

Q61内存泄漏 线程泄漏 OOM 和 GC Root 是什么P0
核心结论

内存泄漏是已经不再需要的对象仍被可达引用保留;线程泄漏是应结束的线程或任务持续存在;OOM 是内存分配失败,可能由泄漏、峰值过高、大对象或其他资源压力导致。GC Root 是垃圾回收可达性分析的起点,例如线程栈引用、静态引用所关联的根和 JNI 引用等。S28

机制说明:线程泄漏会占用栈、调度和其他资源,也可能通过 Runnable 留住 Activity,进而形成内存泄漏,但两者不是同一概念。排查时复现进入退出循环,观察对象数量与内存曲线,用 heap dump 找到从根到泄漏对象的引用链。

扩展说明:缓存也属于泄漏吗?有边界、有淘汰策略的缓存是主动保留;长期无界增长或错误保留页面会变成问题。Bitmap 要按显示尺寸采样与合理缓存,不要寄希望于频繁调用 System.gc 来解决设计问题。

Q62卡顿 ANR 崩溃怎样区别 怎样排查P0
核心结论

卡顿是用户可感知的响应或帧呈现不及时;ANR 是系统认定应用未在相应时限内完成特定响应;崩溃是未处理异常、原生故障等导致异常终止。ANR 不等于“主线程连续执行超过 5 秒”这一条规则。S29

机制说明:输入分发超时常见默认阈值约 5 秒,但广播、服务等有各自条件和时限,且受版本和场景影响。主线程可能不是在忙算,而是在等锁、远端 Binder,或者没有合适的可接收输入窗口。抓取主线程栈后还要追等待链。

扩展说明:崩溃先看什么?完整异常类型、cause 链和相关业务栈,结合版本、机型与用户步骤;原生崩溃需要保留对应符号。不要只截最上面一行,也不要给所有异常套 try-catch 后当作已修复。

Q63CPU 与功耗怎样优化P0
核心结论

减少没有必要的计算、唤醒、网络和无线扫描,并按用户可感知需求决定频率。先测量前台、后台、连接与空闲等场景,再针对热点和唤醒来源处理,不以单次 CPU 百分比代表全部功耗。

机制说明:设备 SDK 常见问题是持续高频 BLE 扫描、过密心跳、失败后无休止重连、频繁日志和轮询。可以限制扫描窗口、使用退避、合并状态更新、停止无人使用的任务,并确保 WakeLock 成对释放且有超时。

扩展说明:线程都休眠为什么仍可能耗电?频繁唤醒可能让 CPU 或无线模块难以进入低功耗状态。用系统电量统计、Perfetto 和适用设备上的功耗数据,结合时长、温度与网络环境做对照。

Q64怎样证明优化有效并且没有副作用P0
核心结论

统一测试条件与指标口径,进行多次前后对比,关注中位数和尾部时延,而不只挑最好一次。既测主要收益,也测崩溃、内存、线程、功耗、数据就绪时间和关键交互,防止局部指标变好但实际体验变差。

机制说明:控制构建类型、机型、系统、温度、后台负载、网络、缓存与编译状态,区分冷温热启动。可以用 Macrobenchmark 等进行回归;Baseline Profile 可改善部分代码的执行准备,但不能消除网络、错误依赖或主线程 I/O。S30

扩展说明:异步化后首帧快了 100ms 就能上线吗?还要确认内容是否可用、点击是否阻塞、是否发生数据竞争和页面销毁后回调。指标提升与正确性都满足后,再结合灰度数据判断。

10
8 个技术主题

工程案例与综合场景

Q65座舱多屏模块的冷启动优化实践P0

实践说明:座舱多屏互动模块的冷启动优化,先用 Perfetto 把启动链路拆成初始化、布局创建、数据准备和首帧相关阶段,重点排查主线程阻塞和不必要的前置依赖。随后结合异步 Inflate、重任务后台化、ViewStub 按需加载和 IdleHandler 调整初始化时序。经过多轮实车复测与 Trace 复采,响应时延由 380ms 降到 240ms,降低约 37%;启动完成时延由 1200ms 降到 700ms,降低约 42%。

分析方法:针对一个真实瓶颈,依次分析“原本谁在等待谁”“Trace 上有什么证据”“为什么这个任务可以延后或并行”“怎样保证依赖与生命周期正确”“复测是否出现副作用”。

验证记录:380ms 的起点和终点是什么?冷启动怎么保证?哪个优化收益最大?测试多少轮、什么设备、看什么统计量?各模块的改动范围是什么?这些结论应以实际记录为依据;如果当时没有拆分单项收益,就说明结果来自组合优化,不编造每个措施节省的毫秒数。

Q66为什么选异步 Inflate 而不是只优化 XMLP0

实践说明:“我会先确认 Inflate 是否确实在关键路径且占比较高。精简 XML 可以减少创建和布局成本;异步 Inflate 可以在满足线程安全约束时,把部分创建工作从主线程移开。两者可以结合,但异步化会带来内容晚到、页面销毁后回调和状态协调问题,所以需要验证适用控件、数据依赖以及最终挂载时机。”

边界情况:如果布局不能异步创建怎么办?先定位不支持的 View 或工厂,考虑调整范围、同步创建最小首屏骨架、按需加载后续内容;不要为了维持“异步方案”破坏控件线程约束。回退到主线程的实际情况也要通过 Trace 确认。

验证标准:能说明收益来自缩短主线程关键路径,并说清总工作量可能没有下降,就比只说“后台更快”完整。还要承认首次内容就绪可能延后,需要用用户体验指标验证。

Q67跨屏交互与设备 SDK 有哪些共通设计P0

实践说明:“两类工作都涉及异步状态变化、系统接口、资源生命周期和环境差异。跨屏时要处理窗口和配置变化、输入坐标或焦点、页面重建与状态恢复;设备 SDK 要处理连接变化、权限、协议状态和断连恢复。共通方法是拆解调用链路、用证据定位边界问题,并把异常恢复设计成明确流程。”

知识补充:虚拟显示在 Android 中是具有显示标识和配置的逻辑显示输出,其内容可以送到指定 Surface;它不必等同于录屏,也不必自动拥有 Launcher。Activity 流转到另一个 Display 后是否重建,需要看配置与系统策略;返回键交给哪个窗口,则还涉及焦点和输入路由。

扩展说明:应用调用接口、系统联调、系统问题定位和 Framework 或驱动修改具有不同的实现边界。工程记录应区分这些层次,标明实际改动范围以及对应的验证证据。

Q68设备命令超时 用户再次点击 会发生什么P0
核心结论

第一次可能已经执行,只是结果未到;第二次如果重新发送,就可能重复执行。因此 UI 防连点只是第一层,SDK 与设备协议还需要请求 ID、幂等或去重规则,并明确未知结果如何查询与恢复。

参考方案:对“设置温度为 24 度”这样的目标状态操作,可按协议定义幂等;对“执行一次开锁”或“增加一次计数”这类动作,不能盲目重试。连接异常后先确认设备状态或查询请求结果;若协议无法保证,就对外暴露“结果未知”,而不是擅自报失败后重做。

扩展说明:如何实现业务恰好一次?通常需要客户端与服务端或设备共同的持久化、去重和确认协议。单靠 TCP、一个本地 boolean 或 UI 禁用按钮,不能普遍保证跨崩溃与断线的恰好一次执行。

Q69设备每秒上报一百次数据 页面越来越卡怎么办P0
核心结论

先区分哪些数据必须完整处理,哪些只是 UI 当前状态。原始数据处理放在合适的后台执行路径,队列有界;UI 可按展示需求合并、采样或保留最新值,避免每条上报都触发复杂布局与绘制。

机制说明:温度读数、进度等状态通常可以覆盖旧展示值,但命令结果、告警和需要完整记录的采样数据不能随意丢弃。SDK 应对不同数据类别规定背压策略,分别观察接收、解析、存储、分发和渲染的耗时及积压量。

扩展说明:直接把所有回调丢进线程池就行吗?不行,可能改成后台无界积压、乱序和内存增长。正确方案要控制生产和消费速率,明确顺序和丢弃策略。

Q70SDK 初始化耗时 首屏又要使用它 怎么处理P0
核心结论

先把“首屏真的需要哪些能力”拆出来。同步完成轻量状态与必要配置,重型能力按依赖异步准备;首屏请求通过明确的就绪状态、等待上限与失败结果协调。若首屏确实依赖某项结果,就承认它仍在关键路径,继续优化或调整产品交互。

机制说明:设计 NotInitialized、Initializing、Ready、Failed 等状态,合并重复初始化。页面观察状态后展示加载、可用或失败,避免轮询和忙等。依赖图中 A 等 B、B 又等 A 会形成死锁或无限等待,不能只凭“都放后台”解决。

扩展说明:准备失败时界面怎么办?依据功能必要性选择重试、限制部分功能或降级展示,同时保留错误原因与诊断信息。必须有失败路径,不能让加载指示无限转。

Q71从 Java 扩展到 Kotlin 与 BLE 的实践路径P0

实践说明:对于以 Java 为主的 Android 开发,可优先学习 Kotlin 的空安全、协程、Flow 和 ViewModel,它们直接影响状态与并发设计。BLE 按扫描、连接、服务发现、订阅、握手和业务响应的顺序逐步验证。对于新的设备协议,先结合协议文档和最小验证程序建立可观测链路;文档学习、实验验证和量产应用应分别记录。

实践步骤:实际运行一个设备列表页面和一个协议解析练习。若有设备,再验证扫描、权限拒绝、订阅、断连和清理。没有真实 BLE 设备时,也能验证状态机和虚拟传输,但应明确实机能力尚未验证。

实践边界:区分已理解的机制、已完成的实现和待验证的未知点。准确记录实现语言、运行环境和验证范围,避免把实验能力当成已验证的生产能力。

Q72与产品 测试 固件或算法团队意见不一致怎么办P0
核心结论

先对齐用户目标和可验收标准,再用复现步骤、日志、协议或测量结果讨论取舍。涉及跨团队问题时明确阶段责任和接口契约,共同完成最小复现,确定修复方案、回归范围与发布节奏。

协作记录:针对真实案例,依次记录背景、争议点、判断依据、与相关方确认的方案、结果和后续防复发措施。若与算法团队协作,补充输入输出格式、计算时延预算、线程模型、设备资源限制和异常数据处理。

扩展说明:线上事故第一步是复盘吗?通常先控制影响、保留证据并恢复服务,再定位与复盘。复盘应形成可执行改进,例如增加阶段错误码、补充回归场景或修复依赖设计,而不是只归责某个人。

11
学习安排

学习路线与实践检查

两天集中学习安排

时间块任务完成标准
第一天上午 2 小时Q01 至 Q16 组件 View 与帧调度能口述生命周期并解释事件拦截和绘制流程
第一天下午 2 小时Q17 至 Q24 Handler 线程与 Binder能判断任务在哪个线程执行以及谁负责取消
第一天下午 2 小时Q25 至 Q32 Kotlin MVVM写出并运行一次状态加载与错误处理
第一天晚上 1 小时Q57 至 Q66 性能与项目整理一份项目记录 检查结论是否有证据
第二天上午 2 小时Q40 至 Q49 设备通信不看材料讲完整 BLE 与配网流程
第二天下午 1.5 小时Q33 至 Q39 网络解释超时 重试 鉴权并发与慢请求拆解
第二天下午 1.5 小时Q50 至 Q56 SDK 设计设计 API 状态机 错误分类与测试矩阵
第二天晚上 1 小时Q65 至 Q72 综合实践完成实践检查并补齐理解偏差

时间不足时,优先顺序为 Q28 至 Q32、Q17 至 Q24、Q40 至 Q49、Q57 至 Q66,再回到 View 和组件基础。已经熟悉的内容通过小型验证后直接跳过,不必从第一页平均用力。

设备连接与状态面板实践

做一个“设备连接与状态面板”:页面显示连接状态与最近数据,ViewModel 暴露不可变 UI 状态,Repository 调用设备接口;先用 FakeTransport 模拟成功、超时、断连和重复回包,再按条件接入真实 BLE 或 Socket。

验收点:旋转后不重复创建连接;重复点击不会产生无限并发;断连后待完成请求能结束;旧会话回调不会改写新会话;退出后没有监听和线程持续增长;UI 高频状态更新不会把命令回包丢掉;错误日志能定位失败阶段。

这项练习用于串联各层知识。实现后保留 README、状态转换说明、复现步骤和实际运行证据,便于后续检查、维护与团队交流;未实现或未验证的部分应明确记录。

一次 45 分钟知识检查

时长检查内容核心要点
3 分钟梳理工程背景与目标明确一个可验证的工程案例
5 分钟Q03 Q28 Q29 旋转与 ViewModel生命周期顺序 作用域 进程死亡边界
5 分钟Q13 Q16 事件与绘制拦截取消 帧调度 实际呈现区别
6 分钟Q17 Q20 Q23 并发与 IPCLooper 归属 有界并发 同步 Binder 风险
7 分钟Q41 Q42 Q48 通信回调驱动状态机 业务确认 消息边界
7 分钟Q50 Q51 Q68 SDK 情境设计契约 资源归属 超时与幂等
8 分钟Q65 Q66 性能案例分析指标定义 Trace 证据 方案取舍 回归
4 分钟Q71 Q72 实践边界与协作验证范围明确 协作方法清楚

检查方法:每一部分按 0 至 3 分记录:0 分表示尚未理解;1 分表示知道定义但不清楚机制;2 分表示定义、机制与边界基本正确;3 分表示能够给出具体场景、运行证据或可验证方案。检查后先修正事实错误和理解偏差,再补充实践记录。

十二条关键结论

  1. Service 默认不替你创建后台线程,绑定也不必然是跨进程。
  2. Handler 的执行线程由 Looper 决定,post 到主线程仍会占用主线程。
  3. suspend 不表示自动切后台,协程顺序调用不会自动并行。
  4. ViewModel 保留有作用域边界,原对象不能跨进程死亡存活。
  5. SavedStateHandle 保存小型恢复状态,不替代持久化存储。
  6. onResume 和 doFrame 结束都不等于目标内容已经实际呈现。
  7. BLE 连接成功、订阅成功、握手成功与业务成功是不同阶段。
  8. GATT 操作返回已受理,不能直接当作完成回调。
  9. TCP 没有业务消息边界,超时也不证明命令没有执行。
  10. 后台化仍要管理依赖、并发、取消和资源清理。
  11. 权限要结合系统版本、targetSdk、API 和用户当前授权状态。
  12. 项目指标要说明起止点、环境、统计方法与自己的实际贡献。

项目接入前需要澄清的问题

“SDK 主要连接哪类设备,BLE、Wi-Fi 和 Socket 各自承担哪些能力?”这能帮助判断工作重点是协议、平台适配还是上层业务。

“SDK 的宿主是内部应用还是外部客户应用,公开 API 和版本兼容如何管理?”这能帮助判断工程交付与支持成本。

“目前最主要的稳定性或性能问题发生在哪个阶段,团队如何获取设备侧与应用侧联合日志?”这有助于确定联调方式与 Perfetto 分析范围。

“项目当前阶段最重要的交付是什么,Android、固件、测试和算法团队怎样分工?”这有助于明确交付预期和协作方式。

12
术语 · 资料

术语与官方资料

易混术语速查

术语通俗含义
SDK给其他开发者集成使用的能力包及接口 文档和示例
BLE低功耗蓝牙 常用于发现 控制和小量数据交换
GATTBLE 中组织与访问服务和特征数据的一套规则
CCCD配置客户端接收特征通知或指示的常见描述符
MTU某协议层一份传输单元的大小上限 不是直接等于业务载荷
SoftAP设备以接入点方式创建热点
IPC进程间通信的总称
BinderAndroid 常用的进程间通信机制
AIDL描述接口并生成 Binder 调用适配代码的工具机制
ParcelAndroid 用于组织跨边界传输数据的容器
MVVM将界面 状态逻辑与数据职责组织清楚的架构方式
背压消费跟不上生产时 控制速率 排队或数据保留策略
幂等同一意图重复执行不会产生额外的业务副作用
TTID 与 TTFD初始显示时间与完整显示时间相关的启动指标
ANR系统判定应用未及时完成某类响应

官方资料索引

正文中的 S 编号对应以下资料。Android 权限、后台限制及库 API 应以项目实际采用的系统、targetSdk 和依赖版本核对。示例用于理解机制,不构成完整的生产 SDK。

S01Android Developers Activity 生命周期

S02Android Developers 任务与返回栈

S03Android Developers 绑定服务

S04Android Developers 前台服务类型要求

S05Android Developers App Startup 初始化管理

S06Android Developers ViewGroup 触摸事件管理

S07Perfetto FrameTimeline 与卡顿分析

S08Android Developers Handler API

S09Android Developers ThreadPoolExecutor API

S10Kotlin 空安全

S11Kotlin 协程基础

S12Kotlin 协程异常处理 与 取消和超时

S13Android Developers ViewModel 概览

S14Android Developers ViewModel 保存状态

S15Kotlin StateFlow API

S16Android Developers 生命周期感知协程

S17Square Retrofit API

S18Square OkHttp 拦截器指南

S19Android Developers 蓝牙权限

S20Android Developers BLE 数据传输与通知

S21Android Developers BluetoothGatt API 与 Android 14 MTU 行为变化

S22Android Developers Wi-Fi Network Request API

S23Android Developers 附近 Wi-Fi 设备权限

S24Android Developers 面向 Android 17 的行为变化

S25IETF RFC 9293 TCP 规范

S26Android Developers 应用启动时间

S27Android Developers AsyncLayoutInflater API

S28Android Developers Java 内存分析

S29Android Developers ANR 分析

S30Android Developers Macrobenchmark 与 Baseline Profiles

章节目录