赞
踩
上篇介绍应用端适配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
整个流程就是Launcher
到ActivityTaskManagerService
,ActivityTaskManagerService
通知SystemUI
创建SplashScreenView
,SystemUI
再addWindow
通过WMS
添加画面的过程。
时序图如下:
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()
,通过Socket
向Zygote
进程请求创建应用进程。
在添加启动画面的过程中,会进入到 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
注册到ActivityManagerTaskService
中TaskOrganizerController
的列表中,``ActivityManagerTaskService调用的
TaskOrganizer即为
ShellTaskOrganizer```,与SystemUI夸进程通信。
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
添加到PhoneWindow
的DecorView
中与之建立联系。
完整方法调用流程如下:
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
时判断是否转移SplashScreenView
将初始屏幕视图从WMShell传输到客户端。在第一个 onDraw
调用 syncTransferSplashscreenViewTransaction
,这样就可以确保客户端视图已准备好显示,并且我们可以使用 applyTransactionOnDraw
使所有转换发生在同一帧。
确保在删除启动屏幕窗口之前显示启动屏幕视图。一旦复制的初始屏幕视图在DecorView上绘制,使用 applyTransactionOnDraw
确保surfaceview的传输和隐藏起始窗口starting window发生在同一帧。
将SplashScreenView
与Activity的PhoneWindow
绑定attach在一起,也就是添加到Activity对应的DecorView
中,成功显示后再将SplashScreenView
移除并setContentView
。
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
绘制到屏幕上SplashScreen
在contentView
绘制第一帧前移除。
/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
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。