StartingWindow显示流程 - 安卓R

本文详细解析了Android系统中StartingWindow的工作机制,包括STARTING_WINDOW_TYPE_SNAPSHOT与STARTING_WINDOW_TYPE_SPLASH_SCREEN两种类型的区别及实现流程。通过跟踪源码,介绍了不同类型的StartingWindow如何创建、显示以及涉及的主要类和方法。

接着ATMS启动Activity流程中frameworks/base/services/core/java/com/android/server/wm/ActivityStack.java的startActivityLocked方法中调用了frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java的showStartingWindow方法:

    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
        ......
        final boolean shown = addStartingWindow(packageName, theme,
                compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
                allowTaskSnapshot(),
                mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal());
        if (shown) {
            mStartingWindowState = STARTING_WINDOW_SHOWN;
        }
    }

调用了ActivityRecord的addStartingWindow方法:

    boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
            CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
            IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
            boolean allowTaskSnapshot, boolean activityCreated) {
        ......
        final ActivityManager.TaskSnapshot snapshot =
                mWmService.mTaskSnapshotController.getSnapshot(task.mTaskId, task.mUserId,
                        false /* restoreFromDisk */, false /* isLowResolution */);
        final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
                allowTaskSnapshot, activityCreated, snapshot);

        if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
            ......
            return createSnapshot(snapshot);
        }
        ......
        // There is no existing starting window, and we don't want to create a splash screen, so
        // that's it!
        if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
            return false;
        }

        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData");
        mStartingData = new SplashScreenStartingData(mWmService, pkg,
                theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                getMergedOverrideConfiguration());
        scheduleAddStartingWindow();
        return true;
    }

这里的type主要有两种情况,分别是STARTING_WINDOW_TYPE_SNAPSHOTSTARTING_WINDOW_TYPE_SPLASH_SCREEN

如果是STARTING_WINDOW_TYPE_SNAPSHOT,则调用了ActivityRecord的createSnapshot方法:

    private boolean createSnapshot(ActivityManager.TaskSnapshot snapshot) {
        ......
        mStartingData = new SnapshotStartingData(mWmService, snapshot);
        scheduleAddStartingWindow();
        return true;
    }

创建了SnapshotStartingData对象mStartingData,最后调用了ActivityRecord的scheduleAddStartingWindow方法。

回到ActivityRecord的addStartingWindow方法中,如果是STARTING_WINDOW_TYPE_SPLASH_SCREEN,则创建了SplashScreenStartingData对象mStartingData,最后也调用了ActivityRecord的scheduleAddStartingWindow方法:

    void scheduleAddStartingWindow() {
        // Note: we really want to do sendMessageAtFrontOfQueue() because we
        // want to process the message ASAP, before any other queued
        // messages.
        if (!mWmService.mAnimationHandler.hasCallbacks(mAddStartingWindow)) {
            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Enqueueing ADD_STARTING");
            mWmService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
        }
    }

通过mWmService.mAnimationHandler.postAtFrontOfQueue切换到动画线程调用了ActivityRecord的成员变量mAddStartingWindow的run方法:

    private class AddStartingWindow implements Runnable {

        @Override
        public void run() {
            ......
            WindowManagerPolicy.StartingSurface surface = null;
            try {
                surface = startingData.createStartingSurface(ActivityRecord.this);
            } catch (Exception e) {
                Slog.w(TAG, "Exception when adding starting window", e);
            }
            ......
        }
    }

这里调用了之前创建的startingData的createStartingSurface方法,如果是SnapshotStartingData,则调用了frameworks/base/services/core/java/com/android/server/wm/SnapshotStartingData.java的createStartingSurface方法:

    @Override
    StartingSurface createStartingSurface(ActivityRecord activity) {
        return mService.mTaskSnapshotController.createStartingSurface(activity, mSnapshot);
    }

调用了frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotController.java的createStartingSurface方法:

    /**
     * Creates a starting surface for {@param token} with {@param snapshot}. DO NOT HOLD THE WINDOW
     * MANAGER LOCK WHEN CALLING THIS METHOD!
     */
    StartingSurface createStartingSurface(ActivityRecord activity,
            TaskSnapshot snapshot) {
        return TaskSnapshotSurface.create(mService, activity, snapshot);
    }

