前言
一、主要是优化什么?
- 冷启动的时间
- 启动阶段在哪里?
二、多少才算优化好?
- 标准是什么。
- 日志
- adb
三、如何分析那些地方有问题应该如何优化呢?
- 借助profile
- 严格模式:主线程是否被阻塞
- 黑白屏如何解决?
一、主要是优化什么?
启动优化的核心目标是 缩短用户从点击应用图标到看到可交互界面的等待时间,提升用户体验。但具体优化方向需根据不同类型的启动场景(冷启动、温启动、热启动)针对性处理:
启动类型 | 触发条件 | 优化重点 |
---|---|---|
冷启动 | 应用进程未存在(完全重启) | 进程创建、Application初始化、首屏渲染耗时 |
温启动 | 应用进程存在但 Activity 栈被销毁 | Activity 重建、数据恢复、避免重复初始化 |
热启动 | 应用进程和 Activity 均在后台 | 快速恢复前台界面,减少主线程卡顿 |
一般启动优化是针对冷启动,进程创建我们干预不了,我们一般是在Application初始化、首屏渲染(onCreate
→ onStart
→ onResume
)耗时进行优化。
二、如何知道启动耗时是多少?
2.1 日志方式:display
2.2 adb方式【推荐】
需要获得root
adb shell am start -S -W com.example.lifecycledemo/.MainActivity
-s 先停止 ,-w 再启动
TotalTime: 580
【我们主要优化这个】
- 含义:应用自身从启动到完成首帧渲染(
Activity.onResume()
结束)的总耗时,单位为毫秒(ms)。
WaitTime: 591
- 含义:系统视角的总耗时,包括进程创建、系统资源调度等开销,通常略大于
TotalTime
。 - 公式:
WaitTime = TotalTime + 系统调度耗时
2.3 优化到什么程度,才算符合标准呢?
冷启动优化的目标是 让用户感知不到明显等待,具体标准需结合设备性能和行业实践:
设备类型 | 理想冷启动耗时 | 容忍上限 | 优化优先级 |
---|---|---|---|
高端设备 | ≤ 400ms | ≤ 800ms | 高 |
中端设备 | ≤ 800ms | ≤ 1200ms | 中 |
低端设备 | ≤ 1200ms | ≤ 2000ms | 高 |
-
行业参考:
- Google 官方建议:应用应在 1秒内 完成冷启动(用户可交互)。
- Android Vitals:冷启动时间超过 5秒 会被标记为性能问题。
- 用户体验研究:超过 2秒 的冷启动会导致 用户流失率显著上升。
但注意,这只是一个参考,并没有严格的标准,因为本身就有些应用需要启动的东西比较多。
三、如何分析那些地方有问题应该如何优化呢?
3.1 Android Studio Profiler:使用 CPU 跟踪分析冷启动各阶段耗时(如类加载、布局渲染)。
运行app,运行一段时间后,结束跟踪。
可以跳转到源码位置
可以看到子线程中也可以看到。
如果我们在主线程中增加,那么可以在如下地方看到。
总结:
- 这里面,可以找出有耗时的地方进行优化。
- Top Down(自顶向下):从 调用栈的顶层方法(入口) 开始,逐层向下展开,展示每个方法的调用路径和耗时占比。
- Flame Chart(火焰图):根据耗时百分比查看调用栈,便于发现总耗时很长的调用链
【常用】
- Bottom Up(自底向上):相对于Top Down,能够更方便查看耗时方法
【常用】
3.2 StrictMode模式
StrictMode
是 Android 提供的一个 开发阶段 的调试工具,用于检测主线程(UI 线程)中的 非法操作,例如:
- 主线程磁盘 I/O(如读写 SharedPreferences、数据库、文件)。
- 主线程网络请求(如未经异步处理的 HTTP 请求)。
- 内存泄漏(如未关闭的
Cursor
、BroadcastReceiver
)。 - 其他耗时操作(如密集计算、类加载)。
其核心目的是 提前暴露潜在的性能问题,防止应用出现卡顿或 ANR(Application Not Responding)。
class MyApplication :Application(){
private val TAG = "MyApplication"
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
// 配置线程策略(检测主线程问题)
StrictMode.setThreadPolicy(
ThreadPolicy.Builder()
.detectDiskReads() // 检测磁盘读
.detectDiskWrites() // 检测磁盘写
.detectNetwork() // 检测网络请求
.penaltyLog() // 违规时输出日志
.penaltyDeath() // 违规时直接崩溃
.build()
)
// 配置虚拟机策略(检测内存泄漏等)
StrictMode.setVmPolicy(
VmPolicy.Builder()
.detectLeakedSqlLiteObjects() // 检测未关闭的 SQLite 游标
.detectLeakedClosableObjects() // 检测未关闭的 Closeable 对象
.penaltyLog() // 违规时输出日志
.penaltyDeath() // 违规时直接崩溃
.build()
)
}
}
}
class MainActivity : AppCompatActivity() {
private val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
var tvHello = findViewById<TextView>(R.id.tv_hello);
tvHello.setOnClickListener(object :OnClickListener{
override fun onClick(v: View?) {
Log.d(TAG, "onClick: ")
}
})
Thread.sleep(6000)
}
}
3.3 黑白屏优化
在 冷启动阶段,当用户点击应用图标后,系统会先显示一个 默认窗口背景(通常为白色或黑色),直到首屏 Activity 的布局渲染完成。这个中间过程导致的短暂白屏或黑屏现象称为 黑白屏。
这个是google加的,因为如果没有黑白屏,加上你app启动时间过长,那么就会想没点击一下,用户体验差,所以加了,但是黑白屏并不好看,所以我们需要换成自己的。
代码示例:
<!-- res/values/styles.xml -->
<style name="LaunchTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- 设置背景为启动图或颜色 -->
<item name="android:windowBackground">@drawable/splash_background</item>
<!-- 可选:全屏显示 -->
<item name="android:windowFullscreen">true</item>
</style>
<!-- res/values-v31/styles.xml(适配 Android 12+ SplashScreen) -->
<style name="LaunchTheme" parent="Theme.SplashScreen">
<item name="windowSplashScreenBackground">@color/splash_background_color</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon</item>
<item name="postSplashScreenTheme">@style/AppTheme</item>
</style>
<!-- AndroidManifest.xml -->
<activity
android:name=".MainActivity"
android:theme="@style/LaunchTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
// MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// 必须在 super.onCreate 前切换主题
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
3.4 异步加载
1. 异步/延迟初始化
优化前代码(Application.java):
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
// 主线程同步初始化
initPushSDK(); // 耗时 400ms
initAnalyticsSDK(); // 耗时 300ms
initDatabase(); // 耗时 500ms
}
}
优化后代码:
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
// 异步初始化非关键任务
Executors.newSingleThreadExecutor().execute(() -> {
initDatabase(); // 数据库初始化放到子线程
});
// 延迟关键路径外的 SDK 初始化
new Handler(Looper.getMainLooper()).postDelayed(() -> {
initPushSDK();
initAnalyticsSDK();
}, 2000); // 延迟到首屏渲染后
}
}