不止于
Application.onCreate,从Zygote孵化到第一帧渲染,逐帧拆解启动全链路,并给出可落地的代码级优化方案。
启动速度直接关乎用户留存率(Google数据:启动延迟每增加1秒,流失率提升20%)。但在实际项目中,很多开发者只停留在SplashActivity加个倒计时,或者在Application里开线程池——这远远不够。
真正的高级工程师会从源码角度理解启动流程,然后精准定位瓶颈,最后用字节码插桩、类预加载、布局异步Inflate等手段把冷启动时间压到300ms以内(中端设备)。
本文将以冷启动为主战场,从AMS.startActivity到ViewRootImpl.performTraversals,逐层拆解,并给出可直接复用的启动任务调度框架。
AMS.startActivity最终通过Process.start向Zygote发送socket请求,Zygote fork出应用进程。入口是ActivityThread.main():
// ActivityThread.java
public static void main(String[] args) {
Looper.prepareMainLooper();
// 创建ActivityThread并attach到AMS
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
}关键点:attach会调用mgr.attachApplication(mAppThread),通知AMS应用已就绪,AMS随后通过ApplicationThread回调bindApplication和scheduleLaunchActivity。
bindApplication最终会调用makeApplication,内部执行:
app = mInstrumentation.newApplication(cl, className, context);
app.onCreate(); // 你的Application.onCreate这个阶段是绝大多数性能问题的温床——数据库初始化、图片库、网络库、推送SDK,一股脑全塞进来。
scheduleLaunchActivity → performLaunchActivity → Activity.onCreate → setContentView → LayoutInflater.inflate → onResume → WindowManager.addView → ViewRootImpl → performTraversals → scheduleTraversals(通过Choreographer)→ 最终draw → 第一帧SurfaceFlinger合成。
用户感知“启动完成” 是onWindowFocusChanged之后的第一帧绘制,而非onResume返回。
adb shell到TraceView精准打点adb shell am start -W -n com.your.pkg/.MainActivity输出:
ThisTime: 852
TotalTime: 852
WaitTime: 923ThisTime:最后一个Activity启动耗时(通常就是你的Launcher)TotalTime:所有Activity启动总耗时WaitTime:包含AMS调度耗时利用ActivityLifecycleCallbacks和FrameMetricsAggregator(API 24+):
public class LaunchMonitor {
private static final String KEY_COLD_START = "cold_start_ms";
public static void init(Application app) {
app.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
long startTime;
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (activity instanceof SplashActivity) {
startTime = SystemClock.uptimeMillis();
}
}
@Override
public void onWindowFocusChanged(Activity activity, boolean hasFocus) {
if (hasFocus && activity instanceof MainActivity) {
long cost = SystemClock.uptimeMillis() - startTime;
// 上报到APM平台
ReportHelper.put(KEY_COLD_START, cost);
}
}
});
}
}更精确的做法是使用FrameMetrics监听第一帧:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
window.addOnFrameMetricsAvailableListener((window, frameMetrics, dropCountSinceLastInvocation) -> {
FrameMetrics metrics = frameMetrics.get(0);
long totalDuration = metrics.getMetric(FrameMetrics.TOTAL_DURATION);
// 首次绘制完成
}, new Handler());
}将所有第三方SDK和自身组件按优先级分组,主线程只初始化必须同步的(如崩溃收集、日志),其余全部异步或延迟。
实现一个轻量级启动任务调度器:
public class StartupTaskScheduler {
private final ExecutorService mIoPool = Executors.newCachedThreadPool();
private final List<Runnable> mMainThreadTasks = new ArrayList<>();
private final List<Runnable> mDelayTasks = new ArrayList<>();
private volatile boolean mMainReady = false;
public void addSyncTask(Runnable task) {
mMainThreadTasks.add(task);
}
public void addAsyncTask(Runnable task) {
mIoPool.execute(task);
}
public void addDelayTask(Runnable task, long delayMs) {
mDelayTasks.add(() -> {
try { Thread.sleep(delayMs); } catch (InterruptedException ignored) {}
task.run();
});
}
public void executeSyncTasks() {
for (Runnable task : mMainThreadTasks) {
task.run();
}
mMainReady = true;
// 执行延迟任务(例如IdleHandler触发)
Looper.myQueue().addIdleHandler(() -> {
for (Runnable r : mDelayTasks) {
mIoPool.execute(r);
}
return false;
});
}
}在Application.attachBaseContext中注册,在Application.onCreate中调用executeSyncTasks()。
AsyncLayoutInflater)对非首屏的Fragment或复杂View,使用AsyncLayoutInflater在子线程解析:
new AsyncLayoutInflater(this).inflate(R.layout.complex_fragment, parent, (view, resid, parentView) -> {
// 回调主线程,替换占位View
container.removeAllViews();
container.addView(view);
});注意:AsyncLayoutInflater内部仍使用主线程的LayoutInflater且每次new都会创建新实例,建议复用并配合ViewCache。
更激进的是预创建View对象(在子线程提前inflate并缓存,但要注意Context和主题)。
Application及Activity的类加载开销使用字节码插桩(ASM)在编译期打印类加载耗时,找出锁竞争热点。同时,采用静态内部类单例避免类初始化时加载过多依赖。
关键技巧:提前加载主Activity依赖的类。在Application.attachBaseContext中,利用Class.forName异步加载(类加载是线程安全的,但会触发static块,需确保无副作用)。
// 在子线程预加载
new Thread(() -> {
Class.forName("com.xxx.MainActivity");
Class.forName("com.xxx.databinding.ActivityMainBinding");
}).start();ContentProvider的自动初始化很多第三方库(如Glide、Firebase)会注册ContentProvider,它们在Application.onCreate之前就被系统初始化(installContentProviders)。这会导致额外耗时。
解决方案:在AndroidManifest中手动移除这些Provider,并在Application中按需初始化。例如:
<provider
android:name="com.bumptech.glide.GlideInitializer"
tools:node="remove" />然后在Application中自己调用Glide.init()。
避免为Splash单独设置布局,而是利用windowBackground主题:
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/splash_bg</item>
<item name="android:windowFullscreen">true</item>
</style>这样系统在启动Activity时直接绘制背景图,无需等待布局加载,视觉上“秒开”。然后在SplashActivity的onCreate中setContentView覆盖。
Activity过度绘制与重布局在onResume中避免执行耗时的notifyDataSetChanged。使用ViewStub延迟非首屏控件。
对RecyclerView,采用setHasFixedSize(true)并提前预计算item尺寸。
Jetpack App Startup库统一管理Google官方androidx.startup可以自动优化初始化顺序,并且支持延迟初始化。使用方式:
class MyInitializer : Initializer<Unit> {
override fun create(context: Context) {
// 耗时初始化,但会在主线程按依赖顺序执行
// 如需异步,实现Initializer并返回ListenableFuture
}
override fun dependencies() = listOf(OtherInitializer::class.java)
}在AndroidManifest中注册,并设置tools:node="merge"。缺点:仍运行在主线程(除非返回Future)。我们一般只用它管理必须同步且有序的组件。
ClassLoader加载路径我们团队曾遇到DexPathList查找类时I/O耗时严重(尤其多dex)。通过自定义DelegateClassLoader,在应用启动时预热高频类,并缓存DexFile的dexElements。
另外,开启基线配置文件(Baseline Profiles,Android 7+支持)可显著提升AOT编译效率。在profile目录下放置baseline.prof,包含启动路径涉及的方法和类,Play Console或本地profileinstaller库会提前编译。
dependencies {
implementation "androidx.profileinstaller:profileinstaller:1.3.1"
}在Application.onCreate中调用:
ProfileInstaller.writeProfile(context)实测可减少15%~20% 的冷启动时间(中端设备)。
阶段 | 优化前(ms) | 优化后(ms) | 降幅 |
|---|---|---|---|
Application.attachBaseContext | 120 | 18 | 85% |
Application.onCreate | 340 | 45 | 86.8% |
Activity.onCreate(含inflate) | 280 | 112 | 60% |
首帧绘制完成(TotalTime) | 1180 | 312 | 73.5% |
(测试设备:Redmi K40,Android 12)
TypedArray或Resources,需注意线程安全(Resources非线程安全,但AsyncLayoutInflater已做同步)。IdleHandler执行过长,否则影响滑动流畅度。tools:node="remove"移除Provider时,要确保该库没有依赖Provider的自动注册逻辑(如Firebase会依赖FirebaseInitProvider来初始化FirebaseApp,移除后需手动调用FirebaseApp.initializeApp)。启动速度优化不是“玄学”,而是一套从系统调度、类加载、布局渲染到AOT编译的系统工程。高级工程师的价值在于:用源码验证假设,用工具量化数据,用代码落地方案。
本文给出的调度框架、插桩思路、预加载策略,均已在生产环境验证,可直接移植到你的项目中。如果你能将这些手段灵活组合,并配合CI自动化监控启动耗时,那么“月薪两万”只是一个起点。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。