性能优化

第一是启动优化,减少 Application 和首屏 Activity 的耗时操作;
第二是 UI 渲染优化,避免主线程耗时、减少布局层级、降低过度绘制;
第三是内存优化,比如避免内存泄漏、控制 Bitmap 大小、减少不必要对象创建;
第四是网络和图片优化,比如接口合并、缓存、分页加载、图片压缩和复用;
第五是包体积和电量优化,比如开启 R8、资源压缩、减少后台任务和定位频率。


二、启动优化

1. 启动分类

Android 启动一般分为:

冷启动:进程不存在,从零开始创建 Application 和 Activity
温启动:进程存在,但 Activity 需要重新创建
热启动:Activity 还在内存中,直接回到前台

优化重点主要是 冷启动


2. 冷启动过程

冷启动大致流程:

点击图标
   ↓
创建进程
   ↓
创建 Application
   ↓
Application.attachBaseContext()
   ↓
Application.onCreate()
   ↓
启动首个 Activity
   ↓
Activity.onCreate()
   ↓
setContentView()
   ↓
测量、布局、绘制
   ↓
首帧显示

所以启动慢一般出现在:

Application 初始化太重
首屏 Activity 初始化太重
布局太复杂
主线程做了耗时任务
第三方 SDK 初始化过多

3. 启动优化手段

1)减少 Application 中的初始化

不要在 Application.onCreate() 中一次性初始化所有 SDK。

不推荐:

class App : Application() {
    override fun onCreate() {
        super.onCreate()

        initMapSdk()
        initPushSdk()
        initShareSdk()
        initPaymentSdk()
        initAnalyticsSdk()
        initImageLoader()
        initDatabase()
    }
}

优化思路:

必要 SDK 立即初始化
非必要 SDK 延迟初始化
按需初始化
异步初始化
分阶段初始化

例如:

class App : Application() {
    override fun onCreate() {
        super.onCreate()

        initNecessarySdk()

        Handler(Looper.getMainLooper()).post {
            initDelaySdk()
        }
    }
}

更好的方式是按业务场景初始化:

地图 SDK:进入地图页面再初始化
分享 SDK:点击分享时再初始化
支付 SDK:进入支付流程再初始化

2)耗时任务放到子线程

比如:

Executors.newSingleThreadExecutor().execute {
    initDatabase()
    loadLocalConfig()
}

但要注意:

不是所有初始化都能放子线程,有些 SDK 要求必须主线程初始化,需要看文档。


3)使用启动器框架管理初始化

大型项目中可以用类似:

Jetpack App Startup
或者自研任务调度器

解决:

初始化顺序
线程调度
依赖关系
懒加载

4)首屏只加载必要数据

不要首屏一次性请求很多接口。

可以拆成:

首屏必需数据:优先加载
非首屏数据:延迟加载
推荐、广告、统计:后置加载

5)优化启动页白屏/黑屏

可以设置启动主题:

<style name="Theme.App.Starting" parent="Theme.SplashScreen">
    <item name="windowSplashScreenBackground">@color/white</item>
    <item name="windowSplashScreenAnimatedIcon">@drawable/ic_logo</item>
    <item name="postSplashScreenTheme">@style/Theme.App</item>
</style>

或者老方式:

<style name="AppTheme.Launcher">
    <item name="android:windowBackground">@drawable/bg_splash</item>
</style>

这样可以减少用户感知上的白屏。


三、UI 渲染和卡顿优化

Android 一帧大约只有:

16.6ms

如果主线程在 16.6ms 内没完成绘制,就可能掉帧。


1. 避免主线程耗时操作

主线程不要做:

网络请求
数据库大查询
文件读写
Bitmap 解码
复杂 JSON 解析
大量循环计算
大量对象创建

错误示例:

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

    val json = assets.open("big.json").bufferedReader().readText()
    parseJson(json)
}

优化:

lifecycleScope.launch(Dispatchers.IO) {
    val json = assets.open("big.json").bufferedReader().readText()
    val data = parseJson(json)

    withContext(Dispatchers.Main) {
        render(data)
    }
}

2. 减少布局层级

复杂 XML 会增加 measure、layout、draw 成本。

优化手段:

使用 ConstraintLayout 减少嵌套
使用 include 复用布局
使用 merge 减少多余根布局
使用 ViewStub 延迟加载不常用布局

例如空页面、错误页面可以用:

<ViewStub
    android:id="@+id/errorStub"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout="@layout/layout_error" />

需要时再加载:

val errorView = viewStub.inflate()

3. 避免过度绘制

过度绘制就是同一个像素被重复绘制多次。

常见原因:

根布局设置背景
子布局也设置背景
CardView、RecyclerView Item 多层背景叠加
不必要的透明度和阴影

可以通过开发者选项:

调试 GPU 过度绘制

查看。

优化方式:

去掉重复 background
减少半透明 View
减少复杂 shape
合理使用 clipToPadding

4. RecyclerView 优化

RecyclerView 是性能优化重点。

常见优化点:

