本文首发地址 https://h89.cn/archives/548.html

这两年聊安卓性能优化,明显能感觉到一个变化:以前大家更爱盯着启动速度、卡顿、内存峰值这些老问题,现在还得把后台约束、电池指标、系统工具链一起放进来考虑。Google 一边在内核侧推进 AutoFDO,一边又在后台任务和功耗治理上持续收紧。对开发者来说,这意味着“性能优化”已经不只是把代码写快,而是要把系统规则也一起吃透。

我自己看下来,2026 年这波变化里,最值得关注的不是又冒出了多少新名词,而是有些事情已经越来越明确了:哪些收益属于系统升级带来的红利,哪些坑还是得靠应用自己填。

这篇文章我按“系统能力 → 后台与功耗 → 内存 → 工具链 → Compose”的顺序往下聊,尽量把那些容易被写空、写偏的地方讲得直白一点。

一、先说我的判断

如果现在让我给一次性能治理排优先级,我会先做这几件事:

  1. 先用 Perfetto、Profiler、Macrobenchmark 找到瓶颈,再决定优化方向
  2. 后台任务优先用系统认可的 API,不要把手动 Wake Lock 当默认方案
  3. 图片、缓存、生命周期对象是内存问题最高发的三个点
  4. Compose 先控制重组范围,再看列表和状态流转
  5. AutoFDO 值得关注,但它主要是系统和内核侧收益,应用开发者更多是“受益者”而不是“直接操作者”

二、AutoFDO 了解一下就够了

AutoFDO 可以简单理解成:系统或内核在编译阶段参考真实运行时的热点路径,去优化代码布局和分支决策。它更偏底层能力,不是普通 App 开发者日常直接操作的东西。

对我们来说,知道这件事有两个意义就够了:

  • Android 系统层还在继续做性能优化,底层红利会慢慢传导到设备体验上
  • 这类优化替代不了应用自己的启动优化、线程治理、内存治理

所以这一节不用看太深,把它当成 2026 年安卓性能趋势里的一个背景信息就行。

三、后台与功耗:别把 Wake Lock 当常规方案

国内大多数应用并不上架 Play,没必要把 Google Play 的规则研究得太细。但这部分照样要重视,因为后台任务写得不克制,最后都会落到发热、耗电和后台异常上。说到底,原则很简单:尽量走系统认可的通道,少用手动保活,少让 CPU 在不该工作的时候一直工作。

我自己更关心下面这几个判断:

  • 前台服务不等于 Wake Lock,一个管存活,一个管 CPU
  • 能交给 WorkManager 和系统调度的任务,就别自己长时间持有 Wake Lock
  • 位置、蓝牙、传感器、Socket 这些场景,优先优化频率、时机和超时
  • 真遇到异常耗电,除了看自己代码,也别漏掉第三方 SDK

说白了,Wake Lock 不是不能用,而是不该当默认答案。

四、常见后台场景,我一般会这样改

1. 用户主动上传或下载:优先系统方案

如果任务是用户明确触发的长时间上传或下载,我会优先考虑系统提供的数据传输方案,而不是自己手动兜 Wake Lock。

val jobScheduler =
    context.getSystemService(JobScheduler::class.java)

val jobInfo = JobInfo.Builder(
    UPLOAD_JOB_ID,
    ComponentName(context, UploadJobService::class.java)
)
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
    .setUserInitiated(true)
    .setEstimatedNetworkBytes(totalBytes, 0L)
    .build()

jobScheduler.schedule(jobInfo)

适用场景大概是:

  • 用户点击“下载离线视频”
  • 用户点击“上传相册备份”
  • 用户确认发起大文件传输

这类任务真正的思路,不是“怎么把 Wake Lock 持有得更稳”,而是“干脆走系统认可的长传输通道”。

2. 一次性或周期同步:优先 WorkManager

对大多数同步任务,我还是更倾向于 WorkManager。它的价值不只是“写起来省事”,更重要的是它天然会顺着系统约束走,通常比手写服务、手写唤醒更稳。

class DataSyncWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result {
        return runCatching { syncData() }
            .fold(
                onSuccess = { Result.success() },
                onFailure = {
                    if (runAttemptCount < 3) Result.retry() else Result.failure()
                }
            )
    }
}

val request = OneTimeWorkRequestBuilder<DataSyncWorker>()
    .setConstraints(
        Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .build()
    )
    .setBackoffCriteria(
        BackoffPolicy.EXPONENTIAL,
        1,
        TimeUnit.MINUTES
    )
    .build()

WorkManager.getInstance(context).enqueue(request)

如果你已经用了 WorkManager,但后台还是异常耗电,我的建议不是先怀疑 WorkManager 本身,而是先排查 Worker 有没有超时、卡死,或者在某些分支下没有正常结束。

