Android App 启动界面(SplashScreen)深入解析:从系统机制到优化实践

Android App 启动界面(SplashScreen)深入解析:从系统机制到优化实践

在 Android 应用启动过程中,SplashScreen(启动界面)扮演着至关重要的角色。它不仅用于提升用户体验,避免黑屏现象,还涉及 System UI、WindowManagerService (WMS)、ActivityTaskManagerService (AMS) 等多个核心组件的协作。

本文将深入分析 Android SplashScreen 的显示原理、跨进程机制、优化策略,最终提升应用的启动速度和用户体验。

1. Android 启动界面的显示过程

在 Android 应用启动时,系统会采取一系列策略确保界面平滑过渡。整个过程可以分为 Launcher 动画、Starting Window(启动窗口)、Task Snapshot(任务快照)、Activity 渲染 四个阶段。

(1) Launcher 过渡动画

当用户点击应用图标时:

Launcher(桌面应用) 负责播放 应用图标的放大动画,以提升启动的流畅度。这个动画只是一个过渡,不涉及 App 界面加载。

(2) Starting Window(启动窗口)

由 System UI 进程 负责创建,并由 TaskOrganizer 进行管理。作用:填充 App 启动期间的空白时间,避免黑屏现象。显示内容可以是:

纯色窗口(默认应用主题背景)App Icon(用于视觉衔接)自定义 SplashScreen(支持动画)

(3) Task Snapshot(任务快照)

在 热启动(App 仍然驻留在后台)时,系统会直接显示最近任务的截图 (Task Snapshot),而不是重新加载界面。这样可以减少冷启动时间,提高响应速度。Task Snapshot 由 WMS(WindowManagerService) 维护,并在 ActivityRecord 级别控制其显示。

(4) Activity 启动并绘制界面

ActivityTaskManagerService (AMS) 负责启动应用的 Activity。ActivityRecord 调用 showStartingWindow(),请求 System UI 创建 Starting Window。当 Activity 首次绘制完成 后:

onFirstWindowDrawn() 触发,通知 ActivityRecord 移除 Starting Window。SplashScreen 退出,显示 Activity 主界面。

2. SplashScreen 是如何创建和管理的?

在 SplashScreen 显示的过程中,涉及 System UI、WindowManagerService (WMS)、ActivityTaskManagerService (AMS) 以及 应用进程。下面是具体流程:

(1) SystemServer 触发 Activity 启动

startActivityLocked() -> showStartingWindow()通过 TaskOrganizerController.addStartingWindow() 向 System UI 进程发送创建请求。

(2) System UI 进程创建 SplashScreen

System UI 负责创建 SplashScreen SplashscreenWindowCreator.addSplashScreenStartingWindow()解析应用 Theme,创建 SplashScreenView通过 WindowManagerService.addWindow() 将 SplashScreen 添加到 WMS

public class SplashscreenWindowCreator extends AbsSplashWindowCreator {

void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,

@StartingWindowInfo.StartingWindowType int suggestType) {

final Context context = SplashscreenContentDrawer.createContext(...);

final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(...);

final FrameLayout rootLayout = new FrameLayout(context);

mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo, viewSupplier::setView, ...);

addWindow(taskId, windowInfo.appToken, rootLayout, display, params, suggestType);

}

}

(3) SplashScreen 退出

onFirstWindowDrawn() 触发 removeStartingWindow()。默认动画:淡入淡出 Fade Out 方式移除 SplashScreen。自定义动画:可通过 setOnExitAnimationListener() 实现动画过渡。

removeStartingWindow可以直接看调用堆栈, 里面同时会触发copySplashScreenView:

copySplashScreenView:765, TaskOrganizerController (com.android.server.wm)

requestCopySplashScreen:2818, ActivityRecord (com.android.server.wm)

transferSplashScreenIfNeeded:2807, ActivityRecord (com.android.server.wm)

removeStartingWindow:2946, ActivityRecord (com.android.server.wm)

