Android 12之启动画面Splash Screens(二) -- framework原理

上篇介绍应用端适配Splash Screens的流程:Android 12之启动画面Splash Screens (一) – 适配,本篇介绍Splash Screens的framework层原理,基于Android12L进行分析,对比Android12有些许变化但流程一致。

系统中创建并预绘制启动画面的流程

SplashScreenView创建与添加流程的时序图如下:
在这里插入图片描述
方法调用流程如下:

Task.startActivityLocked—>
StartingSurfaceController.showStartingWindow—>
ActivityRecord.showStartingWindow—>addStartingWindow—>scheduleAddStartingWindow
—>ActivityRecord.AddStartingWindow.run—>
StartingData.createStartingSurface—>
SplashScreenStartingData.createStartingSurface—>
StartingSurfaceController.createSplashScreenStartingSurface—>
TaskOrganizerController.addStartingWindow—>
TaskOrganizer.addStartingWindow—>
StartingWindowController.addStartingWindow—>
StartingSurfaceDrawer.addSplashScreenStartingWindow—>
SplashscreenContentDrawer.createContentView—>makeSplashScreenContentView—>
StartingWindowViewBuilder.build()—>fillViewWithIcon—SplashScreenView.Builder.build—>SplashScreenView—>
StartingSurfaceDrawer.SplashScreenViewSupplier.setView—>
StartingSurfaceDrawer.addWindow—>
WindowManagerGlobal.addView—>
ViewRootImpl.setView—>IWindowSession.addToDisplayAsUser—>
WindowManagerService. addToDisplay—>addWindow—>
rootLayout.addView

整个流程就是LauncherActivityTaskManagerServiceActivityTaskManagerService通知SystemUI创建SplashScreenViewSystemUIaddWindow通过WMS添加画面的过程。

startActivity的流程

时序图如下:
在这里插入图片描述

Task.startActivityLocked()之前的流程就是Activity.startActivity()进行的流程,方法调用流程如下(承接上述流程):

Activity.startActivity—>startActivityForResult—>
Instrumentation.execStartActivity—>
ActivityStartController.startActivity—>startActivityAsUser—>
getActivityStartController().obtainStarter().execute—>
ActivityStarter.execute—>executeRequest—>startActivityUnchecked—>startActivityInner—>
Task.startActivityLocked—>
StartingSurfaceController.showStartingWindow—>

同时Activity.startActivity()ActivityManagerService发起请求处理startActivity,ActivityManagerService调用ProcessList.startProcess(),通过SocketZygote进程请求创建应用进程。

SystemUI相关组件WMShell

在添加启动画面的过程中,会进入到 SystemUI进程进行一些特殊处理,主要涉及到SysUI的WMShell组件。SystemUI为系统处理各种业务逻辑的关键代码,包含有20多个组件,从中可以看出 SystemUI的复杂程度。其中的WMShell也是复杂多样的,其中SplitScreen分屏模式、OneHanded单手模式、Freeform自由窗口模式、Bubble气泡通知窗口(Android Q)、PIP画中画模式等等系统模式窗口为WMShell处理的一部分,SystemUI引用framework的系统库,通过Dagger2依赖注入,将WMComponent,WMShellModule、WMShellBaseModule整合构建出StartingWindowController、ShellTaskOrganizer、StartingSurfaceDrawer等实例实现启动画面的过渡作用。
方法初始化调用流程如下:

SystemUIFactory.init—>
WMComponent.default init—>getShellInit().init()
InitImpl.init—>
ShellInitImpl.init—>
ShellTaskOrganizer.registerOrganizer()—>
TaskOrganizer.registerOrganizer—>
TaskOrganizerController.registerTaskOrganizer(ITaskOrganizer)—>mTaskOrganizers.add(organizer)

