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 启动

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

(2) System UI 进程创建 SplashScreen

  1. System UI 负责创建 SplashScreen SplashscreenWindowCreator.addSplashScreenStartingWindow()
  2. 解析应用 Theme,创建 SplashScreenView
  3. 通过 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 传递到应用进程

  1. System UI 进程执行 copySplashScreenView() 生成 Parcelable
  2. 通过 AMS.onSplashScreenViewCopyFinished()SplashScreenView 传递给 应用进程
  3. 应用进程的 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 UISplashScreen 过渡阶段管理 Activity 的窗口。

为什么 Leash 很重要?

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

  1. 增强窗口动画的平滑性

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

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

LeashSplashScreen 过渡中的作用

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

  1. 创建 SplashScreen 窗口

    • System UI 创建 SplashScreenStarting Window)。
    • SplashScreen 绑定 SurfaceControl 并创建 Leash
      SurfaceControl leash = new SurfaceControl.Builder()
          .setName("SplashScreen Leash")
          .build();
      
  2. 窗口 Leash 绑定到 Activity

    • ActivitySurface 创建完成,系统会通过 Leash 控制 SplashScreen 平滑过渡到 Activity
  3. 动画控制和释放 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. 总结

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值