启动窗口 [Android R]

一、启动窗口的类型

	private static final int STARTING_WINDOW_TYPE_NONE = 0; //不使用启动窗口
    private static final int STARTING_WINDOW_TYPE_SNAPSHOT = 1; //应用热启动,启动窗口是activity的一张截图
    private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2; //应用冷启动,启动窗口是应用配置的一张图片

二、启动窗口的意义

应用有三种启动状态:冷启动、温启动、热启动
冷启动:应用自设备开机后首次启动;系统终止应用后,应用首次启动。时间开销最大。
热启动:应用的所有Activity仍驻留在内存中,系统的全部工作只是将Activity重新带到前台。时间开销最小。
温启动:应用进程已经存在,但应用必须通过调用onCreate()从头重新创建Activity。时间开销中间。

应用冷启动时,自点击home launcher的应用Icon起,到应用完成第一次绘制,系统进程、应用进程要完成大量的初始化工作,非常费时。假如没有starting window,这一过程给用户的感受就是:这应用好卡,点击之后3秒钟过去了,还没反应;或者,我是不是没点到应用图标上。starting window的意义就在于,用户点击应用Icon之后,启动窗口会立即打开,等应用完成第一次绘制后,启动窗口被系统销毁,纵享丝滑。

三、启动窗口的流程

3.1 ActivityStack.SHOW_APP_STARTING_PREVIEW

    // Set to false to disable the preview that is shown while a new activity
    // is being started.
    private static final boolean SHOW_APP_STARTING_PREVIEW = true; // 1. starting window的总开关
    ...
void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
            boolean newTask, boolean keepCurTransition, ActivityOptions options) {
        Task rTask = r.getTask();
        final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront();
        final boolean isOrhasTask = rTask == this || hasChild(rTask);
        ...
            } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
                // Figure out if we are transitioning from another activity that is
                // "has the same starting icon" as the next one.  This allows the
                // window manager to keep the previous window it had previously
                // created, if it still had one.
                Task prevTask = r.getTask();
                ActivityRecord prev = prevTask.topActivityWithStartingWindow();
                if (prev != null) {
                    // We don't want to reuse the previous starting preview if:
                    // (1) The current activity is in a different task.
                    if (prev.getTask() != prevTask) {
                        prev = null;
                    }
                    // (2) The current activity is already displayed.
                    else if (prev.nowVisible) {
                        prev = null;
                    }
                }
                r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity)); // 2. 调用ActivityRecord默认方法,两个类在同一个package,无需import
            }
        } else {
            // If this is the first activity, don't do any fancy animations,
            // because there is nothing for it to animate on top of.
            ActivityOptions.abort(options);
        }
    }

注释1:该boolean值是ActivityStack类的全局变量,正如英文注释,此布尔值是startingwindow的总开关。
注释2:r是startActivityLocked方法的一个形参,是ActivityRecord类型的变量,调用ActivityRecord.showStartingWindow()方法。

3.1 ActivityRecord.showStartingWindow()

    static final int STARTING_WINDOW_NOT_SHOWN = 0;
    static final int STARTING_WINDOW_SHOWN = 1;
    static final int STARTING_WINDOW_REMOVED = 2;
    int mStartingWindowState = STARTING_WINDOW_NOT_SHOWN; // 1. 启动窗口的状态

    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
        if (mTaskOverlay) {
            // We don't show starting window for overlay activities.
            return;
        }
        if (pendingOptions != null
                && pendingOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
            // Don't show starting window when using shared element transition.
            return;
        }

        final CompatibilityInfo compatInfo =
                mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo);
        final boolean shown = addStartingWindow(packageName, theme,
                prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
                allowTaskSnapshot(),
                mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal()); // 2. 调用本类方法
        if (shown) {
            mStartingWindowState = STARTING_WINDOW_SHOWN; // 3. 置启动窗口的状态
        }
    }

注释1:全局变量,访问权限为default。描述启动窗口的状态,一共有3种状态:默认的not_shown;添加成功后的shown;移除后的removed。
注释2:调用本类的addStartingWindow方法,返回值类型为boolean值,代表启动窗口是否添加成功。
注释3:启动窗口添加成功后,其状态设置为shown。