onFirstWindowDrawn:6956, ActivityRecord (com.android.server.wm)

performShowLocked:4422, WindowState (com.android.server.wm)

commitFinishDrawingLocked:257, WindowStateAnimator (com.android.server.wm)

lambda$new$8:1015, DisplayContent (com.android.server.wm)

$r8$lambda$-dBz3LtsWovWVT0SE5m__EwNfT4:0, DisplayContent (com.android.server.wm)

accept:0, DisplayContent$$ExternalSyntheticLambda32 (com.android.server.wm)

apply:2832, WindowContainer$ForAllWindowsConsumerWrapper (com.android.server.wm)

apply:2822, WindowContainer$ForAllWindowsConsumerWrapper (com.android.server.wm)

applyInOrderWithImeWindows:4656, WindowState (com.android.server.wm)

copySplashScreenView会跨进程到systemui进程.

(4) SplashScreen 传递到应用进程

System UI 进程执行 copySplashScreenView() 生成 Parcelable。通过 AMS.onSplashScreenViewCopyFinished() 将 SplashScreenView 传递给 应用进程。应用进程的 ActivityThread 负责最终展示 SplashScreenView,确保平滑过渡。

因此,SplashScreenView 最初在 System UI 进程绘制,随后通过 copySplashScreenView() 传递给 应用进程 并在 ActivityThread 中显示,确保过渡流畅。

应用进程接收并显示

public final class ActivityThread extends ClientTransactionHandler {

public void handleAttachSplashScreenView(ActivityClientRecord r, SplashScreenViewParcelable parcelable, SurfaceControl startingWindowLeash) {

final SplashScreenView view = new SplashScreenView.Builder(r.activity).createFromParcel(parcelable).build();

decorView.addView(view);

// 这个函数看后面有分析

syncTransferSplashscreenViewTransaction(view, r.token, decorView, startingWindowLeash);

}

}

3. 什么是 Leash?为什么它很重要?

什么是 Leash?

在 Android 窗口管理系统中,Leash(皮带) 是 SurfaceControl 的一个代理对象,用于 控制和管理窗口(Surface),但不会改变窗口的归属。

简单来说:

Leash 是 系统为某个窗口(Surface)创建的一个控制句柄,开发者或系统可以通过它平滑控制窗口的动画、缩放、移动等操作,而不影响窗口的生命周期。Leash 允许 WindowManager 在 不同进程 之间控制窗口,如 System UI 在 SplashScreen 过渡阶段管理 Activity 的窗口。

为什么 Leash 很重要?

在 Android 11(API 30)之后,Google 引入 Leash 机制,目的是:

增强窗口动画的平滑性:

Leash 允许 WindowManager 预先捕获 Activity 的窗口,并在 System UI 进程中控制其动画,而不是直接交给 App 进程处理,这样可以避免启动时的卡顿。

跨进程窗口管理:

例如,SplashScreen 由 System UI 进程创建,然后在 Activity 窗口加载完成后,将 SplashScreen 平滑交给 App 进程,这个过程就依赖 Leash。

Leash 在 SplashScreen 过渡中的作用

在 Android 12+(API 31),SplashScreen 退出时涉及 Leash,具体流程如下:

创建 SplashScreen 窗口

System UI 创建 SplashScreen(Starting Window)。SplashScreen 绑定 SurfaceControl 并创建 Leash:SurfaceControl leash = new SurfaceControl.Builder()

.setName("SplashScreen Leash")

.build();

窗口 Leash 绑定到 Activity

当 Activity 的 Surface 创建完成,系统会通过 Leash 控制 SplashScreen 平滑过渡到 Activity。

动画控制和释放 Leash

Leash 允许 System UI 控制 SplashScreen 的 缩放、淡入淡出、滑动退出 等动画:transaction.setAlpha(leash, 0f);

transaction.apply();

动画完成后,Leash 被销毁,窗口归还给 Activity 进程。

