• 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);
                    ...
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    方法中先调用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));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    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

  • 相关阅读:
    【Java 基础篇】Java Date 类详解:日期和时间操作的利器
    Dubbo中的负载均衡算法之平滑加权轮询算法源码解析
    【设计模式】工厂模式(c++实现)
    echarts-折线图配置详解
    【K8S原理理解】小白级理解 k8s Operator 及 Informer
    java中类A的所有实例方法都可以在A的子类中进行覆盖(Override)吗
    Java ME SDK 3.0
    中创新航:动力电池变局者?
    Jmeter之接口测试流程详解
    [笔记] 筛素数——朴素筛法及其优化:埃氏筛法 #质数(素数)
  • 原文地址:https://blog.csdn.net/CJohn1994/article/details/126716329