初始化后将ShellTaskOrganizer注册到ActivityManagerTaskServiceTaskOrganizerController的列表中,``ActivityManagerTaskService调用的TaskOrganizer即为ShellTaskOrganizer```,与SystemUI夸进程通信。

addSplashScreenStartingWindow方法的添加流程

StartingSurfaceDrawer.addSplashScreenStartingWindow源码添加SplashScreenView的过程中,代码如下:

    void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,
            @StartingWindowType int suggestType) {
        final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
        final FrameLayout rootLayout = new FrameLayout(
                mSplashscreenContentDrawer.createViewContextWrapper(context));
        rootLayout.setPadding(0, 0, 0, 0);
        rootLayout.setFitsSystemWindows(false);
        final Runnable setViewSynchronized = () -> {
            
            // waiting for setContentView before relayoutWindow
            SplashScreenView contentView = viewSupplier.get();
            final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
            // If record == null, either the starting window added fail or removed already.
            // Do not add this view if the token is mismatch.
            if (record != null && appToken == record.mAppToken) {
                // if view == null then creation of content view was failed.
                if (contentView != null) {
                    try {
                        rootLayout.addView(contentView);
                    } catch (RuntimeException e) {
                        Slog.w(TAG, "failed set content view to starting window "
                                + "at taskId: " + taskId, e);
                        contentView = null;
                    }
                }
                record.setSplashScreenView(contentView);
            }
        };
        ...
        mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
                viewSupplier::setView, viewSupplier::setUiThreadInitTask);
        try {
            if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
                //
                mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
                ...
    }

方法中先调用SplashscreenContentDrawer.createContentView()创建SplashScreenView,创建过程中保存在SplashScreenViewSupplier对象中,调用addWindow方法将类型为FrameLayout的rootLayout添加到Window中(实际上也是添加到DecorView),返回true后通过工作线程(名为setViewSynchronized的Runnable接口)将SplashScreenView添加到rootLayout中。此过程rootLayout.addView()会向WMS申请执行Session#relayout,只有经过了relayout后,窗口才拥有了WMS为其分配的画布,有了画布,窗口才能进行绘制工作。视图如果没有在第一次doFrame上添加到 PhoneWindow,则视图不会在第一帧上呈现。所以这里我们需要在第一轮relayoutWindow之前同步Window上的View。

客户端转移启动画面流程

时序图与系统中创建并预绘制启动画面的流程类似,这里就不贴图了。
上述中(Launcher)通过startActivity启动应用的过程中创建并预绘制启动画面,
SplashScreenView转移到客户端App中。整个流程与上述系统中创建并预绘制启动画面的流程一致,都通过SystemUI的WMShell组件来复制构建SplashScreenView
一开始从Launcher点击启动某个应用时,应用进程还未创建,此时需要StartingWindow预绘制SplashScreenView中的图标和背景等内容,当StartingWindow的第一帧画面显示后移除StartingWindow,再将SplashScreenView转移复制到进程的ActivityThread中,同时将SplashScreenView添加到PhoneWindowDecorView中与之建立联系。
完整方法调用流程如下:

ActivityRecord.onFirstWindowDrawn()—>removeStartingWindow()—>transferSplashScreenIfNeeded()—>requestCopySplashScreen()—>
TaskOrganizerController.copySplashScreenView()—>
ITaskOrganizer.copySplashScreenView()—>
ShellTaskOrganizer.copySplashScreenView()—>
TaskOrganizer.copySplashScreenView()—>
StartingWindowController.copySplashScreenView()—>
StartingSurfaceDrawer.copySplashScreenView()—>
ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished()—>
ActivityTaskManagerService.onSplashScreenViewCopyFinished()—>
ActivityRecord.onCopySplashScreenFinish()—>scheduleTransaction()—>
TransferSplashScreenViewStateItem.execute()—>
ClientTransactionHandler.handleAttachSplashScreenView()
ActivityThread.handleAttachSplashScreenView()—>createSplashScreen()—>
syncTransferSplashscreenViewTransaction()—>reportSplashscreenViewShown()—>
ActivityClient.getInstance().reportSplashScreenAttached()—>
ActivityClientController.splashScreenAttached()—>
ActivityRecord.splashScreenAttachedLock()—>onSplashScreenAttachComplete()—>removeStartingWindowAnimation()—>
StartingSurfaceController.StartingSurface.remove()

removeStartingWindow方法详解

removeStartingWindow时判断是否转移SplashScreenView
将初始屏幕视图从WMShell传输到客户端。在第一个 onDraw调用 syncTransferSplashscreenViewTransaction,这样就可以确保客户端视图已准备好显示,并且我们可以使用 applyTransactionOnDraw使所有转换发生在同一帧。

确保在删除启动屏幕窗口之前显示启动屏幕视图。一旦复制的初始屏幕视图在DecorView上绘制,使用 applyTransactionOnDraw确保surfaceview的传输和隐藏起始窗口starting window发生在同一帧。

SplashScreenView与Activity的PhoneWindow绑定attach在一起,也就是添加到Activity对应的DecorView中,成功显示后再将SplashScreenView移除并setContentView

handleAttachSplashScreenView 方法详解

ActivityThread继承 ClientTransactionHandler执行handleAttachSplashScreenView()
在客户端应用中createSplashScreen()调用方法流程如下:

ActivityThread.handleAttachSplashScreenView()—>createSplashScreen()—>
syncTransferSplashscreenViewTransaction()—>reportSplashscreenViewShown()
—>SplashScreenGlobal.handOverSplashScreenView()
—>SplashScreen.dispatchOnExitAnimation()

其中syncTransferSplashscreenViewTransaction()方法确保在移除闪屏窗口之前显示闪屏视图。一旦复制的SplashScreenView在DecorView上绘制,使用 applyTransactionOnDraw确保表面视图的传输和隐藏起始窗口发生在同一帧

handOverSplashScreenView()方法主要通知SplashScreenView消失,也就是上个章节中SplashScreen.setOnExitAnimationListener的监听接口的回调。

    @Override
    public void handleAttachSplashScreenView(@NonNull ActivityClientRecord r,
            @Nullable SplashScreenView.SplashScreenViewParcelable parcelable,
            @NonNull SurfaceControl startingWindowLeash) {
        final DecorView decorView = (DecorView) r.window.peekDecorView();
        if (parcelable != null && decorView != null) {
            createSplashScreen(r, decorView, parcelable, startingWindowLeash);
        } else {
            // shouldn't happen!
            Slog.e(TAG, "handleAttachSplashScreenView failed, unable to attach");
        }
    }

    private void createSplashScreen(ActivityClientRecord r, DecorView decorView,
            SplashScreenView.SplashScreenViewParcelable parcelable,
            @NonNull SurfaceControl startingWindowLeash) {
        final SplashScreenView.Builder builder = new SplashScreenView.Builder(r.activity);
        final SplashScreenView view = builder.createFromParcel(parcelable).build();
        view.attachHostWindow(r.window);
        decorView.addView(view);
        view.requestLayout();

        view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
            private boolean mHandled = false;
            @Override
            public void onDraw() {
                if (mHandled) {
                    return;
                }
                mHandled = true;
                // Transfer the splash screen view from shell to client.
                // Call syncTransferSplashscreenViewTransaction at the first onDraw so we can ensure
                // the client view is ready to show and we can use applyTransactionOnDraw to make
                // all transitions happen at the same frame.
                syncTransferSplashscreenViewTransaction(
                        view, r.token, decorView, startingWindowLeash);
                view.post(() -> view.getViewTreeObserver().removeOnDrawListener(this));
            }
        });
    }

    private void reportSplashscreenViewShown(IBinder token, SplashScreenView view) {
        ActivityClient.getInstance().reportSplashScreenAttached(token);
        synchronized (this) {
            if (mSplashScreenGlobal != null) {
                mSplashScreenGlobal.handOverSplashScreenView(token, view);
            }
        }
    }

    private void syncTransferSplashscreenViewTransaction(SplashScreenView view, IBinder token,
            View decorView, @NonNull SurfaceControl startingWindowLeash) {
        // Ensure splash screen view is shown before remove the splash screen window.
        // Once the copied splash screen view is onDrawn on decor view, use applyTransactionOnDraw
        // to ensure the transfer of surface view and hide starting window are happen at the same
        // frame.
        final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
        transaction.hide(startingWindowLeash);

        decorView.getViewRootImpl().applyTransactionOnDraw(transaction);
        view.syncTransferSurfaceOnDraw();
        // Tell server we can remove the starting window
        decorView.postOnAnimation(() -> reportSplashscreenViewShown(token, view));
    }

onResume中,才真正去将PhoneWindow中的DecorView绘制到屏幕上SplashScreencontentView绘制第一帧前移除。

API源码目录

/frameworks/base/core/java/android/app/ActivityTaskManager.java
/frameworks/base/core/java/android/app/ActivityThread.java
/frameworks/base/core/java/android/app/ClientTransactionHandler.java
/frameworks/base/core/java/android/app/ActivityClient.java
/frameworks/base/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java

/frameworks/base/core/java/android/view/WindowManagerGlobal.java

/frameworks/base/core/java/android/window/TaskOrganizer.java
/frameworks/base/core/java/android/window/SplashScreenView.java
/frameworks/base/core/java/android/window/ITaskOrganizer.aidl

/frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java
/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
/frameworks/base/services/core/java/com/android/server/wm/ActivityStartController.java
/frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
/frameworks/base/services/core/java/com/android/server/wm/SplashScreenStarting
Data.java
/frameworks/base/services/core/java/com/android/server/wm/StartingData.java
/frameworks/base/services/core/java/com/android/server/wm/StartingSurfaceController.java
/frameworks/base/services/core/java/com/android/server/wm/Task.java
/frameworks/base/services/core/java/com/android/server/wm/TaskOrganizerController.java

/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java

/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
/frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

言并肃

感谢大哥支持!您的鼓励是我动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值