本章涵盖 Android App 全维度性能优化:帧率优化与 Jank 检测、内存泄漏分析、冷启动优化与 Baseline Profiles、包体积减小、电量优化,以及 Compose 专项性能调优。
📋 章节目录
| 节 | 主题 |
|---|---|
| 7.1 | 帧率优化(16ms 原则 / Jank 检测 / Systrace) |
| 7.2 | 内存优化(内存泄漏 / LeakCanary / MAT) |
| 7.3 | 启动优化(冷启动 / Baseline Profiles) |
| 7.4 | 包体积优化(R8 / ABI Split / Bundle) |
| 7.5 | 电量与网络优化 |
| 7.6 | Compose 性能调优 |
7.1 帧率优化
16ms 原则与丢帧检测
屏幕刷新率 60Hz → 每帧预算 = 1000ms / 60 = 16.67ms
屏幕刷新率 120Hz → 每帧预算 = 1000ms / 120 = 8.33ms
帧渲染两阶段:
Main Thread(UI Thread):测量、布局、Record DisplayList
RenderThread(硬件加速):执行 DisplayList,提交 GPU
Jank(丢帧)= 某帧用时超过预算,导致跳帧/卡顿
kotlin
// 检测主线程卡顿:BlockCanary / 自定义 Choreographer 回调
class FrameMonitor {
private var lastFrameTime = 0L
private val frameCallback = Choreographer.FrameCallback { frameTimeNanos ->
if (lastFrameTime != 0L) {
val duration = (frameTimeNanos - lastFrameTime) / 1_000_000 // ns → ms
if (duration > 16) {
Log.w("FrameMonitor", "Jank detected! Frame took ${duration}ms")
}
}
lastFrameTime = frameTimeNanos
Choreographer.getInstance().postFrameCallback(this)
}
fun start() {
Choreographer.getInstance().postFrameCallback(frameCallback)
}
fun stop() {
Choreographer.getInstance().removeFrameCallback(frameCallback)
}
}
// 避免主线程 IO / 网络 / 数据库
// ❌ 在主线程执行耗时操作
class BadFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val result = database.query("SELECT * FROM products") // ANR 风险!
updateUI(result)
}
}
// ✅ 协程切换线程
class GoodFragment : Fragment() {
private val viewModel: ProductViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.products.collect { products ->
// 已在主线程
adapter.submitList(products)
}
}
}
}
}
RecyclerView 性能优化
kotlin
class ProductAdapter : ListAdapter<Product, ProductViewHolder>(ProductDiffCallback()) {
// ✅ DiffUtil:只更新变化的 Item
class ProductDiffCallback : DiffUtil.ItemCallback<Product>() {
override fun areItemsTheSame(old: Product, new: Product) = old.id == new.id
override fun areContentsTheSame(old: Product, new: Product) = old == new
}
// ✅ 使用 RecycledViewPool 在多个 RecyclerView 间共享 ViewHolder
fun setupSharedPool(recyclerView1: RecyclerView, recyclerView2: RecyclerView) {
val sharedPool = RecyclerView.RecycledViewPool()
sharedPool.setMaxRecycledViews(0, 20)
recyclerView1.setRecycledViewPool(sharedPool)
recyclerView2.setRecycledViewPool(sharedPool)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
val binding = ItemProductBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ProductViewHolder(binding)
}
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
// RecyclerView 配置
recyclerView.apply {
// ✅ 固定大小不变时设置此项(跳过 requestLayout)
setHasFixedSize(true)
// ✅ 预加载更多 Item(避免滑动时加载)
(layoutManager as LinearLayoutManager).initialPrefetchItemCount = 6
// ✅ 图片加载用 Glide 并预拉取
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(rv: RecyclerView, newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
Glide.with(context).resumeRequests()
} else {
Glide.with(context).pauseRequests()
}
}
})
}
7.2 内存优化
常见内存泄漏场景
kotlin
// ❌ 场景1:静态持有 Context(最常见)
object BadSingleton {
var context: Context? = null // 持有 Activity Context → 内存泄漏!
}
// ✅ 使用 Application Context
object GoodSingleton {
private lateinit var appContext: Context
fun init(context: Context) { appContext = context.applicationContext }
}
// ❌ 场景2:内部类持有外部类引用
class BadActivity : AppCompatActivity() {
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
// 匿名内部类隐式持有 BadActivity 的引用
updateUI() // Activity 已销毁时仍持有引用
}
}
}
// ✅ 使用静态内部类 + 弱引用
class GoodActivity : AppCompatActivity() {
private val handler = MyHandler(WeakReference(this))
private class MyHandler(private val activityRef: WeakReference<GoodActivity>)
: Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
activityRef.get()?.updateUI()
}
}
private fun updateUI() { /* ... */ }
}
// ❌ 场景3:未取消的协程
class BadViewModel : ViewModel() {
fun startJob() {
GlobalScope.launch { // 不受 ViewModel 生命周期管理!
delay(10_000)
// ViewModel 已销毁,仍在运行
}
}
}
// ✅ 使用 viewModelScope
class GoodViewModel : ViewModel() {
fun startJob() {
viewModelScope.launch { // ViewModel 销毁时自动取消
delay(10_000)
// 安全
}
}
}
// ❌ 场景4:BroadcastReceiver 未注销
class BadActivity2 : AppCompatActivity() {
private val receiver = object : BroadcastReceiver() { override fun onReceive(c: Context, i: Intent) {} }
override fun onResume() {
super.onResume()
registerReceiver(receiver, IntentFilter("ACTION"))
// 忘记在 onPause 中注销!
}
}
// ✅ 配对注册/注销
class GoodActivity2 : AppCompatActivity() {
private val receiver = object : BroadcastReceiver() { override fun onReceive(c: Context, i: Intent) {} }
override fun onResume() { super.onResume(); registerReceiver(receiver, IntentFilter("ACTION")) }
override fun onPause() { super.onPause(); unregisterReceiver(receiver) }
}
// ❌ 场景5:Fragment ViewBinding 泄漏
class BadFragment : Fragment() {
private val binding: FragmentBadBinding by lazy {
FragmentBadBinding.inflate(layoutInflater) // View 销毁后 binding 仍持有 View 引用
}
}
// ✅ onDestroyView 清空 binding
class GoodFragment : Fragment() {
private var _binding: FragmentGoodBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View {
_binding = FragmentGoodBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null // ✅ 必须清空
}
}
LeakCanary 集成
kotlin
// build.gradle.kts
dependencies {
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
}
// LeakCanary 会自动安装(无需代码配置)
// 检测到泄漏时会显示通知
// 自定义泄漏观察
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (LeakCanary.isInAnalyzerProcess(this)) return
// 添加自定义对象监听(观察是否被回收)
AppWatcher.objectWatcher.expectWeaklyReachable(
object = someObject,
description = "SomeObject should be collected after screen close"
)
}
}
图片内存优化
kotlin
// Glide 配置
@GlideModule
class MyGlideModule : AppGlideModule() {
override fun applyOptions(context: Context, builder: GlideBuilder) {
// 内存缓存大小
val memoryCacheSizeMB = 50
builder.setMemoryCache(LruResourceCache(memoryCacheSizeMB * 1024 * 1024L))
// 磁盘缓存
builder.setDiskCache(
InternalCacheDiskCacheFactory(context, "image_cache", 250 * 1024 * 1024L)
)
// 内存紧张时的策略
builder.setDefaultRequestOptions(
RequestOptions()
.format(DecodeFormat.PREFER_RGB_565) // RGB_565 比 ARGB_8888 少一半内存
.disallowHardwareConfig() // 在需要软件渲染时
)
}
override fun isManifestParsingEnabled() = false
}
// 按需加载合适尺寸
Glide.with(imageView)
.load(url)
.override(imageView.width, imageView.height) // 只加载需要的尺寸
.thumbnail(0.1f) // 先加载 10% 缩略图
.placeholder(R.drawable.ic_placeholder)
.error(R.drawable.ic_error)
.into(imageView)
7.3 启动优化
冷启动流程
用户点击图标
↓
Zygote fork(~50ms)
↓
Application.onCreate()(你的代码)
↓
Activity.onCreate() → onStart() → onResume()
↓
View 测量/布局/绘制
↓
第一帧显示 → TTFD(Time to First Display)
↓
数据加载完成 → TTCD(Time to Complete Display)
kotlin
// ✅ 启动优化实践
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// ✅ 1. 只在主进程初始化
if (isMainProcess()) {
// ✅ 2. 按优先级初始化:关键 → 次要 → 懒加载
initCritical() // 同步:Hilt、Database
initAsync() // 异步:Analytics、推送
}
}
private fun initCritical() {
// 快速初始化(<10ms)
}
private fun initAsync() {
// 在后台线程初始化
thread(name = "init-thread") {
initAnalytics()
initCrashReporter()
initPushService()
}
}
private fun isMainProcess(): Boolean {
return processName == packageName
}
private val processName: String
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
getProcessName()
} else {
// 旧版方式
ActivityManager::class.java.getMethod("getMyMemoryState").let { "" }
}
private fun initAnalytics() { /* ... */ }
private fun initCrashReporter() { /* ... */ }
private fun initPushService() { /* ... */ }
}
App Startup(Jetpack 启动库)
kotlin
// build.gradle.kts
implementation("androidx.startup:startup-runtime:1.1.1")
// 定义 Initializer
class TimberInitializer : Initializer<Unit> {
override fun create(context: Context) {
if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
}
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
}
class AnalyticsInitializer : Initializer<Unit> {
override fun create(context: Context) {
Analytics.initialize(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> =
listOf(TimberInitializer::class.java) // 依赖 Timber 先初始化
}
// AndroidManifest.xml
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.example.TimberInitializer"
android:value="androidx.startup" />
<meta-data
android:name="com.example.AnalyticsInitializer"
android:value="androidx.startup" />
</provider>
// 手动触发(懒初始化)
AppInitializer.getInstance(context)
.initializeComponent(AnalyticsInitializer::class.java)
Baseline Profiles(基线配置文件)
kotlin
// build.gradle.kts(app 模块)
plugins {
id("androidx.baselineprofile")
}
dependencies {
baselineProfile(project(":baselineprofile"))
}
// :baselineprofile 测试模块
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generate() {
rule.collect("com.example.myapp") {
// 定义关键用户旅程(热身路径)
pressHome()
startActivityAndWait()
// 滚动列表
device.findObject(By.res("product_list")).scroll(Direction.DOWN, 3)
// 打开详情
device.findObject(By.res("product_item_0")).click()
device.waitForIdle()
}
}
}
// 生成命令
// ./gradlew :baselineprofile:generateBaselineProfile
7.4 包体积优化
R8(混淆 + Tree Shaking)
groovy
// build.gradle(Release 配置)
android {
buildTypes {
release {
isMinifyEnabled = true // 开启代码混淆
isShrinkResources = true // 移除未使用资源(需 minifyEnabled = true)
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
# proguard-rules.pro
# Kotlin 协程
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
# Retrofit
-keepattributes Signature
-keepattributes Exceptions
-keep class retrofit2.** { *; }
-keepclasseswithmembers class * {
@retrofit2.http.* <methods>;
}
# Room
-keep class * extends androidx.room.RoomDatabase
-dontwarn androidx.room.paging.**
# Gson
-keep class com.example.model.** { *; }
# 防止 ViewModel 被混淆
-keep class * extends androidx.lifecycle.ViewModel {
<init>(...);
}
# Hilt
-keep class dagger.hilt.** { *; }
-keep @dagger.hilt.InstallIn class * { *; }
APK 分析与优化
bash
# 分析 APK 内容(命令行)
./gradlew :app:bundleRelease
# Android Studio → Build → Analyze APK
# 查看:
# - classes.dex(代码)
# - res/(资源)
# - lib/(Native 库)
# - assets/(资产文件)
# 按 ABI 分包(减少每个用户下载的大小)
android {
splits {
abi {
isEnable = true
reset()
include("arm64-v8a", "armeabi-v7a", "x86_64")
isUniversalApk = false
}
}
}
# WebP 图片转换(比 PNG 小 25-35%)
# Android Studio → Refactor → Convert to WebP
资源压缩
kotlin
// 保留特定资源(配合 shrinkResources 使用)
// res/raw/keep.xml
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/keep_this,@drawable/keep_this_too"
tools:discard="@layout/unused_layout" />
// 动态特性模块(Dynamic Feature Module)
// 将非核心功能拆分为按需下载的模块
plugins {
id("com.android.dynamic-feature")
}
android {
// 动态特性模块配置
}
7.5 电量与网络优化
WorkManager(后台任务)
kotlin
// 网络同步 Worker
class SyncWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
val syncResult = syncRepository.sync()
if (syncResult.isSuccess) Result.success()
else Result.retry()
} catch (e: NetworkUnavailableException) {
Result.retry()
} catch (e: Exception) {
Result.failure(workDataOf("error" to e.message))
}
}
companion object {
fun schedule(workManager: WorkManager) {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 需要网络
.setRequiresBatteryNotLow(true) // 低电量时不运行
.build()
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
repeatInterval = 1,
repeatIntervalTimeUnit = TimeUnit.HOURS
)
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.addTag("sync")
.build()
workManager.enqueueUniquePeriodicWork(
"periodic_sync",
ExistingPeriodicWorkPolicy.UPDATE,
syncRequest
)
}
// 链式任务
fun scheduleChain(workManager: WorkManager) {
val cleanupRequest = OneTimeWorkRequestBuilder<CleanupWorker>().build()
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>().build()
val notifyRequest = OneTimeWorkRequestBuilder<NotifyWorker>().build()
workManager
.beginWith(cleanupRequest)
.then(syncRequest)
.then(notifyRequest)
.enqueue()
}
}
}
// 监听 WorkInfo
class SyncViewModel @Inject constructor(
private val workManager: WorkManager
) : ViewModel() {
val syncState: LiveData<WorkInfo?> = workManager
.getWorkInfosByTagLiveData("sync")
.map { workInfos -> workInfos.firstOrNull() }
}
网络请求批量处理
kotlin
// 减少请求次数:批量合并
class BatchRequestManager {
private val pendingIds = mutableListOf<Int>()
private var debounceJob: Job? = null
fun requestProduct(id: Int, scope: CoroutineScope, onResult: (List<Product>) -> Unit) {
pendingIds.add(id)
debounceJob?.cancel()
debounceJob = scope.launch {
delay(50) // 等待 50ms 收集批量请求
val ids = pendingIds.toList()
pendingIds.clear()
onResult(api.getProductsBatch(ids))
}
}
}
7.6 Compose 性能调优
减少不必要的重组
kotlin
// ❌ 导致过多重组
@Composable
fun BadList(viewModel: MyViewModel) {
val state by viewModel.state.collectAsState()
// state 中任何字段变化,整个 LazyColumn 都重组
LazyColumn {
items(state.products) { product ->
// 即使 product 未变化,也可能重组
ProductCard(product)
}
}
}
// ✅ 精确订阅需要的数据
@Composable
fun GoodList(viewModel: MyViewModel) {
val products by viewModel.products.collectAsStateWithLifecycle()
LazyColumn {
items(products, key = { it.id }) { product ->
// key 确保 product 未变化时不重组
ProductCard(product)
}
}
}
// ✅ 稳定类型(Stable)减少重组
@Immutable // 告诉 Compose 此类型不可变
data class Product(val id: Int, val name: String, val price: Double)
// ✅ @Stable 用于可能可变但 Compose 可追踪的类型
@Stable
class ShoppingCart {
var items by mutableStateOf(emptyList<CartItem>())
}
// ✅ derivedStateOf:只在计算结果变化时触发重组
@Composable
fun OptimizedScrollToTop(listState: LazyListState) {
val showButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
// 只在 showButton 从 false→true 或 true→false 时重组
AnimatedVisibility(visible = showButton) {
ScrollToTopButton()
}
}
// ✅ remember 缓存昂贵计算
@Composable
fun ExpensiveComputation(products: List<Product>, filter: String) {
val filteredProducts = remember(products, filter) {
// 只在 products 或 filter 变化时重新计算
products.filter { it.name.contains(filter, ignoreCase = true) }
.sortedBy { it.price }
}
// 使用 filteredProducts
}
避免在重组时分配对象
kotlin
// ❌ 每次重组都创建新 lambda(导致 Button 重组)
@Composable
fun BadButton(item: Item) {
Button(onClick = { handleClick(item) }) { Text("点击") }
// onClick lambda 每次重组都是新对象,Button 无法判断是否需要重组
}
// ✅ 使用 remember 包装 lambda
@Composable
fun GoodButton(item: Item, onItemClick: (Item) -> Unit) {
// 将 onClick 提升到父组件,父组件用 remember 缓存
Button(onClick = { onItemClick(item) }) { Text("点击") }
}
// 父组件中
@Composable
fun Parent(viewModel: MyViewModel) {
val onItemClick: (Item) -> Unit = remember(viewModel) {
{ item -> viewModel.onItemClick(item) }
}
GoodButton(item = someItem, onItemClick = onItemClick)
}
// ✅ 避免在 Modifier 中实例化动画规格
@Composable
fun AnimatedItem(visible: Boolean) {
val animSpec = remember { tween<Float>(durationMillis = 300) }
val alpha by animateFloatAsState(if (visible) 1f else 0f, animSpec, label = "alpha")
Box(Modifier.alpha(alpha)) { /* ... */ }
}
Compose Layout Inspector & Recomposition Counter
kotlin
// 开启重组高亮(Android Studio)
// Profiler → Compose → Recomposition Highlighter
// 手动追踪重组次数(调试用)
var recompositionCount = 0
@Composable
fun DebugRecomposition(name: String, content: @Composable () -> Unit) {
SideEffect {
recompositionCount++
Log.d("Compose", "$name recomposed $recompositionCount times")
}
content()
}
Demo 代码:chapter07
kotlin
// chapter07/PerformanceDemo.kt
package com.example.androiddemos.chapter07
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.unit.dp
// 演示1:derivedStateOf 优化
@Composable
fun DerivedStateDemo() {
val listState = rememberLazyListState()
// ✅ derivedStateOf 避免高频重组
val showTopButton by remember {
derivedStateOf { listState.firstVisibleItemIndex > 3 }
}
Box(modifier = Modifier.fillMaxSize()) {
LazyColumn(state = listState, modifier = Modifier.fillMaxSize()) {
items((1..50).toList(), key = { it }) { index ->
ListItem(
headlineContent = { Text("列表项 $index") },
modifier = Modifier.padding(horizontal = 16.dp)
)
Divider()
}
}
AnimatedVisibility(
visible = showTopButton,
modifier = Modifier.align(Alignment.BottomEnd).padding(16.dp)
) {
FloatingActionButton(onClick = {
/* 滚动到顶部 */
}) {
Text("↑")
}
}
}
}
// 演示2:remember 缓存昂贵计算
@Composable
fun RememberOptimizationDemo() {
val allItems = remember { (1..100).map { "产品 $it:¥${it * 9.9}" } }
var filterText by remember { mutableStateOf("") }
// ✅ remember(filterText):只在 filterText 变化时重新过滤
val filteredItems = remember(filterText) {
if (filterText.isBlank()) allItems
else allItems.filter { it.contains(filterText) }
}
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
Text("性能优化 Demo", style = MaterialTheme.typography.headlineSmall)
Spacer(Modifier.height(8.dp))
OutlinedTextField(
value = filterText,
onValueChange = { filterText = it },
label = { Text("搜索过滤") },
modifier = Modifier.fillMaxWidth()
)
Text("${filteredItems.size} 条结果", style = MaterialTheme.typography.bodySmall)
Spacer(Modifier.height(8.dp))
LazyColumn {
items(filteredItems, key = { it }) { item ->
Text(item, modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp))
Divider()
}
}
}
}
// 演示3:动画性能
@Composable
fun AnimationPerformanceDemo() {
var isVisible by remember { mutableStateOf(true) }
val animSpec = remember { tween<Float>(durationMillis = 500) }
val alpha by animateFloatAsState(if (isVisible) 1f else 0f, animSpec, label = "alpha")
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("动画性能 Demo", style = MaterialTheme.typography.headlineSmall)
Spacer(Modifier.height(24.dp))
Card(
modifier = Modifier.size(200.dp).alpha(alpha)
) {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("动画内容", style = MaterialTheme.typography.headlineMedium)
}
}
Spacer(Modifier.height(24.dp))
Button(onClick = { isVisible = !isVisible }) {
Text(if (isVisible) "隐藏(淡出)" else "显示(淡入)")
}
}
}
章节总结
| 知识点 | 必掌握程度 | 面试频率 |
|---|---|---|
| 主线程 / Jank / 16ms 原则 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 内存泄漏 5 大场景 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| LeakCanary 使用 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 冷启动优化 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Baseline Profiles | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| R8 混淆与 shrinkResources | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| WorkManager 约束条件 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Compose derivedStateOf | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Compose @Stable/@Immutable | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| RecyclerView DiffUtil | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
👉 下一章:第八章------工程化与模块化