精华内容
下载资源
问答
  • 2021-04-25 07:49:39

    概述

    window是android中非常常见的一个概念。Activity、Dialog、Toast这些常用的知识点都是和window密不可分的。
    因此,笔者整理了下window相关的知识,期望能对需要的读者有所帮助。

    window官方描述

    Window源码中对window的描述如下:

    /**
     * Abstract base class for a top-level window look and behavior policy.  An
     * instance of this class should be used as the top-level view added to the
     * window manager. It provides standard UI policies such as a background, title
     * area, default key processing, etc.
     *
     * <p>The only existing implementation of this abstract class is
     * android.view.PhoneWindow, which you should instantiate when needing a
     * Window.
     */
    

    笔者翻译后的大致意思如下:

    • window是一个抽象类,主要用来处理窗口的展示与行为策略(比如触摸,点击等)。
    • window类的实例应该是一个被添加到windowManager的顶级视图。
    • window提供了标准的UI策略,如背景,标题区域,默认密钥处理等。
    • widnow唯一的实现类是android.view.PhoneWindow,如果要使用window就必须通过android.view.PhoneWindow。

    window与decorview

    window与decorview的逻辑关系如下:

    • 一个Activity对应一个PhoneWindow,PhoneWidnow会处理这个activity中的ui展示和 用户的行为(如触摸,点击等)。
    • PhoneWidnow不是一个View对象,通过将PhoneWindow添加到windowManager中,PhoneWindow能够将要处理的行为事件传递给DecorView。
    • DecorView继承自FrameLayout,是除了Window之外最顶级的视图。
    • ContentView就是我们通常使用activity.setContentView()中设置的View。它所对应的id是R.id.content。
      在这里插入图片描述

    源码解析

    接下来就通过源码解析看下具体的window相关的层级之间的关系。

    Activity.setContentView()

    逻辑如下:

    1. 调用window.setContentView() 来初始化Layout
    2. 调用initWindowDecorActionBar来初始化 actionBar

    由于是关注window相关层级之间的关系,因此接下来直接看window.setContentView的源码。

        public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
    

    PhoneWindow.setContentView()

    根据上文解析的Window的注释,已知window的实例是PhoneWindow,因此直接跳转到PhoneWindow的源码。

    主要逻辑如下:

    1. 首先判断contentView是否已经创建,如果没有创建就需要调用installDecor()来初始化DecorView。
    2. 然后会调用inflate()方法,将layout添加到contentView中。(FEATURE_CONTENT_TRANSITIONS是转场动画相关逻辑,暂且不看。)
    3. 最后调用window的callBack的onContentChanged()方法,通知window内容的更改。
        // This is the view in which the window contents are placed. It is either
        // mDecor itself, or a child of mDecor where the contents go.
        ViewGroup mContentParent;
    
        public void setContentView(int layoutResID) {
            if (mContentParent == null) {
                installDecor();
            } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                mContentParent.removeAllViews();
            }
    
            if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                        getContext());
                transitionTo(newScene);
            } else {
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    PhoneWindow.installDecor()

    installDecor()是初始化DecorView的相关逻辑,主要逻辑如下:

    1. 判断DecorView是否为空,如果为空就调用generateDecor()来创建。如果已经初始化过了,那么就再调用setWindow绑定一下此时的window。
    2. 判断contentView是否为空,如果为空,就调用generateLayout()来创建。
    3. decorContentParent就是toolbar的View,也是通过判断是否为空来执行初始化逻辑。
    4. 而后的FEATURE_ACTIVITY_TRANSITIONS就是转场动画相关的逻辑,暂且不看。
        private void installDecor() {
            mForceDecorInstall = false;
            if (mDecor == null) {
                mDecor = generateDecor(-1);
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            } else {
                mDecor.setWindow(this);
            }
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor);
    
                // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
                mDecor.makeOptionalFitsSystemWindows();
    
                final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                        R.id.decor_content_parent);
    
                if (decorContentParent != null) {
                    mDecorContentParent = decorContentParent;
                    mDecorContentParent.setWindowCallback(getCallback());
                    if (mDecorContentParent.getTitle() == null) {
                        mDecorContentParent.setWindowTitle(mTitle);
                    }
    
                    final int localFeatures = getLocalFeatures();
                    for (int i = 0; i < FEATURE_MAX; i++) {
                        if ((localFeatures & (1 << i)) != 0) {
                            mDecorContentParent.initFeature(i);
                        }
                    }
    
                    mDecorContentParent.setUiOptions(mUiOptions);
    
                    if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
                            (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
                        mDecorContentParent.setIcon(mIconRes);
                    } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
                            mIconRes == 0 && !mDecorContentParent.hasIcon()) {
                        mDecorContentParent.setIcon(
                                getContext().getPackageManager().getDefaultActivityIcon());
                        mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
                    }
                    if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
                            (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
                        mDecorContentParent.setLogo(mLogoRes);
                    }
    
                    // Invalidate if the panel menu hasn't been created before this.
                    // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
                    // being called in the middle of onCreate or similar.
                    // A pending invalidation will typically be resolved before the posted message
                    // would run normally in order to satisfy instance state restoration.
                    PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
                    if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
                        invalidatePanelMenu(FEATURE_ACTION_BAR);
                    }
                } else {
                    mTitleView = findViewById(R.id.title);
                    if (mTitleView != null) {
                        if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                            final View titleContainer = findViewById(R.id.title_container);
                            if (titleContainer != null) {
                                titleContainer.setVisibility(View.GONE);
                            } else {
                                mTitleView.setVisibility(View.GONE);
                            }
                            mContentParent.setForeground(null);
                        } else {
                            mTitleView.setText(mTitle);
                        }
                    }
                }
    
                if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
                    mDecor.setBackgroundFallback(mBackgroundFallbackResource);
                }
    
                // Only inflate or create a new TransitionManager if the caller hasn't
                // already set a custom one.
                if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
                    if (mTransitionManager == null) {
                        final int transitionRes = getWindowStyle().getResourceId(
                                R.styleable.Window_windowContentTransitionManager,
                                0);
                        if (transitionRes != 0) {
                            final TransitionInflater inflater = TransitionInflater.from(getContext());
                            mTransitionManager = inflater.inflateTransitionManager(transitionRes,
                                    mContentParent);
                        } else {
                            mTransitionManager = new TransitionManager();
                        }
                    }
    
                    mEnterTransition = getTransition(mEnterTransition, null,
                            R.styleable.Window_windowEnterTransition);
                    mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
                            R.styleable.Window_windowReturnTransition);
                    mExitTransition = getTransition(mExitTransition, null,
                            R.styleable.Window_windowExitTransition);
                    mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
                            R.styleable.Window_windowReenterTransition);
                    mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
                            R.styleable.Window_windowSharedElementEnterTransition);
                    mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
                            USE_DEFAULT_TRANSITION,
                            R.styleable.Window_windowSharedElementReturnTransition);
                    mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
                            R.styleable.Window_windowSharedElementExitTransition);
                    mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
                            USE_DEFAULT_TRANSITION,
                            R.styleable.Window_windowSharedElementReenterTransition);
                    if (mAllowEnterTransitionOverlap == null) {
                        mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
                                R.styleable.Window_windowAllowEnterTransitionOverlap, true);
                    }
                    if (mAllowReturnTransitionOverlap == null) {
                        mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
                                R.styleable.Window_windowAllowReturnTransitionOverlap, true);
                    }
                    if (mBackgroundFadeDurationMillis < 0) {
                        mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
                                R.styleable.Window_windowTransitionBackgroundFadeDuration,
                                DEFAULT_BACKGROUND_FADE_DURATION_MS);
                    }
                    if (mSharedElementsUseOverlay == null) {
                        mSharedElementsUseOverlay = getWindowStyle().getBoolean(
                                R.styleable.Window_windowSharedElementsUseOverlay, true);
                    }
                }
            }
        }
    

    PhoneWindow.generateDecor()

    generateDecor()是创建DecorView的相关逻辑,主要逻辑如下:

    1. 先判断此时的context是否是DecorContext,如果不是,就通过application和此时的content来new 一个DecorContext。
    2. 直接new一个DecorView返回,需要传入参数DecorContext 和此时的window。
        protected DecorView generateDecor(int featureId) {
            // System process doesn't have application context and in that case we need to directly use
            // the context we have. Otherwise we want the application context, so we don't cling to the
            // activity.
            Context context;
            if (mUseDecorContext) {
                Context applicationContext = getContext().getApplicationContext();
                if (applicationContext == null) {
                    context = getContext();
                } else {
                    context = new DecorContext(applicationContext, getContext());
                    if (mTheme != -1) {
                        context.setTheme(mTheme);
                    }
                }
            } else {
                context = getContext();
            }
            return new DecorView(context, featureId, this, getAttributes());
        }
    

    PhoneWindow.generateLayout()

    generateLayout()是创建contentView的方法,主要逻辑如下:

    1. 通过getWindowStyle()获取到window的TypeArray,然后通过这个TypeArray来获取window相关的状态,从而来设置window的属性。(常见的状态如:是否有title,是否有actionbar等)
    2. 此处还有window.getContainer()的逻辑。举个例子,一个dialog会有自己的Window,Dialog的Window的Container就是 它的Activity的Window。
    3. 最终会通过id=ID_ANDROID_CONTENT获取到contentParent返回。ID_ANDROID_CONTENT = com.android.internal.R.id.content。(使用Fragement的时候经常会使用R.id.content,其实就是对应contentView)
        protected ViewGroup generateLayout(DecorView decor) {
            // Apply data from current theme.
    
            TypedArray a = getWindowStyle();
    
            if (false) {
                System.out.println("From style:");
                String s = "Attrs:";
                for (int i = 0; i < R.styleable.Window.length; i++) {
                    s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                            + a.getString(i);
                }
                System.out.println(s);
            }
    
            mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
            int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                    & (~getForcedWindowFlags());
            if (mIsFloating) {
                setLayout(WRAP_CONTENT, WRAP_CONTENT);
                setFlags(0, flagsToUpdate);
            } else {
                setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
            }
    
            if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
                requestFeature(FEATURE_NO_TITLE);
            } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
                // Don't allow an action bar if there is no title.
                requestFeature(FEATURE_ACTION_BAR);
            }
    
            if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
                requestFeature(FEATURE_ACTION_BAR_OVERLAY);
            }
    
            if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
                requestFeature(FEATURE_ACTION_MODE_OVERLAY);
            }
    
            if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
                requestFeature(FEATURE_SWIPE_TO_DISMISS);
            }
    
            if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
                setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
            }
    
            if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
                    false)) {
                setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                        & (~getForcedWindowFlags()));
            }
    
            if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
                    false)) {
                setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
                        & (~getForcedWindowFlags()));
            }
    
            if (a.getBoolean(R.styleable.Window_windowOverscan, false)) {
                setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags()));
            }
    
            if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
                setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
            }
    
            if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,
                    getContext().getApplicationInfo().targetSdkVersion
                            >= android.os.Build.VERSION_CODES.HONEYCOMB)) {
                setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
            }
    
            a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
            a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
            if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString()
                    + ", major: " + mMinWidthMajor.coerceToString());
            if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
                if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
                a.getValue(R.styleable.Window_windowFixedWidthMajor,
                        mFixedWidthMajor);
            }
            if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
                if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
                a.getValue(R.styleable.Window_windowFixedWidthMinor,
                        mFixedWidthMinor);
            }
            if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
                if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
                a.getValue(R.styleable.Window_windowFixedHeightMajor,
                        mFixedHeightMajor);
            }
            if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
                if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
                a.getValue(R.styleable.Window_windowFixedHeightMinor,
                        mFixedHeightMinor);
            }
            if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
                requestFeature(FEATURE_CONTENT_TRANSITIONS);
            }
            if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
                requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
            }
    
            mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
    
            final Context context = getContext();
            final int targetSdk = context.getApplicationInfo().targetSdkVersion;
            final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;
            final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
            final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
            final boolean targetHcNeedsOptions = context.getResources().getBoolean(
                    R.bool.target_honeycomb_needs_options_menu);
            final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);
    
            if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {
                setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE);
            } else {
                setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE);
            }
    
            if (!mForcedStatusBarColor) {
                mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
            }
            if (!mForcedNavigationBarColor) {
                mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
                mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
                        0x00000000);
            }
    
            WindowManager.LayoutParams params = getAttributes();
    
            // Non-floating windows on high end devices must put up decor beneath the system bars and
            // therefore must know about visibility changes of those.
            if (!mIsFloating) {
                if (!targetPreL && a.getBoolean(
                        R.styleable.Window_windowDrawsSystemBarBackgrounds,
                        false)) {
                    setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                            FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
                }
                if (mDecor.mForceWindowDrawsStatusBarBackground) {
                    params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
                }
            }
            if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
                decor.setSystemUiVisibility(
                        decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
            }
            if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
                decor.setSystemUiVisibility(
                        decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
            }
            if (a.hasValue(R.styleable.Window_windowLayoutInDisplayCutoutMode)) {
                int mode = a.getInt(R.styleable.Window_windowLayoutInDisplayCutoutMode, -1);
                if (mode < LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
                        || mode > LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER) {
                    throw new UnsupportedOperationException("Unknown windowLayoutInDisplayCutoutMode: "
                            + a.getString(R.styleable.Window_windowLayoutInDisplayCutoutMode));
                }
                params.layoutInDisplayCutoutMode = mode;
            }
    
            if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
                    >= android.os.Build.VERSION_CODES.HONEYCOMB) {
                if (a.getBoolean(
                        R.styleable.Window_windowCloseOnTouchOutside,
                        false)) {
                    setCloseOnTouchOutsideIfNotSet(true);
                }
            }
    
            if (!hasSoftInputMode()) {
                params.softInputMode = a.getInt(
                        R.styleable.Window_windowSoftInputMode,
                        params.softInputMode);
            }
    
            if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
                    mIsFloating)) {
                /* All dialogs should have the window dimmed */
                if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
                    params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
                }
                if (!haveDimAmount()) {
                    params.dimAmount = a.getFloat(
                            android.R.styleable.Window_backgroundDimAmount, 0.5f);
                }
            }
    
            if (params.windowAnimations == 0) {
                params.windowAnimations = a.getResourceId(
                        R.styleable.Window_windowAnimationStyle, 0);
            }
    
            // The rest are only done if this window is not embedded; otherwise,
            // the values are inherited from our container.
            if (getContainer() == null) {
                if (mBackgroundDrawable == null) {
                    if (mBackgroundResource == 0) {
                        mBackgroundResource = a.getResourceId(
                                R.styleable.Window_windowBackground, 0);
                    }
                    if (mFrameResource == 0) {
                        mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
                    }
                    mBackgroundFallbackResource = a.getResourceId(
                            R.styleable.Window_windowBackgroundFallback, 0);
                    if (false) {
                        System.out.println("Background: "
                                + Integer.toHexString(mBackgroundResource) + " Frame: "
                                + Integer.toHexString(mFrameResource));
                    }
                }
                if (mLoadElevation) {
                    mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
                }
                mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
                mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
            }
    
            // Inflate the window decor.
    
            int layoutResource;
            int features = getLocalFeatures();
            // System.out.println("Features: 0x" + Integer.toHexString(features));
            if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                layoutResource = R.layout.screen_swipe_dismiss;
                setCloseOnSwipeEnabled(true);
            } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
                if (mIsFloating) {
                    TypedValue res = new TypedValue();
                    getContext().getTheme().resolveAttribute(
                            R.attr.dialogTitleIconsDecorLayout, res, true);
                    layoutResource = res.resourceId;
                } else {
                    layoutResource = R.layout.screen_title_icons;
                }
                // XXX Remove this once action bar supports these features.
                removeFeature(FEATURE_ACTION_BAR);
                // System.out.println("Title Icons!");
            } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                    && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
                // Special case for a window with only a progress bar (and title).
                // XXX Need to have a no-title version of embedded windows.
                layoutResource = R.layout.screen_progress;
                // System.out.println("Progress!");
            } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
                // Special case for a window with a custom title.
                // If the window is floating, we need a dialog layout
                if (mIsFloating) {
                    TypedValue res = new TypedValue();
                    getContext().getTheme().resolveAttribute(
                            R.attr.dialogCustomTitleDecorLayout, res, true);
                    layoutResource = res.resourceId;
                } else {
                    layoutResource = R.layout.screen_custom_title;
                }
                // XXX Remove this once action bar supports these features.
                removeFeature(FEATURE_ACTION_BAR);
            } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
                // If no other features and not embedded, only need a title.
                // If the window is floating, we need a dialog layout
                if (mIsFloating) {
                    TypedValue res = new TypedValue();
                    getContext().getTheme().resolveAttribute(
                            R.attr.dialogTitleDecorLayout, res, true);
                    layoutResource = res.resourceId;
                } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                    layoutResource = a.getResourceId(
                            R.styleable.Window_windowActionBarFullscreenDecorLayout,
                            R.layout.screen_action_bar);
                } else {
                    layoutResource = R.layout.screen_title;
                }
                // System.out.println("Title!");
            } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
                layoutResource = R.layout.screen_simple_overlay_action_mode;
            } else {
                // Embedded, so no decoration is needed.
                layoutResource = R.layout.screen_simple;
                // System.out.println("Simple!");
            }
    
            mDecor.startChanging();
            mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    
            ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
            if (contentParent == null) {
                throw new RuntimeException("Window couldn't find content container view");
            }
    
            if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
                ProgressBar progress = getCircularProgressBar(false);
                if (progress != null) {
                    progress.setIndeterminate(true);
                }
            }
    
            if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                registerSwipeCallbacks(contentParent);
            }
    
            // Remaining setup -- of background and title -- that only applies
            // to top-level windows.
            if (getContainer() == null) {
                final Drawable background;
                if (mBackgroundResource != 0) {
                    background = getContext().getDrawable(mBackgroundResource);
                } else {
                    background = mBackgroundDrawable;
                }
                mDecor.setWindowBackground(background);
    
                final Drawable frame;
                if (mFrameResource != 0) {
                    frame = getContext().getDrawable(mFrameResource);
                } else {
                    frame = null;
                }
                mDecor.setWindowFrame(frame);
    
                mDecor.setElevation(mElevation);
                mDecor.setClipToOutline(mClipToOutline);
    
                if (mTitle != null) {
                    setTitle(mTitle);
                }
    
                if (mTitleColor == 0) {
                    mTitleColor = mTextColor;
                }
                setTitleColor(mTitleColor);
            }
    
            mDecor.finishChanging();
    
            return contentParent;
        }
    

    activity中window的初始化

    window是在activity启动的时候被初始化的,widnow初始化的任务栈调用是:

    • ActivityThread.performLaunchActivity()
    • Activity.attach()

    Activity.attach()主要逻辑如下:

    1. 创建PhoneWindow
    2. 为PhoneWindow设置windowManager,通过context.getSystemService()获取到windowManager。
    3. 当activity的mParent存在时,为window的container设置成mParent.getWindow()。(mParent和TabActivity有关,有兴趣的可以自行看下TabActivity源码)
        final void attach(Context context, ActivityThread aThread,
                Instrumentation instr, IBinder token, int ident,
                Application application, Intent intent, ActivityInfo info,
                CharSequence title, Activity parent, String id,
                NonConfigurationInstances lastNonConfigurationInstances,
                Configuration config, String referrer, IVoiceInteractor voiceInteractor,
                Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
            attachBaseContext(context);
    
            mFragments.attachHost(null /*parent*/);
    
            mWindow = new PhoneWindow(this, window, activityConfigCallback);
            mWindow.setWindowControllerCallback(this);
            mWindow.setCallback(this);
            mWindow.setOnWindowDismissedCallback(this);
            mWindow.getLayoutInflater().setPrivateFactory(this);
            if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
                mWindow.setSoftInputMode(info.softInputMode);
            }
            if (info.uiOptions != 0) {
                mWindow.setUiOptions(info.uiOptions);
            }
            mUiThread = Thread.currentThread();
    
            mMainThread = aThread;
            mInstrumentation = instr;
            mToken = token;
            mAssistToken = assistToken;
            mIdent = ident;
            mApplication = application;
            mIntent = intent;
            mReferrer = referrer;
            mComponent = intent.getComponent();
            mActivityInfo = info;
            mTitle = title;
            mParent = parent;
            mEmbeddedID = id;
            mLastNonConfigurationInstances = lastNonConfigurationInstances;
            if (voiceInteractor != null) {
                if (lastNonConfigurationInstances != null) {
                    mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
                } else {
                    mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                            Looper.myLooper());
                }
            }
    
            mWindow.setWindowManager(
                    (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                    mToken, mComponent.flattenToString(),
                    (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
            if (mParent != null) {
                mWindow.setContainer(mParent.getWindow());
            }
            mWindowManager = mWindow.getWindowManager();
            mCurrentConfig = config;
    
            mWindow.setColorMode(info.colorMode);
    
            setAutofillOptions(application.getAutofillOptions());
            setContentCaptureOptions(application.getContentCaptureOptions());
        }
    
    更多相关内容
  • Android全面解析之Window机制

    千次阅读 多人点赞 2020-09-01 20:55:54
    带你认识最本质Android的window机制

    文章已授权『鸿洋』公众号发布

    前言

    你好!
    我是一只修仙的猿,欢迎阅读我的文章。

    Window,读者可能更多的认识是windows系统的窗口。在windows系统上,我们可以多个窗口同时运行,每个窗口代表着一个应用程序。但在安卓上貌似并没有这个东西,但读者可以马上想到,不是有小窗口模式吗,像米UI最新的系统,不就是可以随意创建一个小窗口,然后两个应用同时操作?是的,那是属于android中,window的一种表现方式。但是手机屏幕终究不能和电脑相比,因为屏幕太小了,小到只能操作一款应用,多个窗口就显得非常不习惯,所以Android上关于窗口方面的知识读者可能接触不多。那window的意思就只是小米系统中那种小窗口吗?

    当然不是。Android框架层意义上的window和我们认识的window其实是有点不一样的。我们日常最直观的,每个应用界面,都有一个应用级的window。再例如popupWindow、Toast、dialog、menu都是需要通过创建window来实现。所以其实window我们一直都见到,只是不知道那就是window。了解window的机制原理,可以更好地了解window,进而更好地了解android是怎么管理屏幕上的view。这样,当我们需要使用dialog或者popupWindow的时候,可以懂得他背后究竟做了什么,才能够更好的运用dialog、popupWindow等。

    当然,到此如果你有很多的疑问,甚至质疑我的理论,那就希望你可以阅读完这一篇文章。我会从window是什么,有什么用,内部机制是什么,各种组件是如何创建window等等方面来阐述Android中的window。文章内容非常多,读者可自选章节阅读。

    什么是window机制

    先假设如果没有window,会发生什么:

    我们看到的界面ui是view,如我们的应用布局,更简单是一个button。假如屏幕上现在有一个Button,如图1,现在往屏幕中间添加一个TextView,那么最终的结果是图2,还是图3:

    示例图

    在上图的图2中,如果我要实现点击textView执行他的监听事件逻辑,点击不是textView的区域让textView消失,需要怎么实现呢?读者可能会说,我们可以在Activity中添加这部分的逻辑,那如果我们需要让一个悬浮窗在所有界面显示呢,如上文我讲到的小米悬浮窗,两个不用应用的view,怎么确定他们的显示次序?又例如我们需要弹出一个dialog来提示用户,怎么样可以让dialog永远处于最顶层呢,包括显示dialog期间应用弹出的如popupWindow必须显示在dialog的低下,但toast又必须显示在dialog上面。

    很明显,我们的屏幕可以允许多个应用同时显示非常多的view,他们的显示次序或者说显示高度是不一样的,如果没有一个统一的管理者,那么每一家应用都想要显示在最顶层,那么屏幕上的view会非常乱。

    同时,当我们点击屏幕时,这个触摸事件应该传给哪个view?很明显我们都知道应该传给最上层的view,但是接受事件的是屏幕,是另一个系统服务,他怎么知道触摸位置的最上层是哪个view呢?即时知道,他又怎么把这个事件准确地传给他呢?

    为了解决等等这些问题,急需有一个管理者来统一管理屏幕上的显示的view,才能让程序有条不紊地走下去。而这,就是Android中的window机制。

    window机制就是为了管理屏幕上的view的显示以及触摸事件的传递问题。

    什么是window?

    那什么是window,在Android的window机制中,每个view树都可以看成一个window。为什么不是每个view呢?因为view树中每个view的显示次序是固定的,例如我们的Activity布局,每一个控件的显示都是已经安排好的,对于window机制来说,属于“不可再分割的view”。

    什么是view树?例如你在布局中给Activity设置了一个布局xml,那么最顶层的布局如LinearLayout就是view树的根,他包含的所有view就都是该view树的节点,所以这个view树就对应一个window。

    举几个具体的例子:

    • 我们在添加dialog的时候,需要给他设置view,那么这个view他是不属于antivity的布局内的,是通过WindowManager添加到屏幕上的,不属于activity的view树内,所以这个dialog是一个独立的view树,所以他是一个window。
    • popupWindow他也对应一个window,因为它也是通过windowManager添加上去的,不属于Activity的view树。
    • 当我们使用使用windowManager在屏幕上添加的任何view都不属于Activity的布局view树,即使是只添加一个button。

    view树(后面使用view代称,后面我说的view都是指view树)是window机制的操作单位,每一个view对应一个window,view是window的存在形式,window是view的载体,我们平时看到的应用界面、dialog、popupWindow以及上面描述的悬浮窗,都是window的表现形式。注意,我们看到的不是window,而是view。**window是view的管理者,同时也是view的载体。他是一个抽象的概念,本身并不存在,view是window的表现形式。**这里的不存在,指的是我们在屏幕上是看不到window的,他不像windows系统,如下图:

    windows系统窗口

    有一个很明显的标志:看,我就是window。但在Android中我们是无法感知的,我们只能看到view无法看到window,window是控制view需要怎么显示的管理者。每个成功的男人背后都有一个女人,每个view背后都有一个window。

    window本身并不存在,他只是一个概念。举个栗子:如班集体,就是一个概念,他的存在形式是这整个班的学生,当学生不存在那么这个班集体也就不存在。但是他的好处是得到了一个新的概念,我们可以以班为单位来安排活动。因他不存在,所以也很难从源码中找到他的痕迹,window机制的操作单位都是view,如果要说他在源码中的存在形式,笔者目前的认知就是在WindowManagerService中每一个view对应一个windowStatus。WindowManagerService是什么如果没了解过可以先忽略后面会讲到。读者可以慢慢思考一下这个抽象的概念,后面会慢慢深入讲源码帮助理解。

    • view是window的存在形式,window是view的载体
    • window是view的管理者,同时也是view的载体。他是一个抽象的概念,本身并不存在,view是window的表现形式

    思考:Android中不是有一个抽象类叫做window还有一个PhoneWindow实现类吗,他们不就是window的存在形式,为什么说window是抽象不存在的?读者可自行思考,后面会讲到。

    Window的相关属性

    在了解window的操作流程之前,先补充一下window的相关属性。

    window的type属性

    前面我们讲到window机制解决的一个问题就是view的显示次序问题,这个属性就决定了window的显示次序。window是有分类的,不同类别的显示高度范围不同,例如我把1-1000m高度称为低空,1001-2000m高度称为中空,2000以上称为高空。window也是一样按照高度范围进行分类,他也有一个变量Z-Order,决定了window的高度。window一共可分为三类:

    • 应用程序窗口:应用程序窗口一般位于最底层,Z-Order在1-99
    • 子窗口:子窗口一般是显示在应用窗口之上,Z-Order在1000-1999
    • 系统级窗口:系统级窗口一般位于最顶层,不会被其他的window遮住,如Toast,Z-Order在2000-2999。如果要弹出自定义系统级窗口需要动态申请权限

    Z-Order越大,window越靠近用户,也就显示越高,高度高的window会覆盖高度低的window。

    window的type属性就是Z-Order的值,我们可以给window的type属性赋值来决定window的高度。系统为我们三类window都预设了静态常量,如下(以下常用参数介绍转自参考文献第一篇文章):

    • 应用级window

      // 应用程序 Window 的开始值
      public static final int FIRST_APPLICATION_WINDOW = 1;
      
      // 应用程序 Window 的基础值
      public static final int TYPE_BASE_APPLICATION = 1;
      
      // 普通的应用程序
      public static final int TYPE_APPLICATION = 2;
      
      // 特殊的应用程序窗口,当程序可以显示 Window 之前使用这个 Window 来显示一些东西
      public static final int TYPE_APPLICATION_STARTING = 3;
      
      // TYPE_APPLICATION 的变体,在应用程序显示之前,WindowManager 会等待这个 Window 绘制完毕
      public static final int TYPE_DRAWN_APPLICATION = 4;
      
      // 应用程序 Window 的结束值
      public static final int LAST_APPLICATION_WINDOW = 99;
      
    • 子window

      // 子 Window 类型的开始值
      public static final int FIRST_SUB_WINDOW = 1000;
      
      // 应用程序 Window 顶部的面板。这些 Window 出现在其附加 Window 的顶部。
      public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
      
      // 用于显示媒体(如视频)的 Window。这些 Window 出现在其附加 Window 的后面。
      public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
      
      // 应用程序 Window 顶部的子面板。这些 Window 出现在其附加 Window 和任何Window的顶部
      public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
      
      // 当前Window的布局和顶级Window布局相同时,不能作为子代的容器
      public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
      
      // 用显示媒体 Window 覆盖顶部的 Window, 这是系统隐藏的 API
      public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW + 4;
      
      // 子面板在应用程序Window的顶部,这些Window显示在其附加Window的顶部, 这是系统隐藏的 API
      public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
      
      // 子 Window 类型的结束值
      public static final int LAST_SUB_WINDOW = 1999;
      
    • 系统级window

      // 系统Window类型的开始值
      public static final int FIRST_SYSTEM_WINDOW = 2000;
      
      // 系统状态栏,只能有一个状态栏,它被放置在屏幕的顶部,所有其他窗口都向下移动
      public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
      
      // 系统搜索窗口,只能有一个搜索栏,它被放置在屏幕的顶部
      public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
      
      // 已经从系统中被移除,可以使用 TYPE_KEYGUARD_DIALOG 代替
      public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
      
      // 系统对话框窗口
      public static final int TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8;
      
      // 锁屏时显示的对话框
      public static final int TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9;
      
      // 输入法窗口,位于普通 UI 之上,应用程序可重新布局以免被此窗口覆盖
      public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
      
      // 输入法对话框,显示于当前输入法窗口之上
      public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
      
      // 墙纸
      public static final int TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13;
      
      // 状态栏的滑动面板
      public static final int TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14;
      
      // 应用程序叠加窗口显示在所有窗口之上
      public static final int TYPE_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 38;
      
      // 系统Window类型的结束值
      public static final int LAST_SYSTEM_WINDOW = 2999;
      

    Window的flags参数

    flag标志控制window的东西比较多,很多资料的描述是“控制window的显示”,但我觉得不够准确。flag控制的范围包括了:各种情景下的显示逻辑(锁屏,游戏等)还有触控事件的处理逻辑。控制显示确实是他的很大部分功能,但是并不是全部。下面看一下一些常用的flag,就知道flag的功能了(以下常用参数介绍转自参考文献第一篇文章):

    // 当 Window 可见时允许锁屏
    public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001;
    
    // Window 后面的内容都变暗
    public static final int FLAG_DIM_BEHIND = 0x00000002;
    
    // Window 不能获得输入焦点,即不接受任何按键或按钮事件,例如该 Window 上 有 EditView,点击 EditView 是 不会弹出软键盘的
    // Window 范围外的事件依旧为原窗口处理;例如点击该窗口外的view,依然会有响应。另外只要设置了此Flag,都将会启用FLAG_NOT_TOUCH_MODAL
    public static final int FLAG_NOT_FOCUSABLE = 0x00000008;
    
    // 设置了该 Flag,将 Window 之外的按键事件发送给后面的 Window 处理, 而自己只会处理 Window 区域内的触摸事件
    // Window 之外的 view 也是可以响应 touch 事件。
    public static final int FLAG_NOT_TOUCH_MODAL  = 0x00000020;
    
    // 设置了该Flag,表示该 Window 将不会接受任何 touch 事件,例如点击该 Window 不会有响应,只会传给下面有聚焦的窗口。
    public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
    
    // 只要 Window 可见时屏幕就会一直亮着
    public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
    
    // 允许 Window 占满整个屏幕
    public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
    
    // 允许 Window 超过屏幕之外
    public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
    
    // 全屏显示,隐藏所有的 Window 装饰,比如在游戏、播放器中的全屏显示
    public static final int FLAG_FULLSCREEN      = 0x00000400;
    
    // 表示比FLAG_FULLSCREEN低一级,会显示状态栏
    public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
    
    // 当用户的脸贴近屏幕时(比如打电话),不会去响应此事件
    public static final int FLAG_IGNORE_CHEEK_PRESSES    = 0x00008000;
    
    // 则当按键动作发生在 Window 之外时,将接收到一个MotionEvent.ACTION_OUTSIDE事件。
    public static final int FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000;
    
    @Deprecated
    // 窗口可以在锁屏的 Window 之上显示, 使用 Activity#setShowWhenLocked(boolean) 方法代替
    public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
    
    // 表示负责绘制系统栏背景。如果设置,系统栏将以透明背景绘制,
    // 此 Window 中的相应区域将填充 Window#getStatusBarColor()和 Window#getNavigationBarColor()中指定的颜色。
    public static final int FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS = 0x80000000;
    
    // 表示要求系统壁纸显示在该 Window 后面,Window 表面必须是半透明的,才能真正看到它背后的壁纸
    public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
    

    window的solfInputMode属性

    这一部分就是当软件盘弹起来的时候,window的处理逻辑,这在日常中也经常遇到,如:我们在微信聊天的时候,点击输入框,当软键盘弹起来的时候输入框也会被顶上去。如果你不想被顶上去,也可以设置为被软键盘覆盖。下面介绍一下常见的属性(以下常见属性介绍选自参考文献第一篇文章):

    // 没有指定状态,系统会选择一个合适的状态或者依赖于主题的配置
    public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
    
    // 当用户进入该窗口时,隐藏软键盘
    public static final int SOFT_INPUT_STATE_HIDDEN = 2;
    
    // 当窗口获取焦点时,隐藏软键盘
    public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
    
    // 当用户进入窗口时,显示软键盘
    public static final int SOFT_INPUT_STATE_VISIBLE = 4;
    
    // 当窗口获取焦点时,显示软键盘
    public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
    
    // window会调整大小以适应软键盘窗口
    public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
    
    // 没有指定状态,系统会选择一个合适的状态或依赖于主题的设置
    public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
    
    // 当软键盘弹出时,窗口会调整大小,例如点击一个EditView,整个layout都将平移可见且处于软件盘的上方
    // 同样的该模式不能与SOFT_INPUT_ADJUST_PAN结合使用;
    // 如果窗口的布局参数标志包含FLAG_FULLSCREEN,则将忽略这个值,窗口不会调整大小,但会保持全屏。
    public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
    
    // 当软键盘弹出时,窗口不需要调整大小, 要确保输入焦点是可见的,
    // 例如有两个EditView的输入框,一个为Ev1,一个为Ev2,当你点击Ev1想要输入数据时,当前的Ev1的输入框会移到软键盘上方
    // 该模式不能与SOFT_INPUT_ADJUST_RESIZE结合使用
    public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
    
    // 将不会调整大小,直接覆盖在window上
    public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
    

    window的其他属性

    上面的三个属性是window比较重要也是比较复杂 的三个,除此之外还有几个日常经常使用的属性:

    • x与y属性:指定window的位置
    • alpha:window的透明度
    • gravity:window在屏幕中的位置,使用的是Gravity类的常量
    • format:window的像素点格式,值定义在PixelFormat中

    如何给window属性赋值

    window属性的常量值大部分存储在WindowManager.LayoutParams类中,我们可以通过这个类来获得这些常量。当然还有Gravity类和PixelFormat类等。

    一般情况下我们会通过以下方式来往屏幕中添加一个window:

    // 在Activity中调用
    WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
    windParams.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
    TextView view = new TextView(this);
    getWindowManager.addview(view,windowParams);
    

    我们可以直接给WindowManager.LayoutParams对象设置属性。

    第二种赋值方法是直接给window赋值,如

    getWindow().flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
    

    除此之外,window的solfInputMode属性比较特殊,他可以直接在AndroidManifest中指定,如下:

     <activity android:windowSoftInputMode="adjustNothing" />
    

    最后总结一下:

    • window的重要属性有type、flags、solfInputMode、gravity等
    • 我们可以通过不同的方式给window属性赋值
    • 没必要去全部记下来,等遇到需求再去寻找对应的常量即可

    Window的添加过程

    通过理解源码之后,可以对之前的理论理解更加的透彻。window的添加过程,指的是我们通过WindowManagerImpl的addView方法来添加window的过程。

    想要添加一个window,我们知道首先得有view和WindowManager.LayoutParams对象,才能去创建一个window,这是我们常见的代码:

    Button button = new Button(this);
    WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
    // 这里对windowParam进行初始化
    windowParam.addFlags...
    // 获得应用PhoneWindow的WindowManager对象进行添加window
    getWindowManager.addView(button,windowParams);
    

    然后接下来我们进入addView方法中看看。我们知道这个windowManager的实现类是WindowManagerImpl,上面讲过,进入他的addView方法看一看:

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

    可以发现他把逻辑直接交给mGlobal去处理了。这个mGlobal是WindowManagerGlobal,是一个全局单例,是WindowManager接口的具体逻辑实现。这里运用的是桥接模式。那我们进WindowManagerGlobal的方法看一下:

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // 首先判断参数是否合法
        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 {
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
        
    	synchronized (mLock) {
            ...
            // 这里新建了一个viewRootImpl,并设置参数
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
    
            // 添加到windowManagerGlobal的三个重要list中,后面会讲到
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
    
            // 最后通过viewRootImpl来添加window
            try {
                root.setView(view, wparams, panelParentView);
            } 
            ...
        }  
    }
    

    代码有点长,一步步看:

    • 首先对参数的合法性进行检查
    • 然后判断该窗口是不是子窗口,如果是的话需要对窗口进行调整,这个好理解,子窗口要跟随父窗口的特性。
    • 接着新建viewRootImpl对象,并把view、viewRootImpl、params三个对象添加到三个list中进行保存
    • 最后通过viewRootImpl来进行添加

    补充一点关于WindowManagerGlobal中的三个list,他们分别是:

    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams =
         new ArrayList<WindowManager.LayoutParams>();
    

    每一个window所对应的这三个对象都会保存在这里,之后对window的一些操作就可以直接来这里取对象了。当window被删除的时候,这些对象也会被从list中移除。

    可以看到添加的window的逻辑就交给ViewRootImpl了。viewRootImpl是window和view之间的桥梁,viewRootImpl可以处理两边的对象,然后联结起来。下面看一下viewRootImpl怎么处理:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            ...
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                // 这里调用了windowSession的方法,调用wms的方法,把添加window的逻辑交给wms
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                        mTempInsets);
                setFrame(mTmpFrame);
            } 
            ...
        }
    }
    

    viewRootImpl的逻辑很多,重要的就是调用了mWindowSession的方法调用了WMS的方法。这个mWindowSession很重要重点讲一下。

    mWindowSession是一个IWindowSession对象,看到这个命名很快地可以像到这里用了AIDL跨进程通信。IWindowSession是一个IBinder接口,他的具体实现类在WindowManagerService,本地的mWindowSession只是一个Binder对象,通过这个mWindowSession就可以直接调用WMS的方法进行跨进程通信。

    那这个mWindowSession是从哪里来的呢?我们到viewRootImpl的构造器方法中看一下:

    public ViewRootImpl(Context context, Display display) {
    	...
     	mWindowSession = WindowManagerGlobal.getWindowSession();
     	...
    }
    

    可以看到这个session对象是来自WindowManagerGlobal。再深入看一下:

    public static IWindowSession getWindowSession() {
     synchronized (WindowManagerGlobal.class) {
         if (sWindowSession == null) {
             try {
                 ...
                 sWindowSession = windowManager.openSession(
                         new IWindowSessionCallback.Stub() {
                             ...
                         });
             } 
             ...
         }
         return sWindowSession;
     }
    }
    

    这熟悉的代码格式,可以看出来这个session是一个单例,也就是整个应用的所有viewRootImpl的windowSession都是同一个,也就是一个应用只有一个windowSession。对于wms而言,他是服务于多个应用的,如果说每个viewRootImpl整一个session,那他的任务就太重了。WMS的对象单位是应用,他在内部给每个应用session分配了一些数据结构如list,用于保存每个应用的window以及对应的viewRootImpl。当需要操作view的时候,通过session直接找到viewRootImpl就可以操作了。

    后面的逻辑就交给WMS去处理了,WMS就会创建window,然后结合参数计算window的高度等等,最后使用viewRootImpl进行绘制。这后面的代码逻辑就不讲了,这是深入到WMS的内容,再讲进去就太复杂了(笔者也还没读懂WMS)。读源码的目的是了解整个系统的本质与工作流程,对系统整体的感知,而不用太深入代码细节,Android系统那么多的代码,如果深入进去会出不来的,所以点到为止就好了。

    我们知道windowManager接口是继承viewManager接口的,viewManager还有另外两个接口:removeView、updateView。这里就不讲了,有兴趣的读者可以自己去阅读源码。讲添加流程主要是为了理解window系统的运作,对内部的流程感知,以便于更好的理解window。

    最后做个总结:

    window的添加过程是通过PhoneWindow对应的WindowManagerImpl来添加window,内部会调用WindowManagerGlobal来实现。WindowManagerGlobal会使用viewRootImpl来进行跨进程通信让WMS执行创建window的业务。

    每个应用都有一个windowSession,用于负责和WMS的通信,如ApplicationThread与AMS的通信。

    window机制的关键类

    前面的源码流程中涉及到很多的类,这里把相关的类统一分析一下。先看一张图:

    window内部关键类

    这基本上是我们这篇文章涉及到的所有关键类。且听我慢慢讲。(图中绿色的window并不是一个类,而是真正意义上的window)

    window相关

    window的实现类只有一个:PhoneWindow,他继承自Window抽象类。后面我会重点分析他。

    WindowManager相关

    顾名思义,windowManager就是window管理类。这一部分的关键类有windowManager,viewManager,windowManagerImpl,windowManagerGlobal。windowManager是一个接口,继承自viewManager。viewManager中包含了我们非常熟悉的三个接口:addView,removeView,updateView
    windowManagerImpl和PhoneWindow是成对出现的,前者负责管理后者。WindowManagerImpl是windowManager的实现类,但是他本身并没有真正实现逻辑,而是交给了WindowManagerGlobal。WindowManagerGlobal是全局单例,windowManagerImpl内部使用桥接模式,他是windowManager接口逻辑的真正实现

    view相关

    这里有个很关键的类:ViewRootImpl。每个view树都会有一个。当我使用windowManager的addView方法时,就会创建一个ViewRootImpl。ViewRootImpl的作用很关键:

    • 负责连接view和window的桥梁事务
    • 负责和WindowManagerService的联系
    • 负责管理和绘制view树
    • 事件的中转站

    每个window都会有一个ViewRootImpl,viewRootImpl是负责绘制这个view树和window与view的桥梁,每个window都会有一个ViewRootImpl。

    WindowManagerService

    这个是window的真正管理者,类似于AMS(ActivityManagerService)管理四大组件。所有的window创建最终都要经过windowManagerService。整个Android的window机制中,WMS绝对是核心,他决定了屏幕所有的window该如何显示如何分发点击事件等等。

    window与PhoneWindow的关系

    解释一下标题,window是指window机制中window这个概念,而PhoneWindow是指PhoneWindow这个类。后面我在讲的时候,如果是指类,我会在后面加个‘类’字。如window是指window概念,window类是指window这个抽象类。读者不要混淆。

    还记得我在讲window的概念的时候留了一个思考吗?

    思考:Android中不是有一个抽象类叫做window还有一个PhoneWindow实现类吗,他们不就是window的存在形式,为什么说window是抽象不存在的

    这里我再抛出几个问题:

    • 有一些资料认为PhoneWindow就是window,是view容器,负责管理容器内的view,windowManagerImpl可以往里面添加view,如上面我们讲过的addView方法。但是,同时它又说每个window对应一个viewRootImpl,但却没解释为什么每次addView都会新建一个viewRootImpl,前后发送矛盾。
    • 有一些资料也是认为PhoneWindow是window,但是他说addView方法不是添加view而是添加window,同时拿这个方法的名字作为论据证明view就是window,但是他没解释为什么在使用addView方法创建window的过程却没有创建PhoneWindow对象。

    我们一步步来看。我们首先来看一下源码中对于window抽象类的注释:

     Abstract base class for a top-level window look and behavior policy.  An
     instance of this class should be used as the top-level view added to the
     window manager. It provides standard UI policies such as a background, title
     area, default key processing, etc.
         
    顶层窗口外观和行为策略的抽象基类。此类的实例应用作添加到窗口管理器的顶层视图。
    它提供标准的UI策略,如背景、标题区域、默认键处理等。
    

    大概意思就是:这个类是顶级窗口的抽象基类,顶级窗口必须继承他,他负责窗口的外观如背景、标题、默认按键处理等。这个类的实例被添加到windowManager中,让windowManager对他进行管理。PhoneWindow是一个top-level window(顶级窗口),他被添加到顶级窗口管理器的顶层视图,其他的window,都需要添加到这个顶层视图中,所以更准确的来说,PhoneWindow并不是view容器,而是window容器。

    那PhoneWindow的存在意义是什么?

    第一、提供DecorView模板。如下图:

    我们的Activity是通过setContentView把布局设置到DecorView中,那么DecorView本身的布局,就成为了Activity界面的背景。同时DecorView是分为标题栏和内容两部分,所以也可以可界面设置标题栏。同时,由于我们的界面是添加在的DecorView中,属于DecorView的一部分。那么对于DecorView的window属性设置也会对我们的布局界面生效。还记得谷歌的官方给window类注释的最后一句话吗:它提供标准的UI策略,如背景、标题区域、默认键处理等。这些都可以通过DecorView实现,这是PhoneWindow的第一个作用。

    第二、抽离Activity中关于window的逻辑。Activity的职责非常多,如果所有的事情都自己做,那么会造成本身代码极其臃肿。阅读过Activity启动的读者可能知道,AMS也通过ActivityStarter这个类来抽离启动Activity启动的逻辑。这样关于window相关的事情,就交给PhoneWindow去处理了。(事实上,Activity调用的是WindowManagerImpl,但因PhoneWindow和WindowManagerImpl两者是成对存在,他们共同处理window相关的事务,所以这里就简单写成交给PhoneWindow处理。)当Activity需要添加界面时,只需要一句setContentView,调用了PhoneWindow的setContentView方法,就把布局设置到屏幕上了。具体怎么完成,Activity不必管。

    第三、限制组件添加window的权限。PhoneWindow内部有一个token属性,用于验证一个PhoneWindow是否允许添加window。在Activity创建PhoneWindow的时候,就会把从AMS传过来的token赋值给他,从而他也就有了添加token的权限。而其他的PhoneWindow则没有这个权限,因而也无法添加window。这部分内容我在另一篇文章有详细讲解,感兴趣的读者可以前往了解一下传送门

    当然,PhoneWindow的作用肯定远不止如此,这里列出很重要的三条,也是笔者目前学习到的三个最重要的作用。官方对于一个类的设计的考虑肯定是非常多,不是笔者简单的分析所能阐述,而只是给出一个新的思考方向,带大家认识真正的window。

    总结一下:

    • PhoneWindow本身不是真正意义上的window,他更多可以认为是辅助Activity操作window的工具类。
    • windowManagerImpl并不是管理window的类,而是管理PhoneWindow的类。真正管理window的是WMS。
    • PhoneWindow可以配合DecorView可以给其中的window按照一定的逻辑提供标准的UI策略
    • PhoneWindow限制了不同的组件添加window的权限。

    常见组件的window创建流程

    上面讲的是通过windowManagerImpl创建window的过程,我们通过前面的讲解了解到,WindowManagerImpl是管理PhoneWindow的,他们是同时出现的。因而有两种创建window的方式:

    • 已经存在PhoneWindow,直接通过WindowManagerImpl创建window
    • PhoneWindow尚未存在,先创建PhoneWindow,再利用windowManagerImpl来创建window

    当我们在Activity中使用getWindowManager方法获取到的就是应用的PhoneWindow对应的WindowManagerImpl。下面来讲一下不同的组件是如何创建window的,

    Activity

    如果有阅读过Activity的启动流程的读者,会知道Activity的启动最后来到了ActivityThread的handleLaunchActivity这个方法。

    关于Activity的启动流程,我写过一篇文章,有兴趣的读者可以点击下方链接前往:

    Activity启动流程详解(基于api28)

    至于为什么是这个方法这里就不讲了,有兴趣的读者可以去看上面的文章。我们直接来看这个方法的代码:

    public void handleLaunchActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ...;
        // 这里对WindowManagerGlobal进行初始化
        WindowManagerGlobal.initialize();
    
       	// 启动Activity并回调activity的onCreate方法
        final Activity a = performLaunchActivity(r, customIntent);
        ...
    }
    
    
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        try {
            // 这里创建Application
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    		...
            if (activity != null) {
                ...
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.setOuterContext(activity);
                // 这里将window作为参数传到activity的attach方法中
                // 一般情况下这里window==null
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);  
                ...
                // 最后这里回调Activity的onCreate方法
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
            }
        
        ...
    }
    

    handleLaunchActivity的代码中首先对WindowManagerGlobal进行初始化,然后调用了performLaunchActivity方法。代码很多,这里只截取了重要部分。首先会创建Application对象,然后再调用Activity的attach方法,把window作为参数传进去,最后回调activity的onCreate方法。所以这里最有可能创建window的方法就是Activity的attach方法了。我们进去看一下:

    final void attach(...,Context context,Window window, ...) {
        ...;
     	// 这里新建PhoneWindow对象,并对window进行初始化
    	mWindow = new PhoneWindow(this, window, activityConfigCallback);
        // Activity实现window的callBack接口,把自己设置给window
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);    
        ...
        // 这里初始化window的WindowManager对象
    	mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);        
    }
    

    同样只截取了重要代码,attach方法参数非常多,我只留下了window相关的参数。在这方法里首先利用传进来的window创建了PhoneWindow。Activity实现window的callBack接口,可以把自己设置给window当观察者。当window发生变化的时候可以通知activity。然后再创建WindowManager和PhoneWindow绑定在一起,这样我们就可以通过windowManager操作PhoneWindow了。(这里不是setWindowManager吗,windowManager是什么时候创建的?)我们进去setWindowManager方法看一下:

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        // 这里创建了windowManager
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    

    这个方法里首先会获取到应用服务的WindowManager(实现类也是WindowManagerImpl),然后通过这个应用服务的WindowManager创建了新的windowManager。

    从这里可以看到是利用系统服务的windowManager来创建新的windowManagerImpl,因而这个应用所有的WindowManagerImpl都是同个内核windowManager,而创建出来的仅仅是包了个壳。

    这样PhoneWindow和WindowManagerImpl就绑定在一起了。Activity可以通过WindowManagerImpl来操作PhoneWindow。


    到这里Activity的PhoneWindow和WindowManagerImpl对象就创建完成了,接下来是如何把Activity的布局文件设置给PhoneWindow。在上面我讲到调用Activity的attach方法之后,会回调Activity的onCreate方法,在onCreate方法我们会调用setContentView来设置布局,如下:

    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
        initWindowDecorActionBar();
    }
    

    这里的getWindow就是获取到我们上面创建的PhoneWindow对象。我们继续看下去:

    // 注意他有多个重载的方法,要选择参数对应的方法
    public void setContentView(int layoutResID) {
        // 创建DecorView
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
    
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 这里根据布局id加载布局
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            // 回调activity的方法
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
    

    同样我们只看重点代码:

    • 首先看decorView创建了没有,没有的话创建DecorView
    • 把布局加载到DecorView中
    • 回调Activity的callBack方法

    这里补充一下什么是DecorView。DecorView是在PhoneWindow中预设好的一个布局,这个布局长这样:

    decorView

    他是一个垂直排列的布局,上面是ActionBar,下面是ContentView,他是一个FrameLayout。我们的Activity布局就加载到ContentView里进行显示。所以Decorview是Activity布局最顶层的viewGroup。

    然后我们看一下怎么初始化DercorView的:

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            // 这里创建了DecorView
            mDecor = generateDecor(-1);
            ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            // 对DecorView进行初始化,得到ContentView
            mContentParent = generateLayout(mDecor);
            ...
        }
    }
    

    installDecor方法中主要是新建一个DecorView对象,然后加载预设好的布局对DecorView进行初始化,(预设好的布局就是上面讲述的布局)并获取到这个预设布局的ContentView。好了然后我们再回到window的setContentView方法中,初始化了DecorView之后,把Activity布局加载到DecorView的ContentView中如下代码:

    // 注意他有多个重载的方法,要选择参数对应的方法
    public void setContentView(int layoutResID) {
        ...
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 这里根据布局id加载布局
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...
       	mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            // 回调activity的方法
            cb.onContentChanged();
        }
    }
    

    所以可以看到Activitiy的布局确实是添加到DecorView的ContentView中,这也是为什么onCreate中使用的是setContentView而不是setView。最后会回调Activity的方法告诉Activity,DecorView已经创建并初始化完成了。


    到这里DecorView创建完成了,但还缺少了最重要的一步:把DecorView作为window添加到屏幕上。从前面的介绍我们知道添加window需要用到WindowManagerImpl的addView方法。这一步是在ActivityThread的handleResumeActivity方法中被执行:

    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        // 调用Activity的onResume方法
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        ...
        // 让decorView显示到屏幕上
    	if (r.activity.mVisibleFromClient) {
            r.activity.makeVisible();
      	}
    

    这一步方法有两个重点:回调onResume方法,把decorView添加到屏幕上。我们看一下makeVisible方法做了什么:

    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }
    

    是不是非常熟悉?直接调用WindowManagerImpl的addView方法来吧decorView添加到屏幕上,至此,我们的Activity界面就会显示在屏幕上了。


    好了,这部分很长,最后来总结一下:

    • 从Activity的启动流程可以得到Activity创建Window的过程
    • 创建PhoneWindow -> 创建WindowManager -> 创建decorView -> 利用windowManager把DecorView显示到屏幕上
    • 回调onResume方法的时候,DecorView还没有被添加到屏幕,所以当onResume被回调,指的是屏幕即将到显示,而不是已经显示

    PopupWindow

    popupWindow日常使用的也比较多,最常见的需求是弹一个菜单出来等。popupWindow也是利用windowManager来往屏幕上添加window,但,popupWindow是依附于activity而存在的,当Activity未运行时,是无法弹出popupWindow的,通过源码可以知道,当调用onResume方法的时候,其实后续还有很多事情在做,这个时候Activity也是尚未完全启动,所以popupWindow不能在onCreate、onStart、onResume方法中弹出。

    弹出popupWindow的过程分为两个:创建view;通过windowManager添加window。首先看到PopupWindow的构造方法:

    public PopupWindow(View contentView, int width, int height, boolean focusable) {
        if (contentView != null) {
            mContext = contentView.getContext();
            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        }
    
        setContentView(contentView);
        setWidth(width);
        setHeight(height);
        setFocusable(focusable);
    }
    

    他有多个重载方法,但最终都会调用到这个有四个参数的方法。主要是前面的得到context和根据context获得WindowManager。


    然后我们看到他的显示方法。显示方法有两个:showAtLocationshowAsDropDown。主要是处理显示的位置不同,其他都是相似的。我们看到第一个方法:

    public void showAtLocation(View parent, int gravity, int x, int y) {
        mParentRootView = new WeakReference<>(parent.getRootView());
        showAtLocation(parent.getWindowToken(), gravity, x, y);
    }
    

    逻辑很简单,父view的根布局存储了起来,然后调用另外的重载方法:

    public void showAtLocation(IBinder token, int gravity, int x, int y) {
        // 如果contentView是空直接返回
        if (isShowing() || mContentView == null) {
            return;
        }
    
        TransitionManager.endTransitions(mDecorView);
        detachFromAnchor();
        mIsShowing = true;
        mIsDropdown = false;
        mGravity = gravity;
    	// 得到WindowManager.LayoutParams对象
        final WindowManager.LayoutParams p = createPopupLayoutParams(token);
        // 做一些准备工作
        preparePopup(p);
    
        p.x = x;
        p.y = y;
    	// 执行popupWindow显示工作
        invokePopup(p);
    }
    

    这个方法的逻辑主要有:

    • 判断contentView是否为空或者是否进行显示
    • 做一些准备工作
    • 进行popupWindow显示工作

    这里我们看一下他的准备工作做了什么:

    private void preparePopup(WindowManager.LayoutParams p) {
        ...
            
        if (mBackground != null) {
            mBackgroundView = createBackgroundView(mContentView);
            mBackgroundView.setBackground(mBackground);
        } else {
            mBackgroundView = mContentView;
        }
    	// 创建了DecorView
        // 注意,这里的DecorView并不是我们之前讲的DecorView,而是他的内部类:PopupDecorView
        mDecorView = createDecorView(mBackgroundView);
        mDecorView.setIsRootNamespace(true);
    
        ...
    }
    

    接下来再看他的显示工作:

    private void invokePopup(WindowManager.LayoutParams p) {
        ...
       	// 调用windowManager添加window
        mWindowManager.addView(decorView, p);
    
        ...
    }
    

    到这里popupWindow就会被添加到屏幕上了。


    最后总结一下:

    • 根据参数构建popupDecorView
    • 把popupDecorView添加到屏幕上

    Dialog

    dialog的创建过程Activity比较像:创建PhoneWindow,初始化DecorView,添加DecorView。我这里就简单讲解一下。首先看到他的构造方法:

    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        ...
        // 获取windowManager
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    	// 构造PhoneWindow
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        // 初始化PhoneWindow
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mListenersHandler = new ListenersHandler(this);
    }
    

    这里和前面的Activity创建过程非常像,但是有个重点需要注意mWindowManager其实是Activity的WindowManager,这里的context一般是activity(实际上也只能是activity,非activity会抛出异常,相关内容读者有兴趣可以阅读这篇文章window的token验证),我们看到activity的getSystemService方法:

    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }
    	// 获取activity的windowManager
        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }
    

    可以看到这里的windowManager确实是Activity的WindowManager。接下来看到他的show方法:

    public void show() {
       ...
        // 回调onStart方法,获取前面初始化好的decorview
        onStart();
        mDecor = mWindow.getDecorView();
        ...
        WindowManager.LayoutParams l = mWindow.getAttributes();
        ...
        // 利用windowManager来添加window    
        mWindowManager.addView(mDecor, l);
        ...
        mShowing = true;
        sendShowMessage();
    }
    

    注意这里的mWindowManager是Activity的WindowManager,所以实际上,这里是添加到了Activity的PhoneWindow中。接下来的和前面的添加流程一样,这里我也不多讲解了。


    总结一下:

    • dialog和popupWindow不同,dialog创建了新的PhoneWindow,使用了PhoneWindow的DecorView模板。而popupWindow没有
    • dialog的显示层级数更高,会直接显示在Activity上面,在dialog后添加的popUpWindow也会显示在dialog下
    • dialog的创建流程和activity非常像

    从Android架构角度看Window

    前面我们介绍过关于PhoneWindow和window之间的关系,了解到PhoneWindow其实不是Window,只是一个window容器。不知读者有没想过一个问题,为什么谷歌要建一个不是window但却名字是window的类?是故意要迷惑我们吗?要了解这个问题,我们先来回顾一下整个android的window机制结构。

    首先从WindowManagerService开始,我们知道WMS是window的最终管理者,在WMS中为每一个应用持有一个session,关于session前面我们讲过,每个应用都是全局单例,负责和WMS通信的binder对象。WMS为每个window都建立了一个windowStatus对象,同一个应用的window使用同个session进行跨进程通信,结构大概如下:

    WMS结构

    而负责与WMS通信的,是viewRootImpl。前面我们讲过每个view树即为一个window,viewRootImpl负责和WMS进行通信,同时也负责view的绘制。如果把上面的图画仔细一点就是:

    更详细的结构图

    图中每一个windowStatus对应一个viewRootImpl,WMS通过viewRootImpl来控制view。这也就是window机制的管理结构。当我们需要添加window的时候,最终的逻辑实现是WindowManagerGlobal,他的内部使用自己的session创建一个viewRootImpl,然后向WMS申请添加window,结构图大概如下:

    window的添加结构

    windowManagerGlobal使用自己的IWindowSession创建viewRootImpl,这个IWindowSession是全局单例。viewRootImpl和WMS申请创建window,然后WMS允许之后,再通知viewRootImpl绘制view,同时WMS通过windowStatus存储了viewRootImpl的相关信息,这样如果WMS需要修改view,直接通过viewRootImpl就可以修改view了。


    从上面的描述中可以发现我全程没有提及到PhoneWindow和WindowManagerImpl。这是因为他们不属于window机制内的类,而是封装于window机制之上的框架。假设如果没有PhoneWindow和WindowManager我们该如何添加一个window?首先需要调用WindowGlobal获取session,再创建viewRootImpl,再访问wms,然后再利用viewRootImpl绘制view,是不是很复杂,而这仅仅只是整体的步骤。而WindowManagerImpl正是这个功能。他内部拥有WindowManagerGlobal的单例,然后帮助我们完成了这一系列的步骤。同时,windowManagerImpl也是只有一个实例,其他的windowManagerImpl都是建立在windowManagerImpl单例上。这一点在前面有通过源码介绍到。

    另外,上面我讲到PhoneWindow并不是window而是一个辅助Activity管理的工具类,那为什么他不要命名为windowUtils呢?首先,PhoneWindow这个类是谷歌给window机制进行更上一层的封装。PhoneWindow内部拥有一个DecorView,我们的布局view都是添加到decorView中的,因为我们可以通过给decorView设置背景,宽高度,标题栏,按键反馈等等,来间接给我们的布局view设置。这样一来,PhoneWindow的存在,向开发者屏蔽真正的window,暴露给开发者一个“存在的”window。我们可以认为PhoneWindow就是一个window,window是view容器。当我们需要在屏幕上添加view的时候,只需要获得应用window对应的windowManagerImpl,然后直接调用addView方法添加view即可。这里也可以解释为什么windowManager的接口方法是addView而不是addWindow,一是window确实是以view的存在形式没错,二是为了向开发者屏蔽真正的window,让我们以为是在往window中添加view,window是真实存在的东西。他们的关系画个图如下:

    window整体结构

    黄色部分输于谷歌提供给开发者的window框架,而绿色是真正的window机制结构。通过PhoneWindow我们可以很方便地进行window操作,而不须了解底层究竟是如何工作的。PhoneWindow的存在,更是让window的“可见性”得到了实现,让window变成了一个“view容器”。

    好了最后来总结一下:

    • Android内部的window机制与谷歌暴露给我们的api是不一样的,谷歌封装的目的是为了让我们更好地使用window。
    • dialog、popupWindow等框架更是对具体场景进行更进一步的封装。
    • 我们在了解window机制的时候,需要跳过应用层,看到window的本质,才能更好地帮助我们理解window。
    • 在android的其他地方也是一样,利用封装向开发者屏蔽底层逻辑,让我们更好地运用。但如果我们需要了解他的机制的时候,就需要绕过这层封装,看到本质。

    总结

    全文到这里,就基本结束了。下面先总结一下我这篇文章说了什么:

    • 详述了什么是window
    • 对window的各种参数进行讲解
    • 讲解window机制内的关键类
    • 从源码讲解window的添加流程以及各大组件的window添加流程
    • 详解了PhoneWindow与window的关系,谈了关于谷歌的封装思想

    文中最重要的一点就是认识window的本质,区分好window和view之间的关系以及window与PhoneWindow的关系。

    笔者在写这篇文章的时候,对于各节的安排是比较犹豫的:如果先讲概念,没有源码流程的讲解很难懂;先讲源码流程,没有概念的认知很难读懂源码。最终还是决定了先讲window的真正概念,先让读者有个整体上的感知。

    文章很长,笔者对于window想要讲的都在这篇文章中。

    希望文章对你有帮助。

    全文到此,感谢你的阅读

    原创不易,觉得有帮助可以点赞收藏评论转发关注。
    笔者才疏学浅,有任何错误欢迎评论区或私信交流。
    如需转载请私信交流。

    另外欢迎光临笔者的个人博客:传送门


    参考文献

    《Android开发艺术探索》

    《Android进阶解密》

    展开全文
  • 最新Clover,解决window10

    千次下载 热门讨论 2015-09-28 11:22:15
    解决clover在window10奔溃的问题 文章链接地址:http://www.cnblogs.com/draem0507/p/4843612.html
  • window.open用法详解

    万次阅读 多人点赞 2020-08-09 17:44:52
    目录一、window.open1. 基本语法 本文主要分两部分。第一部分详细介绍window.open的详细用法,包括它的基本语法、使用说明等。第二部分由window.open引出,我们将介绍当存在多个页面相互关联时,页面之间的通信问题...

    window.open

    1. 参数

    window.open常用来在新的window或新的tab页打开一个页面或文件(如图片、PDF等),它支持三个参数:

    1. strUrl:要打开的页面或资源的url地址。
    2. strWindowName:窗口的名字,用于后续对该窗口的引用,不是窗口的标题。
    3. strWindowFeatures:窗口的描述参数,如尺寸、位置、是否启用工具栏等。

    该方法的返回值是新打开的窗口的引用,也就是新窗口的window对象。在遵循同源策略的情况下,可以直接通过该对象访问被打开的页面;即使在跨域的情况下,也可以通过window.postMessage向其发消息。

    我们分别来解读这三个参数的用法:

    (1). strUrl

    打开的窗口中要加载的url,可以是一个HTML页面,或者其他任何浏览器能打开的资源文件。

    要加载的url可以是同域的,也可以是跨域的。在跨域条件下,window.open满足和跨域的iframe一样的限制。从通信的角度来说,使用window.open打开窗口和在内嵌的iframe内打开页面是等价的,两者的差异更多是在于视觉效果的不同。

    strUrl允许传入空值。此时第二个参数必须传入一个已打开的窗口的名字,从而获取这个窗口的引用:

    // 打开一个窗口
    let win = window.open('https://www.baidu.com', 'baidu');
    
    // 通过窗口名获取上述窗口
    let refWin = window.open('', 'baidu');
    

    这样就可以在不打开新窗口的情况下获得窗口名为'baidu'的窗口的引用。

    (2). strWindowName

    该参数是被打开的窗口的名字,注意,它并不是窗口的标题。该参数只是一个窗口标识,用于以后通过它来找到对应的窗口的引用。

    比如在上面的例子中,我们可以通过'baidu'这个名字来找到刚打开的百度的窗口,这样就不需要在全局变量中保存该窗口的引用了(它的代价不在于内存损耗,而在于对全局变量的维护)。

    该参数除了支持普通的名字外,还支持和a标签一样的特殊关键字:_self_blank_parent_top,分别用于在当前窗口空白窗口父窗口顶级窗口中打开该窗口,具体的行为请参考a标签的target属性。

    当传入了已经被使用过的窗口的名字时,不会新打开一个窗口,而是在该名字对应的窗口中打开,该窗口之前加载的内容会被替换。

    // 在上述窗口打开csdn首页
    window.open('https://csdn.net', 'baidu');
    

    如果总是想打开新页面,可以给第二个参数传入'_blank'。不过此时如果需要引用这些窗口,需要即时保存窗口的引用。

    (3). strWindowFeatures

    窗口参数描述,字符串类型,各个参数由逗号隔开,参数之间以等号连接。比如下面的例子可以在距当前window左上角(10, 10)位置处打开一个宽度400像素,高度200像素的窗口:

    window.open('https://www.baidu.com', 
      'baidu', 
      'top=10,left=10,width=400,height=200');
    

    在这里插入图片描述
    下面我们来看一些常用参数(图片来自mdn,由于不同浏览器的页面结构不一样,因此不是每个参数对每个浏览器都生效,具体请以实际效果为准。在Chrome中,基本只支持left、top、height和width,因此我们暂且仅介绍这四个参数):
    在这里插入图片描述

    1. left,新窗口相对于当前浏览器页面左侧的距离。
    2. top,新窗口相对于浏览器页面顶部的位置,注意,不是相对于文档区域,而是整个浏览器页面。
    3. height,窗口内容区(即用户区,不包含工具栏、标签栏等)的高度,单位像素,最小值100。
    4. weight,窗口的宽度(包含滚动条),单位像素,最小值100。

    其他参数如menubar、toolbar、location、personalbar等在Chrome中均不支持,如果需要在其他浏览器中启用,请参考mdn - window.open

    2. 返回值

    window.open返回的是对新打开的窗口的引用,即该窗口的window对象:

    let refWin = window.open('https://www.baidu.com', 'baidu');
    console.log(refWin);
    

    在这里插入图片描述
    不过这里引用到的window对象并不具备完整的DOM属性和方法,它仅仅提供了访问该页面的一些基本属性和方法,如图所示。

    1. blur(),手动移除窗口焦点的方法,refWin.blur()可使该窗口失去焦点。
    2. close(),关闭该窗口的方法。
    3. closed,标识该窗口是否已经被关闭。
    4. frames,新窗口内的frames
    5. length,新窗口内iframes的数量。
    6. location,新窗口window的location对象,用于访问窗口的地址信息。
    7. opener,该窗口的打开者。如我们在a页面通过window.open打开b页面,那么b页面的window.opener就是a页面的window。
    8. parent,该窗口的父窗口,由于是顶级窗口,因此它的值等于window自身。
    9. postMessage,通信接口,通过该方法可以实现向新窗口发送消息,优势是支持跨域。
    10. selfwindowtop,前两个均代指当前window,top指的是当前窗口所在页面的顶级窗口,由于自身已经是顶级窗口,因此top也是当前window。

    3. 通信问题

    使用window.open打开新窗口时,原窗口与新窗口之间是可以实现双向通信的。

    在不跨域的情况下,可以直接访问页面内的任何全局变量、方法或DOM元素等。新窗口通过window.opener可以找到原窗口的window;而原窗口可以直接通过window.open的返回值访问新窗口,或者通过该窗口的名字找到该窗口,方法为:let ref = window.open('', 'windowName')

    由于新窗口的加载是异步的,因此不能在调用window.open之后立即访问该窗口。可以在窗口的onload事件内访问新窗口:

    let ref = window.open('/index.html');
    ref.onload = function(){
      ... // 与新窗口通信
    }
    

    而在新页面中,可以直接通过window.opener访问原窗口,如:

    // 查找原窗口内的p元素
    let p = document.querySelectorAll('p', 
      window.opener.document);
    

    在跨域的情况下,以上的方法会报错,因为会受到浏览器跨域安全策略的限制,此时就需要通过window.postMessage实现页面之间的通信。

    在原窗口,可以通过对新窗口的引用调用postMessage:

    let ref = window.open('https://www.baidu.com');
    ref.postMessage(data, '*');
    

    在新窗口内,同样通过window.opener访问原窗口:

    window.opener.postMessage(data, '*');
    

    在使用postMessage进行通信的时候存在一个小的兼容性问题,那就是IE8和IE9中的独立窗口之间通信时只能传递字符串,不支持其他数据类型,而在与页面内的iframe通信时没有这个问题。

    总结

    window.open本质上可以看做<a>标签的js版本,或者说是编码式地打开窗口。但它比<a>标签更加灵活,可以通过js实现与打开的页面之间的通信。

    展开全文
  • JavaScript Window窗口对象

    千次阅读 多人点赞 2020-01-18 11:49:05
    文章目录一、Window对象概述1、Window对象属性2、Window对象方法3、如何使用二、对话框1、警告对话框2、确认对话框3、提示对话框三、打开与关闭窗口1、打开窗口2、关闭窗口(1)关闭当前窗口(2)关闭子窗口四、控制...


    一、Window对象概述

    Window对象可以用来打开浏览器,调整浏览器的位置、大小等等功能。

    Window对象可以处理框架和框架之间的关系,通过这种关系在一个框架中处理另一个框架中的文档。他也是所有对象的顶级对象,通过Window对象的子对象进行操作,可以实现更多的效果。

    1、Window对象属性

    每个对象都有自己的属性,顶级对象Window是所有其他子对象的父对象,它可以出现在每个页面上,并且可以咋单个JS应用程序中被多次使用。

    下表列出了一些Window对象常用属性:

    属性说明
    document对话框中显示当前文档
    frames表示当前对话框中所有frame对象的集合
    location指定当前文档的URL
    name对话框的名字
    status/defaultStatus状态栏中的当前信息
    top表示最顶层的浏览器对话框
    parent/opener表示包含当前对话框的夫对话框
    closed表示当前对话框是否关闭(布尔值)
    self表示当前对话框
    screen表示用户屏幕,提供屏幕尺寸、颜色深度等信息
    navigator表示浏览器对象,用于获取与浏览器相关的信息

    2、Window对象方法

    方法说明
    alert()弹出警告对话框
    confrim()在确认对话框显示指定的字符串
    prompt()弹出一个提示对话框
    open()打开新浏览器对话框并且显示URL或名字引用的文档,并设置创建对话框的属性
    close()关闭被引用的对话框
    focus()指定对话框放在所有浏览器最前面
    blur()指定对话框放在所有浏览器最后面
    scrollTo(x,y)把对话框滚动到指定的坐标
    scrollBy(offsetx,offsety)按照指定位移量滚动对话框
    setTimeout(timer)在指定的毫秒数过后,对传递的表达式求值
    setInerval(interval)指定周期性执行代码
    moveTo(x,y)将对话框移动到指定坐标
    moveBy(offsetx,offsety)将对话框移动到指定的位移量处
    resizeTo(x,y)设置对话框大小
    resizeBy(offsetx,offsety)按照指定的位移量设置对话框大小
    print()“打印”
    navigate(URL)使用对话框显示URL指定的页面

    3、如何使用

    JS运行使用一个字符串来给窗口命名,也可以使用一些关键字来代替。

    Window对象可以直接调用方法和属性,不需要使用new运算符来创建对象:

    window.属性名
    window.方法名(参数列表)
    
    //    使用关键字来代替window   //
    parent.属性名
    parent.方法名(参数列表)
    

    二、对话框

    为了响应用户的某种需求而弹出的小窗口

    1、警告对话框——alert()

    语法如下:

    window.alert(str);
    

    弹出警告对话框,str为对话框显示的字符串,一般运行结果如下:
    在这里插入图片描述

    2、确认对话框——confrim()

    语法如下:

    window.confrim(question);
    

    其中question是要显示在确认对话框的纯文本,也是表达了程序想让用户回答的问题

    而且他有一个返回值,如果单击了确定返回真、反之为假。

    比如下面的这段小程序:

    <script>
        var flag = window.confirm("你确定要关闭浏览器窗口吗?");
        if(flag)
            window.close();
    </script>
    

    会展示下面的结果:
    在这里插入图片描述

    3、提示对话框——prompt()

    弹出一个提示框,在提示框中有一个输入框,在显示输入框的时候,在输入框内显示提示字符串,在输入文本框显示缺省文本并等待用户输入,输入之后,单击确定返回用户输入的字符串,如果是取消则返回null值

    语法如下:

    window.prompt(str1,str2)
    

    参数说明:其中str1表示在对话框要被显示的信息,str2指定对话框内输入框的值。

    比如下面这段小程序:

    <script>
        function pro() {
            var message = document.getElementById("message");
            message.value = window.prompt(message.value,"返回的信息")
        }
    </script>
    <input id="message" type="text" size="40" value="请在此输入信息">
    <br><br>
    <input type="button" value="显示对话框" onClick="pro()">
    

    效果如下:
    在这里插入图片描述


    三、打开与关闭窗口

    1、打开窗口——open()

    语法格式:

    WindowVar = window.open(url,name,[location])
    

    参数说明:

    • WindowVar:如果open方法成功,返回一个Window对象的句柄。
    • url目标窗口的URL
    • name:目标窗口Window对象名称
    • location:大考窗口的可选参数
      他的可选参数,如下表所示:
    参数说明
    top窗口顶部距离屏幕顶部的像素数
    left窗口左端距离屏幕左端的像素数
    width/height宽度/高度
    scrollbars是否显示滚动条
    resizable设定对话框大小是否固定
    toolbar浏览器的工具条,包括后退及前进按钮
    menubar菜单条,一般包括有文件、编辑及其他一些条目
    location定位区,地址栏
    direction更新信息的按钮

    下面举几个例子:

    打开一个新窗口:window.open("new.html","new")

    打开一个指定大小的窗口:window.open("new.html","new","height=140,width=690")

    打开一个指定位置的窗口:window.open("new.html","new","top=300,left=200")

    打开一个带滚动条的固定窗口:window.open("new.html","new","scrollbars,resizable")

    2、关闭窗口

    (1)关闭当前窗口

    下面任意语句可以实现关闭当前窗口:

    window.close();
    close();
    this.close();
    

    例子:关闭子窗口时自动刷新父窗口

    步骤1: 在原来的页面,怼一个<a>链接,用于打开一个新窗口,代码如下:

    <a href="#" onClick="javascript:window.open('new.html','width=400,height=220')">打开链接</a>
    

    步骤2: 新建new.html,然后写入脚本和一个按钮,代码如下:

    <script>
        function clo() {
            alert("关闭子窗口!");
            window.opener.location.reload();        //刷新父窗口
            window.close();
        }
    </script>
    <input type="submit" value="关闭" onclick="clo()">
    

    效果如下:
    在这里插入图片描述

    (2)关闭子窗口

    通过窗口句柄以变量的形式进行保存,然后通过close方法关闭创建的窗口,语法如下:

    windowname.close();
    

    其中windowname表示已打开窗口的句柄

    例如,在主窗口旁边弹出一个子窗口,当单击主窗口的按钮后,自动关闭子窗口,代码如下:

    <form name="form1">
        <input type="button" name="Button" value="关闭子窗口" onclick="newclose()">
    </form>
    <script>
        var win = window.open("new.html","new","width=300,height=100");
        function newclose() {
            win.close();
        }
    </script>
    

    效果如下:
    在这里插入图片描述


    四、控制窗口

    1、移动窗口

    (1)moveTo()方法

    语法如下:

    window.moveTo(x,y);
    

    其中x,y是窗口左上角的坐标,

    (2)resizeTo()方法

    这个方法可以将当前窗口改成(x,y)大小,x、y分别代表宽度和高度,语法如下:

    window.resizeTo(x,y);
    

    (3)screen对象

    屏幕对象反映了当前用户的屏幕设置,下表列出了屏幕对象的一些属性:

    属性说明
    width整个屏幕的水平尺寸
    height整个屏幕的垂直尺寸
    pixelDepth显示器每个像素的位数
    colorDepth返回当前颜色设置所用的位数,1代表黑白;8代表256色(256种颜色);16代表增强色(大概64000种颜色);24/32代表真彩色(1600万种颜色)
    availWidth返回窗口内容区域的水平尺寸
    availHeight返回川康内容区域的垂直尺寸

    2、窗口滚动

    利用Window对象的scroll()方法可以指定窗口的当前位置,从而实现窗口的滚动效果,语法如下:

    window.scroll(x,y);			//滚动到指定的绝对位置,相对文档的左上角
    window.scrollTo(x,y);		//同上
    window.scrollBy(x,y);		
    /*可以将文档滚动到相对位置,如果参数x为正数,向右滚动,否则向左;如果参数y为正数向下滚动,否则向上*/
    

    比如选择一张我们的女神江疏影把图片拉大,然后写脚本:

    <img src="1.jpg" height="1000">
    <script>
        var pos = 0;
        function scroller() {
            pos++;
            scrollTo(0,pos);
            clearTimeout(timer);
            var timer = setTimeout("scroller()",10);
        }
        scroller();
    </script>![在这里插入图片描述](https://img-blog.csdnimg.cn/20200118113027730.gif)
    

    效果如下:(太美啦!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!)
    在这里插入图片描述

    3、改变窗口大小

    实现将当前窗口改变为指定的大小(x,y),当x,y>0为扩大,反之为缩小,语法如下:

    window.resizeBy(x,y);
    

    例子:动态改变窗口大小

    代码如下:

    <script>
        var winheight,winsize,x;
        function openwin() {
            winheight=100;
            winsize=100;
            x=5;
            win2 = window.open("new.html","","scrollbars='no'");
            win2.moveTo(0,0);
            win2.resizeTo(100,100);
            resize();
        }
        function resize() {
            if(winheight>=screen.availHeight-3)
                x=0;
            win2.resizeBy(5,x);
            winheight+=5;
            winsize+=5;
            if(winsize>=screen.width-5){
                winheight=100;
                winsize=100;
                return;
            }
            setTimeout("resize()",50);
        }
    </script>
    <a href="javascript:openwin()">打开一个自动改变大小的窗口</a>
    

    效果如下:
    在这里插入图片描述

    4、访问窗口历史

    利用history对象实现访问窗口历史,history对象是一个只读URL字符串数组,主要存储一个最近访问网页的URL地址列表。语法如下:

    [window.]history.property|method([parameters])
    

    history对象常用属性:

    属性说明
    length历史列表的长度,用于判断列表中的入口数目
    current当前文档的URL
    next历史列表的下一个URL
    previous历史列表的前一个URL

    常用方法:

    方法说明
    back()退回前一页
    forward()重新进入下一页
    go()进入指定网页

    比如使用history对象中的back()forward()方法来引导跳转:

    <a href="javascript:window.history.forward();">forward</a>
    <a href="javascript:window.history.back()">back</a>
    

    使用history.go()方法指定要访问的历史记录,如果参数为正数向前移动,否则向后移动

    <a href="javascript:window.history.go(-1)">向后退一次</a>
    <a href="javascript:window.history.go(2)">向前进两次</a>
    

    length属性访问history数组的长度,通过它可以很快访问末尾:

    <a href="javascript:window.history.go(window.history.length-1)">末尾</a>
    

    5、设置超时

    为一个窗口设置在某段时间后执行何种操作,称为设置超时

    window对象的setTimeout()方法用于设一个超时,语法如下:

    timerId = setTimeout(要执行的代码,以毫秒为单位时间)
    

    还可以使用clearTimeout()来中止该超时设置,语法格式如下:

    clearTimeout(timerId);
    

    展开全文
  • Window类的设计与实现_艾孜尔江撰

    千次阅读 2021-06-14 11:06:12
    设计(头文件) #pragma once #include "LoadInAdvance.h" namespace RenderEngine { class Window { public: static Window&... int Initialize(int windowWidth, int windowHeight, LPCTSTR
  • Flink的Window源码剖析

    千次阅读 2019-09-16 10:27:16
    Window 可以看到,GlobalWindow和TimeWindow均继承自抽象类Window,其源码如下: public abstract class Window { public abstract long maxTimestamp(); } 可以看出,Window抽象类仅有一个maxTimestamp()方法...
  • 使用 window.open 打开新窗口

    千次阅读 2021-11-21 16:49:45
    打开新窗口的方式有哪些 在链接上单击右键,然后选择「在新窗口打开链接」或者「在新标签打开链接」 按住键盘上的 Ctrl/Command ...let windowObjectReference = window.open(strUrl, strWindowName, [strWindowFeatur
  • 浏览器对象的使用(window对象)

    千次阅读 2020-12-19 00:08:01
    window对象是BOM的核心,window对象指当前的浏览器窗口。window对象方法:注意:在JavaScript基础篇中,已讲解了部分属性,window对象重点讲解计时器。1、JavaScript 计时器在JavaScript中,我们可以在设定的时间间隔...
  • JavaScript中window.open()和Window Location href的区别

    万次阅读 多人点赞 2019-03-06 11:12:41
    3:window.open和window.location.href的区别 1:区别 2.window.open不一定是打开一个新窗口!!!!!!!! 3:关于重新定位 4.:在框架内指定页面打开连接 5:是否打开其他网站地址 6:window.open()经过设置后的弹...
  • js的window对象与属性的使用

    万次阅读 2018-11-23 06:11:36
     浏览器对象:window对象。  Window 对象会在 &lt;body&gt; 或 &lt;frameset&gt; 每次出现时被自动创建。 2. window对象 window对象是BOM中所有对象的核心。BOM Browser Object Model 3.window...
  • 文章目录前言AbstractShifted Windows AttentionShifted window partitioning in successive blocks 前言 论文链接:Swin Transformer: Hierarchical Vision Transformer using Shifted Windows 源码地址:...
  • Py之slidingwindow&sliding_window:slidingwindow、sliding_window的简介、安装、使用方法之详细攻略 目录 sliding_window的简介 sliding_window的安装 sliding_window的使用方法 slidingwindow、...
  • UnityEditor研究学习之EditorWindow

    千次阅读 2019-03-22 22:26:41
    UnityEditor研究学习之EditorWindow 在unity使用过程中,其实我们都是在各个不同功能的Window下工作。 比如在Scene窗口中操作物体,在Inspector中操作物体属性,在Game视窗中观察游戏状态。 所以窗口是Unity...
  • js中document与window的区别

    千次阅读 2019-04-05 13:24:43
     window.self //引用本窗户window=window.self  window.name //为窗口命名  window.defaultStatus //设定窗户状态栏信息  window.location //URL地址,配备布置这个属性可以打开新的页面 对象方法  ...
  • 一文搞定JS中window对象

    千次阅读 2020-06-25 14:16:41
    1.window对象简介 2.窗口操作 3.对话框 4.定时器 5.location对象 6.navagator对象 7.document对象 1.window对象简介 1.在JavaScript中,一个浏览器窗口就是一个window对象。 2.一个窗口就是一个window对象,...
  • 应用窗口添加在AppWindowToken的WindowList中,系统窗口添加在WindowToken的WindowList中,子窗口添加在父窗口的WindowState的WindowList中,AppWindowToken继承WindowToken,WindowToken继承WindowContainer,...
  • iframe或者window.open()添加请求头方法

    千次阅读 2021-09-22 16:07:59
    window.URL || window.webkitURL; var objectUrl = URL.createObjectURL(blob); if (fileName) { var a = document.createElement('a'); if (typeof a.download === 'undefined&#...
  • JavaScript浏览器对象之window对象详解

    千次阅读 2019-05-06 18:04:20
    目录 1 window对象概述 ...2.2 window.innerHeight属性,window.innerWidth属性 2.3 window.pageXOffset属性,window.pageYOffset属性 2.4 iframe元素 2.5 Navigator对象 2.5.1 Navigator.userAgen...
  • 举个例子,在TypeScript中,当我们需要给window对象添加全局变量(如testName),或者需要使用window下自定义创建的变量(以testName为例)。会出现以下ts报错:类型“Window & typeof globalThis”上不存在属性...
  • Android Window 机制探索

    万次阅读 多人点赞 2017-11-16 21:01:01
    Window的概念 setContentView installDecor generateLayout Window的类型 应用窗口 子窗口 系统窗口 Window的内部机制(Activity) Window的创建过程 Window的添加过程 Window的token Activity的attach...
  • 解决办法: ...1.获取宽高放到window.onLoad = function(){ const H = window.innerHeight; const W = window.innerWidth; } 2.使用document.body.clientWidth/document.body.clientHeight代替
  • 今天用ajax做个简单例子登录成功后跳转到展示数据页面,我想要登录成功后用layer提示一下再跳转页面,但是我用window.location.href会直接跳过我的layer提示,就想着让弹窗先出来几秒然后再跳转,现在已经解决,分享给...
  • Flink中window Trigger的理解

    千次阅读 2019-06-03 18:12:27
    初识Flink,对于window相关的几个概念不甚了解,先抛出几个关键问题! window是如何划分的? watermark生成方式?多久产生一次? window触发计算条件? 延迟数据如何处理? 一、window是如何划分的? ...
  • JS逆向中常见的window.webpackJsonp分析

    千次阅读 2022-03-31 15:59:50
    }, 520:function(x){ console.log('520' + x) }, 't2':function(x){ } }]) 这种形式看着可能更加一头雾水了,需要一步步分析一下究竟是什么意思: 第一部分: (window.webpackJsonp = window.webpackJsonp || []) ...
  • window.name属性详解(Javascript)

    千次阅读 2020-11-17 21:22:45
    关于window下自带name的属性 不知道大家有没有发现这样一种情况 在控制台里直接输出未声明变量,正常情况应该是会报错的,而且声明未赋值的变量输出应该是undefined 但是偏偏就个别特例,就是name属性 其实window...
  • Flink窗口-计数窗口(CountWindow

    千次阅读 2021-05-23 17:56:38
    countWindow的具体使用
  • 探索浏览器页面关闭window.close()的使用

    千次阅读 多人点赞 2020-08-19 10:36:51
    说起来window.close(),这也是个“不太让人省心”的角色。因为浏览器兼容性千差万别,还对他有诸多限制。 使用语法: window.close() 场景复现 昨天发现有人在csdn上传违禁文件,举报后来到了这个页面: 里面那个...
  • window.URL.createObjectURL

    千次阅读 2021-09-15 13:43:49
    URL对象是硬盘(SD卡等)指向的一个文件路径,如果我们做文件上传的时候,想在没有上传服务器端的情况下看到上传图片的效果图的时候,就可以通过var url = window.URL.createObjectURL(files[0]);获得一个http格式...
  • --window-top和--window-bottom

    千次阅读 2021-03-31 15:06:04
    为了优雅的解决多端高度定位问题,uni-app新增了2个css变量:–window-top和–window-bottom,这代表了页面的内容区域距离顶部和底部的距离。举个实例,如果你想在原生tabbar上方悬浮一个菜单,之前写bottom:0。这样...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,252,837
精华内容 901,134
关键字:

window