workManager.getWorkInfoByIdFlow(syncWorkId)
    .collect { workInfo ->
        val stopReason = workInfo?.stopReason ?: return@collect
        logStopReason(syncWorkId, stopReason)
    }

STOP_REASON_TIMEOUT 这种停止原因,就值得重点看看。

3. 位置更新:减少频率,利用批处理

位置类任务很容易被写成“持续高频监听 + 额外 Wake Lock”,但这类方案大多数时候都不够克制。

val request = LocationRequest.Builder(
    Priority.PRIORITY_BALANCED_POWER_ACCURACY,
    10_000L
).apply {
    setMinUpdateIntervalMillis(5_000L)
    setMaxUpdateDelayMillis(20_000L)
    setWaitForAccurateLocation(false)
}.build()

我更建议这样处理:

  • 根据业务把精度和频率降到刚好够用
  • 打开批处理能力,减少 CPU 被唤醒次数
  • 回调里先缓存数据,后续再交给 Worker 统一处理

位置回调到达时,系统本身就会短暂唤醒设备,因此很多场景下没必要再额外手动持有 Wake Lock。

4. 其他几个容易踩坑的场景

这一类场景我就不展开写太细了,记住原则就够:

  • 蓝牙扫描别长时间开着,能限时就限时,能低功耗模式就别用激进模式
  • 传感器别一上来就高频常驻,能批处理就批处理,能用聚合数据就别自己长期采集
  • Socket 监听阶段通常不需要手动 Wake Lock,真正需要关注的是消息到了之后的处理时长
  • 如果某个页面或功能明明不复杂,却异常耗电,记得顺手排查第三方 SDK

五、内存优化:先抓大头,别一上来就玩花活

内存治理最怕两种情况:

  • 只盯 GC 次数,不看真正的大对象和泄漏点
  • 过早上“复杂对象池”,结果维护成本上去了,收益不明显

我更推荐先把这三类问题盯住。

1. Bitmap 和大对象生命周期

图片往往就是内存峰值的头号来源。比起一开始就自己手搓复杂池化,先把尺寸、格式、缓存策略管住,收益通常更直接。

Glide.with(imageView)
    .load(url)
    .override(800, 600)
    .format(DecodeFormat.PREFER_RGB_565)
    .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
    .into(imageView)

实践上重点看四件事:

  • 按显示尺寸解码,不要原图硬塞
  • 能接受时用 RGB_565
  • 避免同一资源被重复解码
  • 列表快速滚动时观察图片加载峰值和回收时机

2. 缓存不是越多越好

缓存真正的价值,是减少重复计算和 IO,不是把能缓存的东西都长期堆在内存里。

class UserRepository {
    private val memoryCache = LruCache<String, User>(50)

    suspend fun getUser(id: String): User {
        memoryCache.get(id)?.let { return it }
        return fetchFromNetwork(id).also { memoryCache.put(id, it) }
    }

    fun trimMemory(level: Int) {
        if (level >= ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW) {
            memoryCache.evictAll()
        }
    }
}

缓存策略至少要回答三个问题:

  • 这份数据重复访问概率高不高
  • 丢失后重建成本高不高
  • 在内存紧张时能不能快速释放

3. 泄漏防护:比“优化技巧”更值得先做

很多看起来像性能问题的现象,最后顺着排查下去,根因其实是泄漏。

class MyActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    render(state)
                }
            }
        }
    }
}

优先排查这些老问题:

  • 单例误持有 Activity 或 View
  • 监听器、回调、协程没有跟生命周期解绑
  • Adapter、Drawable、Bitmap 链路把 Context 一路带住

所以 LeakCanary 这类工具我还是很建议保留。泄漏一旦积累起来,后面你看到的卡顿、频繁 GC、后台被杀,很多都只是表象。

六、测量工具链:没有数据,先别急着下结论

1. Perfetto 适合看“系统全景”

如果你想看全局,我觉得 Perfetto 还是最顺手的工具之一。它很适合回答这类问题:

  • 启动慢到底卡在主线程、Binder、IO 还是渲染
  • Wake Lock 是谁拿的,什么时候释放的
  • 一次操作中线程切换、锁竞争、调度延迟到底长什么样

应用内可以先埋轻量 trace:

inline fun <T> trace(name: String, block: () -> T): T {
    Trace.beginSection(name)
    return try {
        block()
    } finally {
        Trace.endSection()
    }
}

抓系统 trace 时,命令行方式也很直接:

adb shell perfetto -o /data/misc/perfetto-traces/app.perfetto-trace -t 10s sched freq idle am wm gfx view binder_driver hal dalvik
adb pull /data/misc/perfetto-traces/app.perfetto-trace

