第一是启动优化,减少 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、线上监控等工具定位瓶颈,再针对性优化。