使用 DiffUtil
设置合适的缓存
避免 item 布局太复杂
避免 onBindViewHolder 做耗时操作
图片加载取消和复用
使用 setHasFixedSize(true)
局部刷新 payload

例如:

recyclerView.setHasFixedSize(true)

如果 item 高度固定,可以减少重新测量。

使用 DiffUtil:

class UserDiffCallback : DiffUtil.ItemCallback<User>() {

    override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem == newItem
    }
}

避免:

adapter.notifyDataSetChanged()

尽量使用:

submitList(newList)

或者:

notifyItemChanged(position, payload)

四、内存优化

内存优化主要看:

内存泄漏
Bitmap 占用
对象频繁创建
集合缓存过大
生命周期管理

1. 避免内存泄漏

常见泄漏场景:

静态变量持有 Activity
单例持有 Context
Handler 延迟消息持有 Activity
匿名内部类持有外部类
Dialog 没有 dismiss
注册监听后没有反注册
协程/线程生命周期过长
WebView 没有销毁

错误示例:

object UserManager {
    var context: Context? = null
}

如果传入 Activity:

UserManager.context = this

就可能泄漏 Activity。

优化:

UserManager.context = applicationContext

2. ViewModel 不要持有 Activity

不推荐:

class MainViewModel(
    private val activity: Activity
) : ViewModel()

因为 ViewModel 生命周期可能比 Activity 长。

推荐:

class MainViewModel(
    private val repository: UserRepository
) : ViewModel()

如果需要 Context,尽量使用:

ApplicationContext

3. Handler 泄漏

老代码中常见:

private val handler = Handler()

handler.postDelayed({
    updateUI()
}, 10_000)

如果 Activity 已经销毁,消息还在队列里,可能泄漏。

优化:

override fun onDestroy() {
    super.onDestroy()
    handler.removeCallbacksAndMessages(null)
}

4. 监听器反注册

比如:

override fun onStart() {
    super.onStart()
    sensorManager.registerListener(...)
}

override fun onStop() {
    super.onStop()
    sensorManager.unregisterListener(...)
}

5. 使用 LeakCanary 检测泄漏

可以说:

项目中我会接入 LeakCanary,在 debug 包中检测 Activity、Fragment、ViewModel、Dialog 等对象是否泄漏。


五、图片优化

图片是 Android 内存占用大户。

1. 控制图片尺寸

不要把大图直接加载进小控件。

比如 ImageView 只有 100dp,却加载一张 4000×3000 的图片,会浪费大量内存。

如果用 Glide:

Glide.with(imageView)
    .load(url)
    .override(300, 300)
    .centerCrop()
    .into(imageView)

2. 使用合适格式

常见格式:

PNG:适合透明图,但体积较大
JPG:适合照片
WebP:通常体积更小
SVG/VectorDrawable:适合简单图标

优化建议:

图片资源尽量 WebP
图标尽量 VectorDrawable
大图加载压缩
列表图加载缩略图

3. Bitmap 复用和缓存

如果自己处理 Bitmap,要注意:

inSampleSize 压缩
LruCache 内存缓存
磁盘缓存
及时释放无用资源

不过实际项目一般交给:

Glide
Coil
Fresco

六、网络优化

网络优化可以从这些方面讲:

减少请求次数
接口合并
分页加载
缓存策略
压缩传输
超时和重试控制
避免重复请求
弱网优化

1. 减少无效请求

例如页面多次进入时,不要重复请求相同数据,可以做:

内存缓存
数据库缓存
Repository 层统一管理

2. 分页加载

列表页不要一次性加载几千条数据。

可以:

分页接口
Paging 3
上拉加载更多
预加载下一页

3. 使用缓存

比如 OkHttp 缓存:

val cache = Cache(File(context.cacheDir, "http_cache"), 10L * 1024 * 1024)

val client = OkHttpClient.Builder()
    .cache(cache)
    .build()

也可以业务层缓存:

Room
DataStore
内存 Map
LruCache

4. 数据压缩

请求和响应可以使用:

gzip
br
protobuf

减少传输体积。


七、包体积优化

包体积优化常见点:

开启 minifyEnabled
开启 shrinkResources
删除无用资源
使用 WebP
减少 so 库架构
动态功能模块
资源混淆
避免重复依赖

1. 开启 R8 和资源压缩

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile(
                'proguard-android-optimize.txt'
            ), 'proguard-rules.pro'
        }
    }
}

2. 控制 so 架构

如果没有必要支持所有架构,可以限制:

android {
    defaultConfig {
        ndk {
            abiFilters "arm64-v8a", "armeabi-v7a"
        }
    }
}

现在很多应用重点支持:

arm64-v8a

3. 资源优化

PNG 转 WebP
删除无用图片
图标使用 VectorDrawable
删除不用的语言资源

例如只保留部分语言:

android {
    defaultConfig {
        resConfigs "zh", "en"
    }
}

八、电量优化

电量优化面试也可能问。

主要原则:

减少后台唤醒
减少频繁定位
减少频繁网络请求
合理使用 WorkManager
避免长时间持有 WakeLock
传感器及时关闭

1. 后台任务使用 WorkManager