2. Android Studio Profiler 适合日常定位

Profiler 更像是日常排查的快刀,适合快速回答局部问题:

  • 某一段代码是不是把主线程堵住了
  • 某次交互后内存为什么起不来
  • 分配峰值是不是和某个页面、某个大图、某个列表有关

它不一定比 Perfetto 更强,但胜在更直接,日常开发里拿来先定位一轮很合适。

3. Macrobenchmark 适合做回归

我的习惯是,能稳定复现收益的优化,最后都尽量进自动化回归。否则过几个版本之后,被悄悄改坏的概率其实挺高。

@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun coldStartup() = benchmarkRule.measureRepeated(
        packageName = "com.example.app",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) {
        pressHome()
        startActivityAndWait()
    }
}

建议至少把下面两类场景纳入回归:

  • 冷启动、热启动、回前台
  • 关键列表滚动、首屏渲染、重要业务链路

七、Jetpack Compose:别只盯着“重组”两个字

Compose 性能优化很容易掉进一个误区:只要看到重组,就条件反射地觉得出问题了。其实更该看的,是重组发生得值不值、范围对不对、成本高不高。

1. 列表优先保证稳定 key

@Composable
fun UserList(
    users: List<User>,
    onUserClick: (User) -> Unit
) {
    LazyColumn {
        items(
            items = users,
            key = { it.id }
        ) { user ->
            UserItem(
                user = user,
                onClick = { onUserClick(user) }
            )
        }
    }
}

列表里最常见的问题不是“有重组”,而是:

  • 没有稳定 key
  • 数据对象频繁整体替换
  • 每次滚动都触发高成本计算

2. derivedStateOf 要和依赖一起记忆

@Composable
fun SearchableList(
    items: List<Item>,
    query: String
) {
    val filteredItems by remember(items, query) {
        derivedStateOf {
            items.filter { it.name.contains(query, ignoreCase = true) }
        }
    }

    LazyColumn {
        items(filteredItems, key = { it.id }) { item ->
            ItemRow(item)
        }
    }
}

这里真正的重点不是“用了 derivedStateOf 就一定更快”,而是尽量减少无意义的重复计算,同时保证依赖变化时结果还能正确更新。

3. 状态尽量单向流动

class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(MyUiState())
    val uiState: StateFlow<MyUiState> = _uiState.asStateFlow()

    fun refresh() {
        viewModelScope.launch {
            val data = fetchData()
            _uiState.update { it.copy(data = data) }
        }
    }
}

@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    MyContent(
        data = uiState.data,
        onRefresh = viewModel::refresh
    )
}

状态结构一旦清晰,Compose 的性能问题通常也会跟着简单不少,因为你更容易看清是谁触发了更新、扩散发生在了哪一层。

八、最后放一张我自己常用的检查表

每次做性能专项,我一般都会按这张表过一遍,能少走不少弯路。

1. 后台与功耗

  • 用户主动长传输是否改成 UIDT
  • 周期或一次性同步是否交给 WorkManager
  • 是否存在手动 Wake Lock 长时间持有
  • 蓝牙、位置、传感器是否都设置了边界和超时

2. 内存

  • 大图是否按需解码
  • 缓存是否能在内存紧张时主动收缩
  • 页面退出后对象是否能及时释放
  • 是否用工具确认过泄漏,而不是靠猜

3. 卡顿与启动

  • 首屏慢是在主线程、IO、锁竞争还是渲染
  • 关键链路是否加了 trace
  • 启动和滚动是否已经做了 Macrobenchmark 回归

4. Compose

  • 列表是否有稳定 key
  • 高成本计算是否被限制在必要范围
  • 状态是否存在无意义向下扩散

九、总结

如果要用一句话概括 2026 年安卓性能优化的变化,我会说:系统在继续变强,但对应用行为的边界也画得越来越清楚。

AutoFDO 代表的是系统层“更懂真实负载”的方向,后台与功耗治理代表的是平台对应用行为的边界越来越清楚。落到应用开发上,真正有效的做法其实并不花哨:

  • 用官方推荐的后台 API 替代手工保活
  • 用 trace、benchmark 和 profiler 替代经验判断
  • 先解决 Wake Lock、Bitmap、泄漏、列表这几个高频问题

我自己的感受是,把这些基础动作做扎实,往往比追逐某个单点技巧更有效,也更不容易反复返工。


参考来源


本文链接:安卓性能优化2026:从内存管理到电池续航 - https://h89.cn/archives/548.html

版权声明:原创文章 遵循 CC 4.0 BY-SA 版权协议,转载请附上原文链接和本声明。

标签: 性能优化, Perfetto, 内存管理, 电池续航, JobScheduler, WorkManager, Macrobenchmark

添加新评论