Android 的修炼之道
小哲的公司决定做「轻记账」的原生 Android App——他刚在《JavaSE 到 JavaEE》里学完 Java,正好用上。但移动开发和后端是两套世界:Activity 生命周期、Kotlin 协程、Jetpack Compose、上架审核……周师傅说:Android 是「一个系统 + 一套组件 + 一堆规矩」的江湖,十站带你入门到上架。
移动端的入场券
Android 开发全景:语言 + 系统 + 组件 + 工程师傅!公司要做「轻记账」的原生 Android App——我刚学完 Java,是不是直接就能上手了?
Java 是「一半」——现代 Android 的主力语言是 Kotlin(和 Java 无缝互通,你学过的 Java 语法和面向对象完全用得上)。但真正的门槛不是语言,而是「平台」:
- 系统层:App 跑在 Linux 内核 + ART 运行时上——进程模型和桌面端完全不同。
- 组件层:四大组件(Activity/Service/广播/ContentProvider)——App 的骨架。
- UI 层:Jetpack Compose(声明式 UI)——现代写法。
- 工程层:Gradle 构建、签名、上架、适配——移动开发的「规矩」特别多。
来,十站路线——
记住一句话:Android 开发 = 懂系统(进程/生命周期)+ 会组件(四大件)+ 写 UI(Compose)+ 守规矩(适配/权限/合规)。走,第一站,先看 App 跑在什么上面。
系统架构:平台怎么组织
Linux 内核 · ART · 进程模型 · 系统服务桌面程序想开就开、想关就关;Android App 的「生死」却是系统说了算——不了解平台架构,你就理解不了为什么 Activity 要「保存状态」、为什么内存会被回收。
Android 平台四层架构:
- Linux 内核:地基——进程管理、内存管理、驱动(和第十五本爬虫无关,和服务器 Linux 是亲戚)。
- 系统服务层:AMS(Activity 管理)、WMS(窗口管理)、PackageManager 等——App 和系统打交道的「窗口」。
- ART 运行时:App 代码的「执行引擎」——Kotlin/Java 编译成字节码,ART 负责跑(JIT+AOT,第十四本 JVM 的移动版)。
- 应用框架层:四大组件 + Jetpack 库——开发者直接用的 API。
关键认知:每个 App 跑在自己的进程 + 沙箱里——App 间默认隔离(安全基础);系统内存不足时按优先级杀 App 进程(后台的优先被杀)。
┌──────────────────────────────────┐
│ 应用层:你的 App(四大组件) │ ← 开发者在这里
│ Jetpack 库(ViewModel/Compose…) │
├──────────────────────────────────┤
│ 应用框架层:AMS/WMS/ContentProvider│ ← 系统服务
├──────────────────────────────────┤
│ 系统运行库:ART(执行引擎) │ ← Kotlin/Java 字节码在这跑
│ + 原生库(C/C++) │
├──────────────────────────────────┤
│ Linux 内核:进程/内存/驱动 │ ← 地基
└──────────────────────────────────┘
【进程与沙箱(移动端和桌面最大的不同)】
每个 App 一个进程 + 独立沙箱(互相隔离)
系统内存不足 → 杀后台进程(按优先级:前台>可见>服务>后台)
→ 所以 App 要「保存状态」:进程可能随时被回收!
【Android 版本碎片化(永恒的痛)】
系统版本多(Android 8~15+)、厂商定制多(MIUI/ColorOS…)
→ 适配是 Android 开发日常(第10站)
【ART vs JVM(第十四本对比)】
桌面 Java:JVM 跑,一次编译到处运行
Android:ART 跑,AOT 预编译到设备指令(更快启动)
【构建产物】
.apk(安装包)/ .aab(上架用,Google Play 分包)
系统架构术语
- Linux 内核 / 沙箱:地基 / 进程隔离——安全的基础。
- ART 运行时:Android 的执行引擎——JIT+AOT 混合编译。
- 系统服务:AMS / WMS——App 和系统的「窗口」。
- 进程优先级:后台进程会被系统回收——状态保存的必要性。
- apk / aab:安装包 / 上架分包格式。
- 版本碎片化:多版本多厂商——Android 适配的宿命。
本站收获:平台认知 = 每个 App 一个沙箱进程 + ART 跑代码 + 系统按优先级杀进程 + 版本碎片化。理解了「进程会被杀」,就理解了 Android 一半的设计。
Kotlin:现代语言
空安全 · 扩展函数 · data class · 协程 · 与 Java 互通Kotlin 是 Android 的「官方语言」——比 Java 更简洁、更安全(空指针不再是噩梦)。你有 Java 底子,学 Kotlin 就像「从手动挡换自动挡」。
Kotlin 四大爽点:
- 空安全:类型分「可空」和「不可空」(String vs String?)——编译器就拦住空指针(Java 的 NPE 噩梦拜拜)。
- 扩展函数:给已有类「加方法」——`fun String.isPhone(): Boolean`——不用继承不用改源码。
- data class:一行声明 + 自动生成 equals/hashCode/toString——Java 的 POJO 样板代码全没了。
- 协程:轻量级并发——比线程轻得多(第 7 站主角);还有 lambda、作用域函数(let/apply/run)。
关键:Kotlin 和 Java 100% 互操作——你学的 Java 库(Retrofit 等)直接能用。
// Java: 一个简单的类要写一大坨
// public class User { private String name; ... getter/setter ... }
// Kotlin: data class 一行搞定
data class User(
val id: Long,
val name: String,
val phone: String? // 可空类型:可能为 null
)
// 空安全:编译器强制处理 null
fun greet(user: User?) {
println(user?.name ?: "匿名用户") // 空安全调用 + 兜底
// user!!.name // 强制非空(危险,少用)
}
// 扩展函数:给 String 加方法
fun String.isValidPhone(): Boolean =
length == 11 && startsWith("1")
// 作用域函数
val name = user?.let { it.name } ?: "unknown" // let:非空才执行
user?.apply { /* 在对象上下文里操作 */ }
// lambda:函数式(第一本老朋友)
val adults = users.filter { it.age >= 18 }
.map { it.name }
【Kotlin 与 Java 关系】
互操作:Kotlin 能调 Java 库,Java 也能调 Kotlin
编译:都编译成 JVM 字节码(Android 上由 ART 执行)
趋势:新项目 Kotlin 是默认,Java 老项目继续维护
【协程预告(第7站)】
suspend fun fetchData() = withContext(Dispatchers.IO) { ... }
→ 挂起不阻塞,比线程轻 1000 倍
Kotlin 术语
- 空安全(? 与 ?:):可空类型 + Elvis 运算符——NPE 从运行时移到编译期。
- 扩展函数:给类加方法不继承——String.isValidPhone()。
- data class:自动 equals/hashCode/toString——POJO 终结者。
- 作用域函数:let / apply / run / with——简洁的上下文操作。
- lambda / 函数式:filter/map——和 Python 同款思路。
- 协程(suspend):轻量并发——Android 异步标准(第 7 站)。
- 互操作:Kotlin ↔ Java 无缝——学过的 Java 全用得上。
本站收获:Kotlin = 空安全(NPE 绝迹)+ 扩展函数(简洁)+ data class(省代码)+ 协程(轻并发)。Java 底子 + Kotlin 语法 = Android 开发的语言准备完毕。
四大组件:应用骨架
Activity · Service · 广播 · ContentProvider · 生命周期Android App 的「骨架」由四大组件搭成——每个组件都有自己的一套生命周期规则。搞懂它们,就搞懂了 App 的「活法」。
四大组件各司其职:
- Activity:一个「界面」——看得见的页面。生命周期是面试必考:onCreate(创建)→ onStart(可见)→ onResume(可交互)→ onPause(失去焦点)→ onStop(不可见)→ onDestroy(销毁)。
- Service:后台任务——播放音乐、下载文件(没界面的活)。
- BroadcastReceiver(广播):收「系统/应用消息」——开机、网络变化、电量低(系统事件的通知)。
- ContentProvider(内容提供者):跨 App 共享数据——通讯录、相册就是这样开放的(注意权限)。
Intent 是组件间的「信使」——启动 Activity、发广播、起 Service 全靠它。
用户打开 App
↓
onCreate(创建:初始化 UI/数据)——只调一次
↓
onStart(可见:还没可交互)
↓
onResume(可交互:前台焦点)── 返回 ──┐
↓ │
onPause(失去焦点:弹窗/来电话) │
↓ │
onStop(不可见:切后台)──────────────┤
↓ │
onDestroy(销毁:进程被回收/用户关闭) │
→ 系统可能随时回收后台进程(第1站)────┘
【为什么生命周期重要】
旋转屏幕 → Activity 销毁重建 → 数据要保存(onSaveInstanceState)
切后台 → 音乐 Service 继续播(Service 的用武之地)
内存不足 → 系统杀后台 → 回来要恢复状态
【Kotlin 实现(简化)】
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { ... } // Compose UI(第4站)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putString("note", currentNote) // 保存状态
super.onSaveInstanceState(outState)
}
}
【Service 类型】
前台服务(有通知,用户看得见——播放器)
后台服务(WorkManager 管理——约束触发,现代推荐)
【Intent 三兄弟】
显式 Intent:指定目标组件(App 内跳转)
隐式 Intent:描述动作,系统匹配(打开网页/分享)
PendingIntent:留待将来执行的 Intent(通知点击)
组件术语
- Activity:界面 + 生命周期——App 的门面。
- Service:后台任务——没界面的活。
- BroadcastReceiver:系统/应用事件通知——广播消息。
- ContentProvider:跨 App 数据共享——通讯录模式。
- Intent:组件间的信使——显式/隐式。
- 生命周期:onCreate → onResume → onPause → onDestroy——面试必背。
- 状态保存:旋转/回收后的数据恢复——onSaveInstanceState。
本站收获:四大组件 = Activity(界面)+ Service(后台)+ 广播(通知)+ ContentProvider(共享),Intent 当信使。生命周期是 Android 的「宪法」——背熟它,就懂了一半 Android。
UI 开发:Jetpack Compose
声明式 UI · Composable · 状态驱动 · Material Design老 Android 用 XML 布局 + findViewById;现代 Android 用 Jetpack Compose——声明式 UI,像写函数一样写界面,状态一变界面自动更新。
Jetpack Compose 是 Android 的现代 UI 框架,核心思想:
- 声明式:界面 = f(状态)——用 Kotlin 函数声明 UI,不用 XML 布局。
- 状态驱动:状态(state)一变,用到的界面自动重组(recompose)——不用手动 findViewById + setText。
- Composable 函数:`@Composable` 标注的可组合函数——界面的「积木」。
- Material Design:官方设计语言——组件库现成(按钮/卡片/主题)。
列表用 LazyColumn(虚拟化,几十万条不卡);状态管理用 remember + mutableStateOf(局部)或 ViewModel(全局,第 6 站)。
@Composable
fun OrderListScreen(viewModel: OrderViewModel) {
val orders by viewModel.orders.collectAsState() // 状态订阅
LazyColumn { // 虚拟化列表
items(orders) { order ->
OrderCard(order) // 每行一个卡片
}
}
}
@Composable
fun OrderCard(order: Order) {
Card(elevation = 4.dp, modifier = Modifier.padding(8.dp)) {
Row(Modifier.padding(16.dp)) {
Column(Modifier.weight(1f)) {
Text(order.title, style = MaterialTheme.typography.titleMedium)
Text("¥${order.amount}", color = MaterialTheme.colorScheme.primary)
}
Icon(Icons.Default.Add, contentDescription = null)
}
}
}
// 状态驱动:点一下按钮,界面自动更新
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) } // 状态
Button(onClick = { count++ }) {
Text("点击了 $count 次") // 状态变 → 自动重组
}
}
【Compose vs 老 View 体系】
老(XML + findViewById):手动找控件、手动更新——代码多
Compose:声明式函数 + 状态自动驱动——代码少、好维护
→ 新项目 Compose 是默认,老项目 View 继续维护
【UI 组件速查】
布局:Column / Row / Box(线性/横向/层叠)
常用:Text / Button / Card / Icon / Image
列表:LazyColumn / LazyRow(虚拟化)
主题:MaterialTheme(颜色/字体/形状统一)
UI 术语
- Jetpack Compose:现代声明式 UI 框架——Android 的新默认。
- Composable:@Composable 函数——界面的积木。
- 状态驱动:mutableStateOf + 自动重组——UI 随状态变。
- remember:记住状态——重组不丢数据。
- LazyColumn:虚拟化列表——大数据量不卡。
- Material Design:官方设计语言——主题/组件现成。
- Modifier:布局修饰链——padding/weight/clickable。
本站收获:Compose = 声明式函数写界面 + 状态驱动自动更新 + LazyColumn 管列表。UI 从「手动档」变「自动档」——界面即函数,状态即真相。
数据层:网络与存储
Retrofit/OkHttp · Room · DataStore · 序列化App 的数据从哪来?——网络(Retrofit 调后端)+ 本地(Room 存数据库)。这一站,移动端的数据层三件套。
数据层四件套:
- Retrofit + OkHttp:网络请求事实标准——接口声明成 Kotlin 函数,自动解析 JSON(第十六本 FastAPI 的客户端版)。
- Room:SQLite 的官方 ORM——编译期校验 SQL、返回 Flow/协程结果(第十六本 SQLAlchemy 的 Android 版)。
- DataStore:轻量键值存储——替代老 SharedPreferences(存设置/偏好)。
- 序列化:kotlinx.serialization / Gson / Moshi——JSON ↔ 对象的互转。
架构模式:Repository(仓库)——统一数据入口,内部决定「先查缓存还是先查网络」(第 6 站 MVVM 的主角之一)。
// ① Retrofit:接口声明(自动发请求 + 解析 JSON)
interface LedgerApi {
@GET("api/orders")
suspend fun getOrders(): List<Order> // 协程挂起函数
@POST("api/orders")
suspend fun createOrder(@Body order: Order): Order
}
val retrofit = Retrofit.Builder()
.baseUrl("https://api.ledger.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
// ② Room:本地数据库
@Entity
data class OrderEntity(
@PrimaryKey val id: Long,
val title: String,
val amount: Double,
)
@Dao
interface OrderDao {
@Query("SELECT * FROM orders")
fun observeAll(): Flow<List<OrderEntity>> // Flow 观察变化
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(orders: List<OrderEntity>)
}
// ③ Repository:统一数据入口(缓存优先)
class OrderRepository(
private val api: LedgerApi,
private val dao: OrderDao,
) {
val orders: Flow<List<OrderEntity>> = dao.observeAll()
suspend fun refresh() {
val remote = api.getOrders() // 拉网络
dao.upsert(remote.map { it.toEntity() }) // 存本地
// 界面观察 dao → 自动更新(缓存优先架构)
}
}
【数据流设计】
界面 → ViewModel → Repository → (网络/数据库)
离线可用:先读 Room 缓存 → 后台刷新 → 界面自动更新
→ 和第十六本 Repository 模式一模一样!
数据层术语
- Retrofit / OkHttp:网络请求标准——接口即函数。
- Room(SQLite ORM):本地数据库——编译期校验 SQL。
- DataStore:轻量存储——设置/偏好。
- 序列化:Gson/Moshi/kotlinx——JSON 互转。
- Repository 模式:统一数据入口——缓存优先。
- Flow 观察:数据库变化自动推送——响应式 UI 的数据源。
- 离线优先:本地缓存 + 后台刷新——弱网也流畅。
本站收获:数据层 = Retrofit 拉网络 + Room 存本地 + Repository 统一入口(缓存优先)。和第十六本后端的 Repository/MVVM 完全同构——移动端只是「多了一层本地缓存」。
Jetpack 架构:MVVM
ViewModel · Flow · MVVM · Hilt · WorkManagerApp 的代码怎么组织才不乱?官方推荐 MVVM 架构——UI 和逻辑分离,配置变更(旋转屏幕)不丢数据,依赖注入自动管理。
MVVM 四层(官方推荐架构):
- UI 层:Compose 界面——只负责展示 + 转发用户操作(不写业务)。
- ViewModel:持有状态 + 业务逻辑——配置变更/旋转屏幕时不销毁(Activity 没了它还活着)。
- Repository:数据统一入口(第 5 站讲过)——网络/缓存的决策者。
- 数据层:Retrofit / Room——最底层。
配套:Hilt(依赖注入——Dagger 的官方封装,自动提供对象,第十四本 Spring IoC 的 Android 版)和 WorkManager(后台任务——延迟/约束触发,系统帮忙调度)。
// ① UI 层:Compose 只做展示和转发
@Composable
fun OrderScreen(viewModel: OrderViewModel) {
val uiState by viewModel.uiState.collectAsState()
when (val s = uiState) {
is Loading -> CircularProgressIndicator()
is Success -> OrderList(s.orders, onRefresh = viewModel::refresh)
is Error -> Text("出错了:${s.message}")
}
}
// ② ViewModel:状态 + 业务逻辑(旋转不销毁!)
class OrderViewModel(
private val repo: OrderRepository,
) : ViewModel() {
val uiState: StateFlow<UiState> = repo.orders
.map { UiState.Success(it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), Loading)
fun refresh() = viewModelScope.launch { repo.refresh() }
}
// ③ Hilt:依赖注入(不用手动 new!)
@HiltViewModel
class OrderViewModel @Inject constructor(
private val repo: OrderRepository,
) : ViewModel()
【MVVM 数据流(单向)】
用户操作 → ViewModel(改状态)→ UI 自动更新
→ 逻辑不写 UI 里、状态不散落在界面——好测、好维护
【为什么 ViewModel 重要】
旋转屏幕 Activity 重建——ViewModel 存活,数据不丢
进程被系统杀了(第1站)——ViewModel 也能配合 SavedState 恢复
【WorkManager 场景】
上传日志(延迟+联网才跑)、每日提醒、数据库清理
→ 系统调度,省电,App 被杀也会补跑
架构术语
- MVVM:UI + ViewModel + Repository + 数据层——官方推荐架构。
- ViewModel:状态持有者——配置变更不销毁。
- StateFlow / UiState:响应式状态流——UI 订阅自动更新。
- Hilt:依赖注入——自动组装对象(Spring IoC 的 Android 版)。
- WorkManager:后台任务调度——延迟/约束触发。
- 单向数据流:UI → 逻辑 → 状态 → UI——清晰可测。
- viewModelScope:ViewModel 的生命周期作用域——协程自动取消。
本站收获:MVVM = UI 只管显示 + ViewModel 管状态 + Repository 管数据 + Hilt 自动装配。和第十六本后端的分层思想一模一样——架构的尽头都是「职责分离」。
协程:异步与并发
Dispatchers · 结构化并发 · Flow · 挂起函数网络请求不能在主线程跑(会卡 UI、甚至 ANR);也不能用「回调地狱」嵌套。Kotlin 协程给出优雅答案:像写同步代码一样写异步。
协程三件套:
- 挂起函数(suspend):可以「暂停」的函数——等网络时不阻塞线程,像同步代码一样写。
- Dispatchers(调度器):决定代码跑在哪个线程——Main(UI 线程)/ IO(网络/数据库)/ Default(CPU 计算)。
- 作用域(Scope):协程的生命周期容器——viewModelScope 等,页面销毁自动取消(防内存泄漏)。
进阶:Flow——响应式数据流(冷流:订阅才执行),Room 返回 Flow 实时推送数据库变化(第 5 站老朋友);structured concurrency——协程自动管理父子关系,异常不会静默。
// 错误示范:主线程发网络请求 → 卡死 + ANR!
// 正确姿势:挂起函数 + 切换调度器
class OrderRepository(private val api: LedgerApi) {
// suspend:可挂起的网络请求(Retrofit 原生支持)
suspend fun fetchOrders(): List<Order> =
withContext(Dispatchers.IO) { // IO 线程跑网络
api.getOrders()
}
}
// ViewModel 里调用(自动在主线程安全更新 UI)
class OrderViewModel(private val repo: OrderRepository) : ViewModel() {
fun load() = viewModelScope.launch { // 主线程作用域
val orders = repo.fetchOrders() // 自动切 IO,回来切 Main
uiState.value = UiState.Success(orders) // UI 更新安全
}
}
// Flow:响应式数据流
val latestOrders: Flow<List<Order>> = flow {
while (true) {
emit(repo.fetchOrders()) // 每 30 秒发一次
delay(30_000)
}
}.flowOn(Dispatchers.IO) // 上游在 IO 跑
【协程 vs 线程(第十六本老朋友)】
线程:重量级(1 个线程 ≈ 1MB 栈)——几万个就爆
协程:轻量级(挂起不占线程)——几十万个都行
→ Android 默认用协程(asyncio 的 Kotlin 版)
【结构化并发】
viewModelScope:界面销毁 → 协程自动取消 → 不泄漏
SupervisorJob:子协程失败不影响兄弟
【ANR 预防】
主线程只做 UI——超过 5 秒不响应 = ANR(第9站)
协程术语
- suspend 挂起函数:可暂停的异步代码——像同步一样写。
- Dispatchers:Main / IO / Default——线程调度器。
- viewModelScope:生命周期作用域——自动取消防泄漏。
- withContext:切换调度器——IO 干活、主线程更新。
- Flow:响应式数据流——冷流订阅即执行。
- 结构化并发:父子协程自动管理——异常不静默。
- ANR:主线程卡 5 秒——应用无响应(第 9 站)。
本站收获:协程 = suspend 写异步 + Dispatchers 管线程 + Scope 管生命周期 + Flow 做响应式。Android 异步的标准答案——「挂起不阻塞,取消不漏泄」。
工程化:构建与上架
Gradle · AGP · 签名 · 多渠道 · 上架流程代码写好了,怎么变成用户手机里的 App?Gradle 构建、签名、打包、上架审核——移动端的「最后一公里」全是规矩。
工程化六件事:
- Gradle + AGP:构建系统(第十六本 Maven/Gradle 的老朋友)——依赖管理 + 编译打包。
- 模块化:app 模块 + 多个 library 模块(core/network/ui)——大型项目必备。
- 签名:APK 必须有签名才能安装——密钥要保管好(丢了无法更新!)。
- 多渠道打包:国内上架每个应用商店一个包(友盟/腾讯/华为...)——不同渠道号统计来源。
- 混淆 / R8:代码压缩混淆——减小体积 + 防逆向(专业版加固)。
- 上架流程:Google Play(aab 格式)/ 国内各商店——隐私政策、审核、更新。
【Gradle 配置(build.gradle.kts)】
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.ledger.app"
compileSdk = 35 // 编译版本(对应 Android 15)
defaultConfig {
applicationId = "com.ledger.app"
minSdk = 24 // 最低支持 Android 7.0
targetSdk = 35 // 目标版本(上架要求新)
versionCode = 12 // 版本号(更新递增)
versionName = "2.1.0" // 版本名(给用户看)
}
buildTypes {
release {
isMinifyEnabled = true // R8 混淆压缩
}
}
}
【构建命令】
./gradlew assembleDebug # 调试包(可直接安装)
./gradlew assembleRelease # 发布包(签名+混淆)
./gradlew bundleRelease # aab(Google Play 上架用)
【签名(保命)】
生成:keytool / Android Studio 生成 keystore
保管:丢了 = 以后无法更新同一个 App!
国内商店要签名 + 部分要软著证书
【国内上架流程(简化)】
准备:隐私政策 + 软著 + 应用截图 + 签名包
提交各商店(华为/小米/OPPO/vivo/腾讯应用宝…)
审核(隐私合规是重灾区,第10站)
发布后:版本更新、崩溃监控(第9站)
【版本适配提示】
targetSdk 必须跟随最新(商店强制要求)
minSdk 决定覆盖多少设备(24 = 覆盖 95%+)
工程化术语
- Gradle / AGP:构建系统——依赖 + 编译 + 打包。
- minSdk / targetSdk / compileSdk:最低支持 / 目标版本 / 编译版本。
- 签名(keystore):安装凭证——丢了无法更新。
- R8 混淆:压缩 + 混淆——减小体积防逆向。
- 多渠道打包:各商店独立包 + 渠道统计。
- aab vs apk:Play 分包格式 vs 通用安装包。
- 上架审核:隐私政策 + 合规是重灾区(第 10 站)。
本站收获:工程化 = Gradle 构建 + 模块化 + 签名保命 + 多渠道 + R8 混淆 + 上架审核。开发只是前半场,构建、签名、审核——移动端的规矩比后端多得多。
测试质量:让 App 稳
单元测试 · UI 测试 · ANR · 启动优化 · 崩溃监控App 崩一次,用户流失一批。移动端的质量 = 测试 + 性能 + 监控三件套——这一站,让 App「稳」。
质量三件套:
- 测试:单元测试(JUnit + Mockito 测 ViewModel/业务,第十六本老朋友)+ UI 测试(Compose Test 点按钮断言界面)。
- 性能:ANR(主线程卡 5 秒——别在主线程干重活)、启动优化(冷启动 2 秒内)、内存泄漏(LeakCanary 检测)、帧率(掉帧卡顿)。
- 监控:崩溃监控(Crashlytics)——用户崩溃自动上报 + 堆栈分析;埋点——用户行为统计(第八本老朋友)。
// ① 单元测试:ViewModel 业务逻辑
@Test
fun `订单金额格式化正确`() {
val vm = OrderViewModel(mockRepo)
assertEquals("¥1,299.00", vm.formatAmount(1299.00))
}
// ② UI 测试(Compose Test)
@get:Rule
val composeRule = createComposeRule()
@Test
fun `点击按钮列表更新`() {
composeRule.setContent { Counter() }
composeRule.onNodeWithText("点击了 0 次").assertExists()
composeRule.onNodeWithText("点击了 0 次").performClick()
composeRule.onNodeWithText("点击了 1 次").assertExists()
}
【性能三巨头(移动端特有)】
ANR:主线程 5 秒无响应 → 系统弹「应用无响应」
预防:主线程只做 UI;重活走协程 IO(第7站)
冷启动:点击图标到首帧——目标 < 2 秒
优化:启动任务异步化、延迟初始化、启动页
内存泄漏:Activity 被持有不释放 → OOM
检测:LeakCanary 自动定位泄漏点
【崩溃监控(Crashlytics)】
集成 SDK → 用户崩溃自动上报
→ 崩溃率看板 + 堆栈定位 + 版本对比
→ 发布后盯三天:崩溃率 > 0.5% 要紧急修复
【发布质量门槛】
单元测试覆盖率 > 60%(核心逻辑 100%)
无内存泄漏告警(LeakCanary 全绿)
崩溃率 < 0.5%(新版本观察期)
启动 < 2 秒 / 掉帧率 < 5%
质量术语
- JUnit / Mockito:单元测试——测 ViewModel 逻辑。
- Compose Test:UI 测试——点按钮断言界面。
- ANR:主线程 5 秒无响应——移动端最著名的错误。
- 冷启动优化:启动 < 2 秒——第一印象。
- LeakCanary:内存泄漏自动检测——OOM 的克星。
- Crashlytics:崩溃监控——用户崩溃自动上报。
- 掉帧 / 帧率:60fps 目标——卡顿的可视化指标。
本站收获:质量 = 单测测逻辑 + UI 测交互 + ANR/泄漏/启动三座性能山 + Crashlytics 盯崩溃。移动端没有「重启一下」的机会——用户走了就不会回来。
安全适配:江湖规矩
运行时权限 · 版本适配 · 屏幕适配 · 隐私合规Android 的「江湖规矩」特别多:权限要运行时申请、系统版本碎片化要适配、隐私合规是上架生死线。这一站,把规矩讲完。
四大规矩:
- 运行时权限:Android 6.0+ 权限「动态申请」——用到时弹窗问用户(相机/定位/通讯录);拒绝要优雅降级(第八本安全 + 第七本的老朋友)。
- 版本适配:系统碎片化(Android 8~15+)+ 厂商定制——用兼容库(AndroidX/Jetpack)自动处理大部分差异;targetSdk 跟随最新(商店强制)。
- 屏幕适配:手机/平板/折叠屏——用 dp 单位 + ConstraintLayout/Compose 自适应 + 多资源限定符。
- 隐私合规:个保法(第八本老朋友)——隐私政策、权限最小化、告知同意;国内商店审核重灾区:违规收集个人信息 = 下架。
【运行时权限(Kotlin 简版)】
if (ContextCompat.checkSelfPermission(this, CAMERA) != GRANTED) {
requestPermissions(arrayOf(CAMERA), 100) // 弹窗申请
} else {
openCamera()
}
// 用户拒绝 → 引导去设置页 / 功能降级(别硬来)
【版本适配策略】
用 AndroidX/Jetpack 兼容库(自动处理 90% 差异)
targetSdk 跟上最新(商店强制 + 新行为适配)
老版本降级:功能检测(Build.VERSION.SDK_INT)按需分支
【屏幕适配】
单位用 dp(密度无关)/ sp(文字)——别用 px!
Compose 自适应布局(BoxWithConstraints / 窗口尺寸)
多资源:values-sw600dp(平板)/ values-night(深色模式)
【隐私合规清单(上架生死线)】
□ 隐私政策在应用内可查 + 首次弹窗告知
□ 权限最小化(不申请用不到的权限!)
□ 收集数据要「告知-同意」+ 提供删除渠道(第八本)
□ 敏感权限(通讯录/位置)用途说明
□ SDK 合规(第三方 SDK 也要审——不合规被通报)
【数据安全(第7本老朋友)】
HTTPS 全链路(明文流量默认禁止)
敏感数据加密存储(EncryptedSharedPreferences)
WebView 安全(禁用 file 访问、白名单域名)
轻记账提交应用商店被拒:理由「违规收集个人信息」。整改:① 裁剪权限(去掉用不到的定位)→ ② 隐私政策弹窗 + 说明用途 → ③ 第三方 SDK 过一遍合规审计 → ④ 提交复审核通过。合规不是加分项,是「入场券」——第八本讲过的个保法,在移动端是生死线。
适配术语
- 运行时权限:用到才申请 + 优雅降级——Android 6.0+ 的规矩。
- 版本碎片化:多系统版本 + 厂商定制——AndroidX 兼容库来扛。
- dp / sp:密度无关单位——屏幕适配的地基。
- 隐私合规:个保法 + 商店审核——权限最小化 + 告知同意。
- 数据安全:HTTPS + 加密存储 + WebView 白名单。
- targetSdk:目标版本——商店强制跟随。
- 合规生死线:违规收集个人信息 = 下架。
本站收获:规矩 = 权限动态申请 + AndroidX 扛碎片化 + dp 管适配 + 个保法守合规。Android 开发一半是技术,一半是「守规矩」——守住了,才能上架、才能活。
知识点速查 + 面试高频题
面试/上架前最后一页Android 知识全景表
| 领域 | 核心知识点 | 一句话人话 |
|---|---|---|
| 系统架构 | Linux 内核、ART、沙箱进程、版本碎片化 | App 跑在什么上面 |
| Kotlin | 空安全、扩展函数、data class、协程 | 现代语言四爽点 |
| 四大组件 | Activity/Service/广播/ContentProvider + 生命周期 | App 的骨架 |
| UI | Jetpack Compose、声明式、状态驱动、Material | 界面即函数 |
| 数据层 | Retrofit、Room、DataStore、Repository | 网络 + 本地存储 |
| 架构 | MVVM、ViewModel、Hilt、WorkManager | 职责分离最佳实践 |
| 协程 | suspend、Dispatchers、Scope、Flow | 异步不卡 UI |
| 工程化 | Gradle、签名、多渠道、R8、上架 | 从源码到上架 |
| 测试质量 | JUnit、Compose Test、ANR、启动优化、Crashlytics | 让 App 稳 |
| 安全适配 | 运行时权限、AndroidX、dp、隐私合规 | 江湖规矩 |
面试高频题速记
- Activity 生命周期?→ onCreate→onStart→onResume→onPause→onStop→onDestroy(背熟)。
- 为什么旋转屏幕数据会丢?→ Activity 重建——ViewModel 存活 / onSaveInstanceState 保存。
- Kotlin 比 Java 好在哪?→ 空安全、协程、扩展函数、data class、简洁。
- Compose 和 View 区别?→ 声明式 + 状态驱动 vs 命令式 XML。
- 协程 vs 线程?→ 协程轻量挂起不占线程——几十万并发没问题。
- 什么是 ANR?→ 主线程 5 秒无响应——重活走协程 IO。
- MVVM 各层职责?→ UI 显示 + ViewModel 状态 + Repository 数据。
- 为什么用 Hilt?→ 依赖注入——自动组装、易测试(Spring IoC 的 Android 版)。
- App 上架要注意什么?→ 签名、隐私合规、targetSdk、渠道包。
- 如何防内存泄漏?→ LeakCanary 检测 + Activity 引用清理 + 协程作用域取消。
一半是技术,一半是「守规矩」。
系统在碎片化,所以学会适配;
权限要动态申请,所以学会优雅;
合规是生死线,所以学会克制——
移动开发教的不是「写代码」,是「在规矩里写代码」。