2026-01-12 13:30:25 +08:00
|
|
|
|
# 启动优化方法论
|
2026-01-12 17:14:58 +08:00
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
|
|
|
|
|
应用启动速度直接影响用户体验,是性能优化的重要指标。本文档系统介绍Android应用启动优化的方法论、测量工具和优化策略。
|
|
|
|
|
|
|
|
|
|
|
|
## 启动类型
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 冷启动(Cold Start)
|
|
|
|
|
|
|
|
|
|
|
|
**定义**: 应用进程不存在,系统需要创建新进程并初始化应用。
|
|
|
|
|
|
|
|
|
|
|
|
**特点**:
|
|
|
|
|
|
- 耗时最长
|
|
|
|
|
|
- 需要加载所有资源
|
|
|
|
|
|
- 创建Application和主Activity
|
|
|
|
|
|
|
|
|
|
|
|
**时间范围**: 通常2-5秒
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 温启动(Warm Start)
|
|
|
|
|
|
|
|
|
|
|
|
**定义**: 应用进程存在但Activity被销毁,需要重新创建Activity。
|
|
|
|
|
|
|
|
|
|
|
|
**特点**:
|
|
|
|
|
|
- 进程已存在
|
|
|
|
|
|
- 只需创建Activity
|
|
|
|
|
|
- 比冷启动快
|
|
|
|
|
|
|
|
|
|
|
|
**时间范围**: 通常1-3秒
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 热启动(Hot Start)
|
|
|
|
|
|
|
|
|
|
|
|
**定义**: 应用进程和Activity都存在,只需将Activity带到前台。
|
|
|
|
|
|
|
|
|
|
|
|
**特点**:
|
|
|
|
|
|
- 最快
|
|
|
|
|
|
- 只需恢复UI状态
|
|
|
|
|
|
- 几乎不需要初始化
|
|
|
|
|
|
|
|
|
|
|
|
**时间范围**: 通常< 500ms
|
|
|
|
|
|
|
|
|
|
|
|
## 启动时间测量
|
|
|
|
|
|
|
|
|
|
|
|
### 1. adb命令测量
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 测量启动时间
|
|
|
|
|
|
adb shell am start -W -n com.example.app/.MainActivity
|
|
|
|
|
|
|
|
|
|
|
|
# 输出示例:
|
|
|
|
|
|
# Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.app/.MainActivity }
|
|
|
|
|
|
# Status: ok
|
|
|
|
|
|
# Activity: com.example.app/.MainActivity
|
|
|
|
|
|
# ThisTime: 500
|
|
|
|
|
|
# TotalTime: 500
|
|
|
|
|
|
# WaitTime: 520
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**指标说明**:
|
|
|
|
|
|
- **ThisTime**: 最后一个Activity启动耗时
|
|
|
|
|
|
- **TotalTime**: 所有Activity启动耗时
|
|
|
|
|
|
- **WaitTime**: AMS启动Activity总耗时
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 代码埋点测量
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
public class MainActivity extends AppCompatActivity {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
|
|
long startTime = System.currentTimeMillis();
|
|
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化工作
|
|
|
|
|
|
|
|
|
|
|
|
long endTime = System.currentTimeMillis();
|
|
|
|
|
|
Log.d("Startup", "onCreate耗时: " + (endTime - startTime) + "ms");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onWindowFocusChanged(boolean hasFocus) {
|
|
|
|
|
|
super.onWindowFocusChanged(hasFocus);
|
|
|
|
|
|
if (hasFocus) {
|
|
|
|
|
|
// 首帧渲染完成
|
|
|
|
|
|
long renderTime = System.currentTimeMillis() - getIntent().getLongExtra("start_time", 0);
|
|
|
|
|
|
Log.d("Startup", "首帧渲染耗时: " + renderTime + "ms");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. Systrace/Perfetto分析
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
# 使用Systrace
|
|
|
|
|
|
python systrace.py -t 10 -o trace.html sched freq idle am wm gfx view binder_driver hal dalvik camera input res
|
|
|
|
|
|
|
|
|
|
|
|
# 使用Perfetto
|
|
|
|
|
|
# 通过Android Studio的Profiler录制
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**关键指标**:
|
|
|
|
|
|
- **Application.onCreate**: Application初始化时间
|
|
|
|
|
|
- **Activity.onCreate**: Activity创建时间
|
|
|
|
|
|
- **inflate**: 布局解析时间
|
|
|
|
|
|
- **measure/layout/draw**: 视图测量、布局、绘制时间
|
|
|
|
|
|
|
|
|
|
|
|
### 4. 首屏渲染时间(TTI)
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
public class MainActivity extends AppCompatActivity {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
|
setContentView(R.layout.activity_main);
|
|
|
|
|
|
|
|
|
|
|
|
// 监听首屏渲染
|
|
|
|
|
|
View rootView = findViewById(android.R.id.content);
|
|
|
|
|
|
rootView.getViewTreeObserver().addOnPreDrawListener(
|
|
|
|
|
|
new ViewTreeObserver.OnPreDrawListener() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public boolean onPreDraw() {
|
|
|
|
|
|
rootView.getViewTreeObserver().removeOnPreDrawListener(this);
|
|
|
|
|
|
// 首屏已渲染
|
|
|
|
|
|
logTTI();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 启动优化策略
|
|
|
|
|
|
|
|
|
|
|
|
### 1. Application优化
|
|
|
|
|
|
|
|
|
|
|
|
#### 延迟初始化
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
public class MyApplication extends Application {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onCreate() {
|
|
|
|
|
|
super.onCreate();
|
|
|
|
|
|
|
|
|
|
|
|
// 立即初始化:必须的、轻量的
|
|
|
|
|
|
initCrashHandler();
|
|
|
|
|
|
|
|
|
|
|
|
// 延迟初始化:非关键的、耗时的
|
|
|
|
|
|
initInBackground();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void initInBackground() {
|
|
|
|
|
|
new Thread(() -> {
|
|
|
|
|
|
// 在后台线程初始化
|
|
|
|
|
|
initHeavyLibrary();
|
|
|
|
|
|
initThirdPartySDK();
|
|
|
|
|
|
}).start();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用启动器框架
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 使用Startup库管理初始化任务
|
|
|
|
|
|
public class MyApplication extends Application {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onCreate() {
|
|
|
|
|
|
super.onCreate();
|
|
|
|
|
|
AppStartup.initializeComponent(this, MyStartupInitializer.class);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2. 主线程优化
|
|
|
|
|
|
|
|
|
|
|
|
#### 减少主线程工作
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
public class MainActivity extends AppCompatActivity {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
|
|
|
|
|
|
|
// 避免在主线程做耗时操作
|
|
|
|
|
|
// ❌ 错误:同步网络请求
|
|
|
|
|
|
// String data = networkRequest();
|
|
|
|
|
|
|
|
|
|
|
|
// ✅ 正确:异步加载
|
|
|
|
|
|
loadDataAsync();
|
|
|
|
|
|
|
|
|
|
|
|
setContentView(R.layout.activity_main);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void loadDataAsync() {
|
|
|
|
|
|
new Thread(() -> {
|
|
|
|
|
|
String data = networkRequest();
|
|
|
|
|
|
runOnUiThread(() -> {
|
|
|
|
|
|
updateUI(data);
|
|
|
|
|
|
});
|
|
|
|
|
|
}).start();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用IdleHandler延迟非关键任务
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public boolean queueIdle() {
|
|
|
|
|
|
// 主线程空闲时执行
|
|
|
|
|
|
initNonCriticalComponents();
|
|
|
|
|
|
return false; // false表示只执行一次
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 布局优化
|
|
|
|
|
|
|
|
|
|
|
|
#### 减少布局层级
|
|
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
|
<!-- ❌ 错误:嵌套过深 -->
|
|
|
|
|
|
<LinearLayout>
|
|
|
|
|
|
<LinearLayout>
|
|
|
|
|
|
<LinearLayout>
|
|
|
|
|
|
<TextView />
|
|
|
|
|
|
</LinearLayout>
|
|
|
|
|
|
</LinearLayout>
|
|
|
|
|
|
</LinearLayout>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ✅ 正确:使用ConstraintLayout扁平化 -->
|
|
|
|
|
|
<androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
|
|
|
<TextView />
|
|
|
|
|
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用ViewStub延迟加载
|
|
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
|
<!-- 延迟加载非首屏内容 -->
|
|
|
|
|
|
<ViewStub
|
|
|
|
|
|
android:id="@+id/view_stub"
|
|
|
|
|
|
android:layout="@layout/heavy_content"
|
|
|
|
|
|
android:layout_width="match_parent"
|
|
|
|
|
|
android:layout_height="match_parent" />
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 需要时再加载
|
|
|
|
|
|
ViewStub viewStub = findViewById(R.id.view_stub);
|
|
|
|
|
|
viewStub.inflate();
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 优化布局inflate
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 使用AsyncLayoutInflater异步加载布局
|
|
|
|
|
|
new AsyncLayoutInflater(this).inflate(
|
|
|
|
|
|
R.layout.activity_main,
|
|
|
|
|
|
null,
|
|
|
|
|
|
(view, resid, parent) -> {
|
|
|
|
|
|
setContentView(view);
|
|
|
|
|
|
// 布局已加载完成
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4. 资源优化
|
|
|
|
|
|
|
|
|
|
|
|
#### 减少启动时加载的资源
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 延迟加载大图片
|
|
|
|
|
|
// 使用占位图
|
|
|
|
|
|
ImageView imageView = findViewById(R.id.image);
|
|
|
|
|
|
imageView.setImageResource(R.drawable.placeholder);
|
|
|
|
|
|
|
|
|
|
|
|
// 异步加载
|
|
|
|
|
|
Glide.with(this)
|
|
|
|
|
|
.load(imageUrl)
|
|
|
|
|
|
.into(imageView);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用资源预加载
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 在Application中预加载常用资源
|
|
|
|
|
|
public class MyApplication extends Application {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onCreate() {
|
|
|
|
|
|
super.onCreate();
|
|
|
|
|
|
// 预加载资源到内存
|
|
|
|
|
|
preloadResources();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void preloadResources() {
|
|
|
|
|
|
new Thread(() -> {
|
|
|
|
|
|
// 预加载常用drawable
|
|
|
|
|
|
Resources res = getResources();
|
|
|
|
|
|
res.getDrawable(R.drawable.common_icon);
|
|
|
|
|
|
}).start();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5. 多进程优化
|
|
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
|
<!-- AndroidManifest.xml -->
|
|
|
|
|
|
<activity
|
|
|
|
|
|
android:name=".MainActivity"
|
|
|
|
|
|
android:process=":main" />
|
|
|
|
|
|
|
|
|
|
|
|
<service
|
|
|
|
|
|
android:name=".HeavyService"
|
|
|
|
|
|
android:process=":heavy" />
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**优点**:
|
|
|
|
|
|
- 主进程启动更快
|
|
|
|
|
|
- 避免主进程OOM
|
|
|
|
|
|
- 隔离崩溃影响
|
|
|
|
|
|
|
|
|
|
|
|
**缺点**:
|
|
|
|
|
|
- 进程间通信开销
|
|
|
|
|
|
- 内存占用增加
|
|
|
|
|
|
|
|
|
|
|
|
### 6. 类加载优化
|
|
|
|
|
|
|
|
|
|
|
|
#### 减少类数量
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 使用ProGuard/R8混淆和优化
|
|
|
|
|
|
// build.gradle
|
|
|
|
|
|
android {
|
|
|
|
|
|
buildTypes {
|
|
|
|
|
|
release {
|
|
|
|
|
|
minifyEnabled true
|
|
|
|
|
|
shrinkResources true
|
|
|
|
|
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
|
|
|
|
|
|
'proguard-rules.pro'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用MultiDex优化
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 对于方法数超过65535的应用
|
|
|
|
|
|
android {
|
|
|
|
|
|
defaultConfig {
|
|
|
|
|
|
multiDexEnabled true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
dependencies {
|
|
|
|
|
|
implementation 'androidx.multidex:multidex:2.0.1'
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 7. 启动画面优化
|
|
|
|
|
|
|
|
|
|
|
|
#### 使用Splash Screen API(Android 12+)
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 使用新的Splash Screen API
|
|
|
|
|
|
// 提供更好的启动体验
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 传统启动画面
|
|
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
// 快速显示启动画面
|
|
|
|
|
|
// 避免白屏/黑屏
|
|
|
|
|
|
<style name="LaunchTheme" parent="Theme.AppCompat.Light">
|
|
|
|
|
|
<item name="android:windowBackground">@drawable/splash_background</item>
|
|
|
|
|
|
<item name="android:windowFullscreen">true</item>
|
|
|
|
|
|
</style>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 启动优化检查清单
|
|
|
|
|
|
|
|
|
|
|
|
### Application层
|
|
|
|
|
|
- [ ] 延迟非关键初始化
|
|
|
|
|
|
- [ ] 使用后台线程初始化耗时任务
|
|
|
|
|
|
- [ ] 避免在Application中做网络请求
|
|
|
|
|
|
- [ ] 使用启动器框架管理初始化顺序
|
|
|
|
|
|
|
|
|
|
|
|
### Activity层
|
|
|
|
|
|
- [ ] 减少onCreate中的工作
|
|
|
|
|
|
- [ ] 异步加载数据
|
|
|
|
|
|
- [ ] 使用ViewStub延迟加载
|
|
|
|
|
|
- [ ] 优化布局层级
|
|
|
|
|
|
|
|
|
|
|
|
### 资源层
|
|
|
|
|
|
- [ ] 减少启动时加载的资源
|
|
|
|
|
|
- [ ] 使用占位图
|
|
|
|
|
|
- [ ] 延迟加载大图片
|
|
|
|
|
|
- [ ] 优化资源大小
|
|
|
|
|
|
|
|
|
|
|
|
### 代码层
|
|
|
|
|
|
- [ ] 减少类数量(使用ProGuard)
|
|
|
|
|
|
- [ ] 避免静态初始化块中的耗时操作
|
|
|
|
|
|
- [ ] 优化依赖库加载
|
|
|
|
|
|
|
|
|
|
|
|
## 性能指标
|
|
|
|
|
|
|
|
|
|
|
|
### 目标值
|
|
|
|
|
|
- **冷启动**: < 2秒
|
|
|
|
|
|
- **温启动**: < 1秒
|
|
|
|
|
|
- **热启动**: < 500ms
|
|
|
|
|
|
- **首屏渲染**: < 1秒
|
|
|
|
|
|
|
|
|
|
|
|
### 测量方法
|
|
|
|
|
|
1. 使用adb命令测量
|
|
|
|
|
|
2. 代码埋点测量
|
|
|
|
|
|
3. Systrace/Perfetto分析
|
|
|
|
|
|
4. 用户感知时间测量
|
|
|
|
|
|
|
|
|
|
|
|
## 相关链接
|
|
|
|
|
|
|
|
|
|
|
|
- [[README]]
|
|
|
|
|
|
- [[流畅度(Choreographer+VSYNC)]]
|
|
|
|
|
|
- [[09-调试与工具链/Systrace_Perfetto全解读]]
|