比如日志上传、数据同步,可以用:

WorkManager

并加条件:

只在 Wi-Fi 下执行
只在充电时执行
只在网络可用时执行

2. 定位优化

定位很耗电,要注意:

降低定位频率
进入页面再定位
离开页面停止定位
粗定位优先
后台不要频繁定位

九、数据库优化

如果项目中用了 Room 或 SQLite,可以说:

查询放 IO 线程
建立索引
分页查询
避免 SELECT *
批量插入使用事务
减少主线程数据库访问

比如:

CREATE INDEX index_user_id ON user(id);

Room 中:

@Entity(indices = [Index(value = ["userId"])])
data class UserEntity(
    @PrimaryKey val id: Long,
    val userId: String
)

十、稳定性和监控

优化不是靠感觉,要靠监控。

你可以说:

我一般会通过线上监控和本地工具结合定位问题。本地使用 Profiler、Layout Inspector、Perfetto、LeakCanary;线上通过 Crash、ANR、启动耗时、卡顿率、内存占用、接口耗时等指标来发现问题。

常见工具:

Android Studio Profiler
Memory Profiler
CPU Profiler
Layout Inspector
Perfetto / Systrace
LeakCanary
Firebase Performance
Matrix
Bugly
线上埋点系统

十一、面试官追问:如何定位卡顿?

可以这样回答:

卡顿本质是主线程在一帧 16.6ms 内没有完成输入、动画、布局、绘制等任务。
我一般先通过线上卡顿监控或本地复现找到卡顿场景,然后用 Perfetto、Systrace 或 Android Studio Profiler 看主线程在卡顿时间段做了什么,比如是否有文件 IO、数据库查询、Bitmap 解码、复杂布局 measure/layout、GC 频繁等。
找到原因后,再针对性处理,比如把耗时任务移到 IO 线程、减少布局层级、优化 RecyclerView、减少对象创建、控制图片大小等。


十二、面试官追问:如何定位内存泄漏?

可以这样回答:

内存泄漏我会先用 LeakCanary 在 debug 环境检测,如果发现 Activity 或 Fragment 销毁后没有被回收,就看引用链。
常见原因包括单例持有 Activity、Handler 延迟任务、监听器未反注册、Dialog 未关闭、协程或线程没有取消、WebView 没销毁。
如果是线上内存问题,可以结合 Memory Profiler 或 dump hprof 分析对象数量和引用关系。


十三、面试官追问:如何优化 OOM?

可以这样回答:

OOM 一般是内存占用过高或内存泄漏导致的。我会先看是不是大图加载、Bitmap 没压缩、列表缓存过多、对象泄漏或者一次性加载大量数据。
优化上会控制图片尺寸和格式,使用 Glide/Coil 的缓存和采样能力;列表分页加载;避免静态集合缓存大对象;及时释放资源;同时用 LeakCanary 和 Memory Profiler 查泄漏。


十四、面试官追问:ANR 怎么优化?

ANR 常见原因:

主线程耗时操作
BroadcastReceiver 执行太久
Service 前台/后台任务阻塞
ContentProvider 初始化慢
锁竞争
死锁

可以这样回答:

ANR 本质是主线程长时间没有响应。比如输入事件 5 秒没响应、BroadcastReceiver 10 秒没执行完、Service 20 秒没处理完。
我会先分析 ANR 日志和 traces 文件,看主线程堆栈停在哪里。如果是 IO、数据库、网络、锁等待,就把耗时任务移到子线程,减少锁竞争,避免主线程等待异步结果。
同时 Application、ContentProvider、BroadcastReceiver 里不要做重初始化。


十五、可以背的面试版本

你可以直接这样回答:

Android 性能优化我一般从启动、UI 渲染、内存、网络、图片、包体积和电量几个方向去做。

启动优化主要是减少 Application 和首屏 Activity 的耗时操作,比如第三方 SDK 延迟或按需初始化,首屏只加载必要数据,耗时任务放到子线程,同时优化启动主题减少白屏。

UI 优化主要是避免主线程做耗时操作,减少布局层级,使用 ConstraintLayout、ViewStub、merge/include,减少过度绘制;列表页会重点优化 RecyclerView,比如使用 DiffUtil、局部刷新、控制 item 布局复杂度,避免 onBindViewHolder 中做耗时操作。

内存优化主要是避免内存泄漏和控制大对象,比如 Bitmap。常见泄漏包括单例持有 Activity、Handler 延迟任务、监听器未反注册、协程未取消、Dialog 和 WebView 未释放。我会用 LeakCanary 和 Memory Profiler 定位。

网络优化会做接口合并、分页加载、缓存、避免重复请求、弱网重试和超时控制。图片方面会使用 Glide 或 Coil 控制尺寸、缓存、压缩和格式,比如 WebP、VectorDrawable。

包体积方面会开启 R8、shrinkResources、删除无用资源、限制 so 架构、图片转 WebP。

最重要的是优化不能凭感觉,要先用 Profiler、Perfetto、LeakCanary、线上监控等工具定位瓶颈,再针对性优化。


暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