3.3 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 int type = getStartingWindowType(newTask, taskSwitch, processRunning,
                allowTaskSnapshot, activityCreated, snapshot);

        if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
            if (isActivityTypeHome()) {
                // The snapshot of home is only used once because it won't be updated while screen
                // is on (see {@link TaskSnapshotController#screenTurningOff}).
                mWmService.mTaskSnapshotController.removeSnapshotCache(task.mTaskId);
                if ((mDisplayContent.mAppTransition.getTransitFlags()
                        & WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0) {
                    // Only use snapshot of home as starting window when unlocking directly.
                    return false;
                }
            }
            return createSnapshot(snapshot); // 1. 应用热启动
        }

        // If this is a translucent window, then don't show a starting window -- the current
        // effect (a full-screen opaque starting window that fades away to the real contents
        // when it is ready) does not work for this.
        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme);
        if (theme != 0) {
            AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
                    com.android.internal.R.styleable.Window,
                    mWmService.mCurrentUserId);
            if (ent == null) {
                // Whoops!  App doesn't exist. Um. Okay. We'll just pretend like we didn't
                // see that.
                return false;
            }
            final boolean windowIsTranslucent = ent.array.getBoolean(
                    com.android.internal.R.styleable.Window_windowIsTranslucent, false); // 2. 查看启动Activity主题的windowIsTranslucent属性
            final boolean windowIsFloating = ent.array.getBoolean(
                    com.android.internal.R.styleable.Window_windowIsFloating, false); // 3. 查看windowIsFloating属性
            final boolean windowShowWallpaper = ent.array.getBoolean(
                    com.android.internal.R.styleable.Window_windowShowWallpaper, false);
            final boolean windowDisableStarting = ent.array.getBoolean(
                    com.android.internal.R.styleable.Window_windowDisablePreview, false); // 4.查看windowDisablePreview属性
            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Translucent=%s Floating=%s ShowWallpaper=%s",
                    windowIsTranslucent, windowIsFloating, windowShowWallpaper);
            if (windowIsTranslucent) {
                return false;
            }
            if (windowIsFloating || windowDisableStarting) {
                return false;
            }
            if (windowShowWallpaper) {
                if (getDisplayContent().mWallpaperController
                        .getWallpaperTarget() == null) {
                    // If this theme is requesting a wallpaper, and the wallpaper
                    // is not currently visible, then this effectively serves as
                    // an opaque window and our starting window transition animation
                    // can still work.  We just need to make sure the starting window
                    // is also showing the wallpaper.
                    windowFlags |= FLAG_SHOW_WALLPAPER;
                } else {
                    return false;
                }
            }
        }

        if (transferStartingWindow(transferFrom)) {
            return true;
        }

        // 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) { // 5. 代码走到这里,说明启动窗口的类型不是snapshot。如果也不是splash,那就只能是none.
            return false;
        }

        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData");
        mStartingData = new SplashScreenStartingData(mWmService, pkg, // 6. mStartingData赋值
                theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                // getMergedOverrideConfiguration());
                getMergedOverrideConfiguration(), overrideTheme);
        scheduleAddStartingWindow(); // 7. 发送消息
        return true;
    }

注释1:return createSnapshot(snapshot);启动窗口类型为snapshot,给mStartingData赋值snapshotdata,然后发送消息。

    private boolean createSnapshot(ActivityManager.TaskSnapshot snapshot) {
        if (snapshot == null) {
            return false;
        }

        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SnapshotStartingData");
        mStartingData = new SnapshotStartingData(mWmService, snapshot); // 给mStartingData赋值snapshotdata,类比注释6
        scheduleAddStartingWindow(); // 对应注释7
        return true;
    }

注释2、3、4:代码走到这,说明是应用冷启动。查看应用launcher activity的主题,如果其主题将这3个属性中的任何一个置为true,冷启动的时候,不会创建splash screen。
注释5:代码走到这,说明启动窗口的type既不是snapshot,也不是splashscreen,只能是none,直接return false即可,即根本没有启动窗口。
注释6 、7类比注释1。

3.4 ActivityRecord.scheduleAddStartingWindow()

	private final AddStartingWindow mAddStartingWindow = new AddStartingWindow();
	...
	
    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); // 1. Handler机制,跨线程通信。
        }
    }

    private class AddStartingWindow implements Runnable {

        @Override
        public void run() {
            // Can be accessed without holding the global lock
            final StartingData startingData;
            synchronized (mWmService.mGlobalLock) {
                // There can only be one adding request, silly caller!
                mWmService.mAnimationHandler.removeCallbacks(this);

                if (mStartingData == null) {
                    // Animation has been canceled... do nothing.
                    ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
                            "startingData was nulled out before handling"
                                    + " mAddStartingWindow: %s", ActivityRecord.this);
                    return;
                }

               ///M: Check starting surface aleady add or not.
                if (startingSurface != null) {
                    Slog.v(TAG_WM, "already has a starting surface!!!");
                    return;
                }

                startingData = mStartingData; // 2. 可能有两种类型。
            }

            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Add starting %s: startingData=%s",
                    this, startingData);


            WindowManagerPolicy.StartingSurface surface = null;
            try {
                surface = startingData.createStartingSurface(ActivityRecord.this); // 3. 可能两种类型
            } catch (Exception e) {
                Slog.w(TAG, "Exception when adding starting window", e);
            }
            if (surface != null) {
                boolean abort = false;
                synchronized (mWmService.mGlobalLock) {
                    // If the window was successfully added, then we need to remove it.
                    if (mStartingData == null) {
                        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Aborted starting %s: startingData=%s",
                                ActivityRecord.this, mStartingData);

                        startingWindow = null;
                        mStartingData = null;
                        abort = true;
                    } else {
                        startingSurface = surface;
                    }
                    if (!abort) {
                        ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
                                "Added starting %s: startingWindow=%s startingView=%s",
                                ActivityRecord.this, startingWindow, startingSurface);
                    }
                }
                if (abort) {
                    surface.remove();
                }
            } else {
                ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Surface returned was null: %s",
                        ActivityRecord.this);
            }
        }
    }