Leash 相关的源码分析

在 ActivityThread 里,Leash 被用于同步 SplashScreen 过渡:

private void syncTransferSplashscreenViewTransaction(

SplashScreenView view, IBinder token, View decorView, @NonNull SurfaceControl startingWindowLeash) {

final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();

// 隐藏 `SplashScreen` 的 `Leash`

transaction.hide(startingWindowLeash);

// 确保 `Activity` 窗口已经准备好

decorView.getViewRootImpl().applyTransactionOnDraw(transaction);

// 交接窗口控制权

view.syncTransferSurfaceOnDraw();

// 通知 `System UI` 移除 `SplashScreen`

decorView.postOnAnimation(() -> reportSplashscreenViewShown(token, view));

}

这里 transaction.hide(startingWindowLeash); 代表 隐藏 SplashScreen,然后交给 Activity 来控制新窗口。

Leash 就像溜狗的皮带,System UI 牵着 SplashScreen(窗口),在 Activity 完全准备好之前,它可以控制窗口的动画、移动、隐藏等;当 Activity 窗口稳定后,Leash 被解除,窗口交给应用本身来管理。

4. 应用如何优化启动速度?

(1) 使用 getSplashScreen()

在 高版本Android,推荐使用:

SplashScreen splashScreen = getSplashScreen();

getSplashScreen() 是 Activity 新增的方法,已经取代了 installSplashScreen()。

内部逻辑:如果 SplashScreen 还未创建,则调用 getOrCreateSplashScreen() 进行初始化:

private SplashScreen getOrCreateSplashScreen() {

synchronized (this) {

if (mSplashScreen == null) {

mSplashScreen = new SplashScreen.SplashScreenImpl(this);

}

return mSplashScreen;

}

}

(2) 让 SplashScreen 退出更平滑

getSplashScreen().setOnExitAnimationListener(splashScreenView -> {

splashScreenView.animate().alpha(0f).setDuration(300).withEndAction(() -> {

splashScreenView.remove();

});

});

(3) 启用 Baseline Profile

Baseline Profile 是一组预定义的类和方法规则,告诉 Android 运行时 提前编译关键代码路径,避免应用首次启动时依赖 JIT 编译. 具体可以参考其他文章。

在 gradle.properties 添加:

android.experimental.art-profile-r8-rewriting=true

这行代码开启 Baseline Profile,并确保 R8(优化编译器)可以正确识别并优化 onCreate() 相关的方法。

然后运行:

./gradlew generateBaselineProfile

Baseline Profile其实也不是什么新技术,其实就是结合ART虚拟机特点,记录应用的热点代码,最终被放在 APK 。

Baseline Profile 介于 JIT 和 AOT 之间,它:

定义应用启动时的关键代码路径(包括 onCreate())强制系统在安装或更新应用时提前编译这些代码避免 JIT 重新编译,从而减少 onCreate() 执行时间

SplashScreen 期间的 onCreate() 执行可能会更快,提升启动速度。

5. 总结

SplashScreen 由 System UI 进程创建,最终通过 Leash 交给 应用进程 显示,实现平滑过渡。Leash 机制确保 SplashScreen 退出时不会闪屏或黑屏,提高流畅度。读者也可以使用 WinScope 分析 SplashScreen 生命周期,可优化 Activity 启动过程。开发者可以通过 getSplashScreen()、Baseline Profile 等优化 SplashScreen 的性能,减少启动延迟。

相关文章

智尊系列 起亚赛拉图车载DVD导航
365BT体育app

智尊系列 起亚赛拉图车载DVD导航

📅 10-18 👀 1329
杜海涛的店开在哪里(详细地址和营业时间)
怎样获得免费office365

杜海涛的店开在哪里(详细地址和营业时间)

📅 11-14 👀 4920
一天慢24小时的表是什么表?
怎样获得免费office365

一天慢24小时的表是什么表?

📅 07-24 👀 8108