调用了frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotSurface.java的create方法:

    static TaskSnapshotSurface create(WindowManagerService service, ActivityRecord activity,
            TaskSnapshot snapshot) {

        final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
        final Window window = new Window();
        final IWindowSession session = WindowManagerGlobal.getWindowSession();
        window.setSession(session);
        final SurfaceControl surfaceControl = new SurfaceControl();
        ......
        try {
            final int res = session.addToDisplay(window, window.mSeq, layoutParams,
                    View.GONE, activity.getDisplayContent().getDisplayId(), tmpFrame, tmpRect,
                    tmpRect, tmpCutout, null, mTmpInsetsState, mTempControls);
            if (res < 0) {
                Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
                return null;
            }
        } catch (RemoteException e) {
            // Local call.
        }
        final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
                surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, sysUiVis,
                windowFlags, windowPrivateFlags, taskBounds, currentOrientation, activityType,
                insetsState);
        window.setOuter(snapshotSurface);
        try {
            session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1,
                    tmpFrame, tmpContentInsets, tmpRect, tmpStableInsets, tmpRect,
                    tmpCutout, tmpMergedConfiguration, surfaceControl, mTmpInsetsState,
                    mTempControls, sTmpSurfaceSize, sTmpSurfaceControl);
        } catch (RemoteException e) {
            // Local call.
        }

        final Rect systemBarInsets = getSystemBarInsets(tmpFrame, insetsState);
        snapshotSurface.setFrames(tmpFrame, systemBarInsets);
        snapshotSurface.drawSnapshot();
        return snapshotSurface;
    }

通过session.addToDisplay创建了显示窗口(此流程具体见View的添加流程),并创建了TaskSnapshotSurface对象,存放了显示窗口相关信息。

回到ActivityRecord的成员变量mAddStartingWindow的run方法中,如果startingData是SplashScreenStartingData,则调用了frameworks/base/services/core/java/com/android/server/wm/SplashScreenStartingData.java的createStartingSurface方法:

    @Override
    StartingSurface createStartingSurface(ActivityRecord activity) {
        return mService.mPolicy.addSplashScreen(activity.token, mPkg, mTheme, mCompatInfo,
                mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,
                mMergedOverrideConfiguration, activity.getDisplayContent().getDisplayId());
    }

调用了frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java的addSplashScreen方法:

    /** {@inheritDoc} */
    @Override
    public StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
            int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
            int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
        ......

        WindowManager wm = null;
        View view = null;

        try {
            ......
            final PhoneWindow win = new PhoneWindow(context);
            win.setIsStartingWindow(true);
            ......
            params.setTitle("Splash Screen " + packageName);
            addSplashscreenContent(win, context);

            wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
            view = win.getDecorView();

            if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "Adding splash screen window for "
                + packageName + " / " + appToken + ": " + (view.getParent() != null ? view : null));

            wm.addView(view, params);

            // Only return the view if it was successfully added to the
            // window manager... which we can tell by it having a parent.
            return view.getParent() != null ? new SplashScreenSurface(view, appToken) : null;
        } catch (WindowManager.BadTokenException e) {
            // ignore
            Log.w(TAG, appToken + " already running, starting window not displayed. " +
                    e.getMessage());
        } catch (RuntimeException e) {
            // don't crash if something else bad happens, for example a
            // failure loading resources because we are loading from an app
            // on external storage that has been unmounted.
            Log.w(TAG, appToken + " failed creating starting window", e);
        } finally {
            if (view != null && view.getParent() == null) {
                Log.w(TAG, "view not successfully added to wm, removing view");
                wm.removeViewImmediate(view);
            }
        }

        return null;
    }

通过wm.addView创建显示窗口(此流程具体见View的添加流程),并创建了SplashScreenSurface对象,存放了显示窗口相关信息。

StartingWindow后面是怎样销毁的见StartingWindow销毁流程

总结

1 StartingWindow分为2种,分别是STARTING_WINDOW_TYPE_SNAPSHOT和STARTING_WINDOW_TYPE_SPLASH_SCREEN。

2 STARTING_WINDOW_TYPE_SNAPSHOT一般显示的是Activity之前的一张截图,如果Activity是secure的,则会显示的内容根据其theme绘制。

3 STARTING_WINDOW_TYPE_SPLASH_SCREEN显示的内容根据Activity的theme绘制。