注释1: mAddStartingWindow是AddStartingWindow类型的变量,AddStartingWindow实现了Runnable接口,执行mAddStartingWindow的run()方法。
注释2、3:可能有snapshot、splash两种类型,下面主要介绍splash的情况。

3.5 SplashScreenStartingData.createStartingSurface()

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

3.6 PhoneWindowManager.addSplashScreen()

public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
            int logo, int windowFlags, Configuration overrideConfig, int displayId) {
        if (!SHOW_SPLASH_SCREENS) {
            return null;
        }
        if (packageName == null) {
            return null;
        }

        WindowManager wm = null;
        View view = null;

        try {
            Context context = mContext;
            if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "addSplashScreen " + packageName
                    + ": nonLocalizedLabel=" + nonLocalizedLabel + " theme="
                    + Integer.toHexString(theme));

            // Obtain proper context to launch on the right display.
            final Context displayContext = getDisplayContext(context, displayId);
            if (displayContext == null) {
                // Can't show splash screen on requested display, so skip showing at all.
                return null;
            }
            context = displayContext;

            if (theme != context.getThemeResId() || labelRes != 0) {
                try {
                    context = context.createPackageContext(packageName, CONTEXT_RESTRICTED);
                    context.setTheme(theme);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }

            if (overrideConfig != null && !overrideConfig.equals(EMPTY)) {
                if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "addSplashScreen: creating context based"
                        + " on overrideConfig" + overrideConfig + " for splash screen");
                final Context overrideContext = context.createConfigurationContext(overrideConfig);
                overrideContext.setTheme(theme);
                final TypedArray typedArray = overrideContext.obtainStyledAttributes(
                        com.android.internal.R.styleable.Window);
                final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
                if (resId != 0 && overrideContext.getDrawable(resId) != null) {
                    // We want to use the windowBackground for the override context if it is
                    // available, otherwise we use the default one to make sure a themed starting
                    // window is displayed for the app.
                    if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "addSplashScreen: apply overrideConfig"
                            + overrideConfig + " to starting window resId=" + resId);
                    context = overrideContext;
                }
                typedArray.recycle();
            }

            final PhoneWindow win = new PhoneWindow(context);
            win.setIsStartingWindow(true);

            CharSequence label = context.getResources().getText(labelRes, null);
            // Only change the accessibility title if the label is localized
            if (label != null) {
                win.setTitle(label, true);
            } else {
                win.setTitle(nonLocalizedLabel, false);
            }

            win.setType(
                WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);

            synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
                // Assumes it's safe to show starting windows of launched apps while
                // the keyguard is being hidden. This is okay because starting windows never show
                // secret information.
                // TODO(b/113840485): Occluded may not only happen on default display
                if (displayId == DEFAULT_DISPLAY && mKeyguardOccluded) {
                    windowFlags |= FLAG_SHOW_WHEN_LOCKED;
                }
            }

            // Force the window flags: this is a fake window, so it is not really
            // touchable or focusable by the user.  We also add in the ALT_FOCUSABLE_IM
            // flag because we do know that the next window will take input
            // focus, so we want to get the IME window up on top of us right away.
            win.setFlags(
                windowFlags|
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                windowFlags|
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);

            win.setDefaultIcon(icon);
            win.setDefaultLogo(logo);

            win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.MATCH_PARENT);

            final WindowManager.LayoutParams params = win.getAttributes();
            params.token = appToken;
            params.packageName = packageName;
            params.windowAnimations = win.getWindowStyle().getResourceId(
                    com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
            params.privateFlags |=
                    WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
            params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;

            if (!compatInfo.supportsScreen()) {
                params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
            }

            params.setTitle("Splash Screen " + packageName);
            // addSplashscreenContent(win, context);
            boolean contentAdded = addSplashscreenContent(win, context);

            wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
            view = win.getDecorView();
            if ((context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
                    != Configuration.UI_MODE_NIGHT_YES) {
                PhoneWindowManagerInjector.addStartingWindow(context, view, win, label != null ? label : nonLocalizedLabel);
            }

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

            wm.addView(view, params); // 1. 添加view

            // 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;
    }

注释1:wm.addview。wm是WindowManager类型的,addview()方法的真正实现在WindowManagerImpl类中。

3.7 WindowManagerImpl.addview()

    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

3.8 WindowManagerGlobal.addView()

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId); // 1. 调用
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                index = findViewLocked(view, false);
                if (index >= 0) {
                    Log.e(TAG, "BadTokenException or InvalidDisplayException, clean up.");
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

注释1:调用ViewRootImpl的setView()方法

3.9 ViewRootImpl.setView()

try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    adjustLayoutParamsForCompatibility(mWindowAttributes);
                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls); // 1. 跨进程通信
                    setFrame(mTmpFrame);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    inputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

3.10 Session.addToDisplayAsUser()

public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, int userId, Rect outFrame,
            Rect outContentInsets, Rect outStableInsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
                outInsetsState, outActiveControls, userId);
    }

3.11 WindowManagerService.addWindow()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值