安卓应用的 `StartingWindow`(启动窗口,也称为预览窗口或启动过渡窗口)过程中创建快照,通常是为了捕获应用启动时的初始界面状态(如 `SplashActivity` 或 `MainActivity` 的首次渲染)。以下是具体实现方法及源码级流程分析: --- ### **1. 核心原理** 安卓的 `StartingWindow` 是由系统在 `Activity` 启动时临时显示的窗口,用于避免黑屏或白屏。快照创建的关键点在于: - **时机**:在 `StartingWindow` 显示期间或即将替换为正式 `Activity` 窗口时捕获。 - **方法**:通过 `View` 的绘制回调或 `Window` 的装饰视图(`DecorView`)生成 `Bitmap`。 --- ### **2. 具体实现方法** #### **方法 1:在 `Activity.onCreate()` 中延迟截图** 在 `Activity` 的 `onCreate()` 中,通过 `ViewTreeObserver` 监听视图首次绘制完成的事件: ```java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 获取DecorView并监听首次绘制 final View decorView = getWindow().getDecorView(); decorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // 移除监听器避免重复调用 decorView.getViewTreeObserver().removeOnGlobalLayoutListener(this); // 创建快照 takeSnapshot(); } }); } private void takeSnapshot() { View rootView = getWindow().getDecorView().getRootView(); rootView.setDrawingCacheEnabled(true); Bitmap bitmap = Bitmap.createBitmap(rootView.getDrawingCache()); rootView.setDrawingCacheEnabled(false); // 保存Bitmap到文件(示例) saveBitmapToFile(bitmap, "startup_snapshot.png"); } private void saveBitmapToFile(Bitmap bitmap, String filename) { try (FileOutputStream out = new FileOutputStream(new File(getExternalFilesDir(null), filename))) { bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); } catch (IOException e) { e.printStackTrace(); } } } ``` #### **方法 2:覆盖 `Window` 的 `Callback` 捕获绘制完成** 通过自定义 `Window.Callback` 拦截 `dispatchDraw()` 或 `onDraw()` 事件: ```java public class SnapshotWindowCallback implements Window.Callback { private final Window.Callback originalCallback; private boolean snapshotTaken = false; public SnapshotWindowCallback(Window.Callback originalCallback) { this.originalCallback = originalCallback; } @Override public boolean dispatchTouchEvent(MotionEvent event) { return originalCallback.dispatchTouchEvent(event); } @Override public void onContentChanged() { originalCallback.onContentChanged(); } @Override public void onDraw(Canvas canvas) { originalCallback.onDraw(canvas); if (!snapshotTaken) { snapshotTaken = true; takeSnapshot(); } } // 其他必要方法(省略)... } // 在Activity中设置 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 替换Window的Callback Window.Callback originalCallback = getWindow().getCallback(); getWindow().setCallback(new SnapshotWindowCallback(originalCallback)); } ``` #### **方法 3:使用 `PixelCopy` API(API 26+ 推荐)** 对于高版本安卓,`PixelCopy` 是更高效的截图方式,支持异步请求: ```java private void takeSnapshotWithPixelCopy() { View decorView = getWindow().getDecorView(); Bitmap bitmap = Bitmap.createBitmap(decorView.getWidth(), decorView.getHeight(), Bitmap.Config.ARGB_8888); PixelCopy.request(getWindow(), bitmap, new PixelCopy.OnPixelCopyFinishedListener() { @Override public void onPixelCopyFinished(int result) { if (result == PixelCopy.SUCCESS) { saveBitmapToFile(bitmap, "snapshot_pixelcopy.png"); } } }, new Handler()); } ``` --- ### **3. 源码级流程跟踪** #### **关键调用链** 1. **`ActivityThread.handleLaunchActivity()`** - 系统调用 `ActivityThread` 启动 `Activity`,创建 `PhoneWindow`。 2. **`PhoneWindow.setWindowManager()`** - 初始化 `WindowManager`,准备显示 `StartingWindow`。 3. **`StartingWindow.show()`** - 系统在 `Activity` 完全初始化前显示临时窗口(通常为应用主题背景或静态图)。 4. **`Activity.makeVisible()`** - 当 `Activity` 准备就绪时,替换 `StartingWindow` 为正式窗口,触发 `DecorView` 绘制。 5. **`ViewRootImpl.performTraversals()`** - 实际执行 `measure/layout/draw`,此时可通过 `ViewTreeObserver` 或 `PixelCopy` 捕获内容。 #### **关键代码位置** - **`PhoneWindow.java`** ```java // 显示StartingWindow的逻辑(简化) public void showStartingWindow(Activity activity) { if (mStartingWindow != null) return; mStartingWindow = new StartingWindow(activity, getAttributes()); mWindowManager.addView(mStartingWindow, getAttributes()); } ``` - **`ViewRootImpl.java`** ```java // 触发绘制的核心方法 private void performTraversals() { // ... if (!mStopped) { draw(fullRedrawNeeded); // 实际绘制到Surface } } ``` --- ### **4. 注意事项** 1. **权限**:无需特殊权限,但需确保应用有存储权限(如保存到外部存储)。 2. **性能**:避免在主线程执行耗时操作(如文件写入),推荐异步处理。 3. **兼容性**: - `PixelCopy` 需 API 26+。 - 低版本可使用 `View.draw()` 或 `DrawingCache`。 4. **StartingWindow 特性**: - 若需捕获 `StartingWindow` 本身(而非应用窗口),需通过 `WindowManager` 监听窗口添加事件(需系统权限)。 --- ### **
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

SSSxCCC

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值