Android窗口管理3 创建和添加Window

一 概述

本篇文章主要从系统和应用两个层次分析 Window 的添加过程,以及论述重要组件之间的关系:

1.SystemUI (如 StatusBar) 和 Activity 中 Window 的创建和添加
2.Activity / PhoneWindow / DecorView / StatusBar / ViewRootImpl 之间的关系

涉及代码如下:

frameworks/base/packages/SystemUI/src/com/android/systemui/SystemBars.java
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java

frameworks/base/core/java/android/app/ContextImpl.java
frameworks/base/core/java/android/app/ActivityThread.java
frameworks/base/core/java/android/app/Activity.java
frameworks/base/core/java/android/view/WindowManagerImpl.java
frameworks/base/core/java/android/view/Window.java
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
frameworks/base/core/java/com/android/internal/policy/DecorView.java

frameworks/base/packages/SystemUI/AndroidManifest.xml
frameworks/base/packages/SystemUI/res/values/config.xml
frameworks/base/packages/SystemUI/res/layout/super_status_bar.xml
frameworks/base/packages/SystemUI/res/layout/status_bar.xml
frameworks/base/core/res/res/layout/screen_simple.xml

二 系统窗口的添加

SystemUI 包括很多子类, 状态栏 StatusBar 是最常见的一种。本文以 StatusBar 为例进行分析。

SystemUI 运行的进程名是 SystemUIApplication。

2.1 SystemUIApplication

frameworks/base/packages/SystemUI/AndroidManifest.xml

<application
    android:name=".SystemUIApplication"
    android:persistent="true"
    android:allowClearUserData="false"
    android:allowBackup="false"
    android:hardwareAccelerated="true"
    android:label="@string/app_label"
    android:icon="@drawable/icon"
    android:process="com.android.systemui"
    android:supportsRtl="true"
    android:theme="@style/Theme.SystemUI"
    android:defaultToDeviceProtectedStorage="true"
    android:directBootAware="true"
    android:appComponentFactory="androidx.core.app.CoreComponentFactory">
    ...
    <service android:name="SystemUIService" android:exported="true"/>
    ...
</application>

SystemUI 调用链:

SystemUIApplication.onCreate -> SystemUIService.onCreate -> SystemUIApplication.startServicesIfNeeded -> mServices[i].start() -> SystemBars.start

2.2 SystemBars.start

frameworks/base/packages/SystemUI/src/com/android/systemui/SystemBars.java

public class SystemBars extends SystemUI {
    private SystemUI mStatusBar;
 
    @Override
    public void start() {
        createStatusBarFromConfig();
    }
 
    private void createStatusBarFromConfig() {
        // 从config.xml中读取className-"com.android.systemui.statusbar.phone.StatusBar"
        final String clsName = mContext.getString(R.string.config_statusBarComponent);
        // 反射创建StatusBar对象
        Class<?> cls = mContext.getClassLoader().loadClass(clsName);
        mStatusBar = (SystemUI) cls.newInstance();
        mStatusBar.mContext = mContext;
        mStatusBar.mComponents = mComponents;
        // 将StatusBar注入系统UI的根组件
        if (mStatusBar instanceof StatusBar) {
            SystemUIFactory.getInstance().getRootComponent()
                    .getStatusBarInjector()
                    .createStatusBar((StatusBar) mStatusBar);
        }
        // 创建状态栏View, 并将其添加到WindowManager
        mStatusBar.start();
    }
}

SystemBars.start 做的事情:

1.读取配置文件中的属性 config_statusBarComponent,得到字符串 className 为 StatusBar 的字符串
2.利用反射创建 StatusBar 对象,并调用 StatusBar 的 start 方法用来创建状态栏 View 并将其添加到 WindowManager 中

2.3 StatusBar

2.3.1 StatusBar.start

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java

public class StatusBar extends SystemUI implements DemoMode,
        ActivityStarter, OnUnlockMethodChangedListener,
        OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
        ColorExtractor.OnColorsChangedListener, ConfigurationListener,
        StatusBarStateController.StateListener, ShadeController,
        ActivityLaunchAnimator.Callback, AmbientPulseManager.OnAmbientChangedListener,
        AppOpsController.Callback {
    protected WindowManager mWindowManager;
    protected IWindowManager mWindowManagerService;
    // 状态栏最外层View
    protected StatusBarWindowView mStatusBarWindow;
	// CollapsedStatusBarFragment的根View(即StatusBarWindowView的childView)
    protected PhoneStatusBarView mStatusBarView;
 
    @Override
    public void start() {
        ......
        // 获取WindowManagerImpl
        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        // 获取IWindowManager.Stub.Proxy
        mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
        ......
        // 创建状态栏View, 并将其添加到WindowManager
        createAndAddWindows(result);
        ......
    }
    ......
}

2.3.2 StatusBar.createAndAddWindows

// 创建状态栏View, 并将其添加到WindowManager
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
    // 根据布局文件super_status_bar.xml创建StatusBarWindowView. 
    makeStatusBarView(result);
    mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
    // 将StatusBarWindowView添加到WindowManager. 
    mStatusBarWindowController.add(mStatusBarWindow, getStatusBarHeight());
}

// 获取状态栏高度, App就是借鉴这个方法
public int getStatusBarHeight() {
    if (mNaturalBarHeight < 0) {
        final Resources res = mContext.getResources();
        mNaturalBarHeight = 
        res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
    }
    return mNaturalBarHeight;
}

这里要提一下:
getStatusBarHeight 这个方法,App 开发中获取状态栏高度就是借鉴的这个方法,只不过是反射获取该属性。基本思路就是看源码中 StatusBar 对应的布局,然后读取其高度属性。

2.3.3 StatusBar.makeStatusBarView

protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
    ......
    // 根据布局文件super_status_bar.xml创建StatusBarWindowView
    inflateStatusBarWindow(context);
    mStatusBarWindow.setService(this);
    mStatusBarWindow.setOnTouchListener(getStatusBarWindowTouchListener());
    ......
    // status_bar_container位置填充CollapsedStatusBarFragment
    // CollapsedStatusBarFragment对应的布局文件为status_bar.xml
    FragmentHostManager.get(mStatusBarWindow).
    addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
            // Fragment创建完成时, 初始化
            CollapsedStatusBarFragment statusBarFragment = 
            (CollapsedStatusBarFragment) fragment;
            statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
            PhoneStatusBarView oldStatusBarView = mStatusBarView;
            mStatusBarView = (PhoneStatusBarView) fragment.getView();
            mStatusBarView.setBar(this);
            mStatusBarView.setPanel(mNotificationPanel);
            mStatusBarView.setScrimController(mScrimController);
            mStatusBarView.setBouncerShowing(mBouncerShowing);
            if (oldStatusBarView != null) {
                float fraction = oldStatusBarView.getExpansionFraction();
                boolean expanded = oldStatusBarView.isExpanded();
                mStatusBarView.panelExpansionChanged(fraction, expanded);
            }
            ......
            mStatusBarWindow.setStatusBarView(mStatusBarView);
            ......
        }).getFragmentManager()
        .beginTransaction()
        .replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
                CollapsedStatusBarFragment.TAG)
        .commit();
    ......
    // 创建导航栏(仅针对CarStatusBar)
    createNavigationBar(result);
    ......
}

2.3.4 StatusBar.inflateStatusBarWindow

protected void inflateStatusBarWindow(Context context) {
    // 从super_status_bar.xml布局文件创建StatusBarWindowView
    mStatusBarWindow = (StatusBarWindowView) mInjectionInflater.injectable(
            LayoutInflater.from(context)).inflate(R.layout.super_status_bar, null);
}

2.4 StatusBarWindowController.add

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java

// Adds the status bar view to the window manager.
public void add(ViewGroup statusBarView, int barHeight) {
    mLp = new WindowManager.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            barHeight,
            WindowManager.LayoutParams.TYPE_STATUS_BAR,
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                    | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
            PixelFormat.TRANSLUCENT);
    mLp.token = new Binder();
    mLp.gravity = Gravity.TOP;
    mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
    mLp.setTitle("StatusBar");
    mLp.packageName = mContext.getPackageName();
    mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
    mStatusBarView = statusBarView;
    mBarHeight = barHeight;
    mWindowManager.addView(mStatusBarView, mLp);
    mLpChanged.copyFrom(mLp);
    onThemeChanged();
}

总结 StatusBar.start 做的事情:
一句话总结就是创建状态栏 View 并将其添加到 WindowManager 中。

具体过程为:

  • 根据布局文件 super_status_bar.xml 创建 StatusBarWindowView
  • 上述布局文件中 id 为 status_bar_container 的位置填充 CollapsedStatusBarFragment
  • 创建导航栏(仅针对 CarStatusBar )
  • StatusBarWindowView 添加到 WindowManager

三 应用窗口添加

3.1 ActivityThread.performLaunchActivity

frameworks/base/core/java/android/app/ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r,
        Intent customIntent) {
    ...... 
    try {
        ......
        // 将Activity attach到Application
        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); 
        ......
    }
    ......
    return activity;
}

3.2 Activity.attach

frameworks/base/core/java/android/app/Activity.java

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        ......) {
    attachBaseContext(context);
    ......
    //创建PhoneWindow
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ......
    mUiThread = Thread.currentThread(); 
    mMainThread = aThread;
    mInstrumentation = instr;
    mToken = token;
    ...... 
    //设置WindowManagerImpl
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    //设置父Window
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    } 
    // 获取本地创建的WindowManagerImpl
    //(这个和上面setWindowManager设置进来不是同一个实例)
    mWindowManager = mWindow.getWindowManager();
    ......
}

3.3 Activity.setContentView

在 Activity 启动过程中我们知道执行完 attach 方法后,就要执行回调 Activity 的 onCreate 方法了,我们会在 onCreate 方法中使用 setContentView(layoutResID),来完成布局文件的加载,那么我们来看下 setContentView 这个方法的执行流程。

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

3.4 PhoneWindow.setContentView

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        ......
    } else {
        // 将布局layoutResID添加到DecorView中id为R.id.content的位置
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

3.5 ActivityThread.handleResumeActivity

@Override
public void handleResumeActivity(IBinder token, ......) {
    ...... 
    final ActivityClientRecord r = 
    performResumeActivity(token, finalStateRequest, reason);   
    final Activity a = r.activity; 
    ...... 
    //当Window还未添加到WindowManager, 且还未finish当前Activity或
    //未启动新Activity时, 需要先添加到WindowManager
    boolean willBeVisible = !a.mStartedActivity;
    if (!willBeVisible) {
        willBeVisible = ActivityTaskManager.getService().
        willActivityBeVisible(a.getActivityToken());      
    }
    if (r.window == null && !a.mFinished && willBeVisible) {
        // 拿到PhoneWindow
        r.window = r.activity.getWindow();
        // 拿到DecorView
        View decor = r.window.getDecorView();
        // 设置DecorView不可见
        decor.setVisibility(View.INVISIBLE);
        // 获取本地创建的WindowManagerImpl
        ViewManager wm = a.getWindowManager();
        // 设置Window各属性(如type为TYPE_BASE_APPLICATION)
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        if (r.mPreserveWindow) {
            a.mWindowAdded = true;
            r.mPreserveWindow = false;
            // 获取DecorView的ViewPootImpl(每个View都有一个ViewRootImpl)
            ViewRootImpl impl = decor.getViewRootImpl();
// 通知子View已经被重建(DecorView还是旧实例, 
// Activity是新实例, 因此需要更新callbacks)
// 1.通常情况下, ViewRootImpl通过
//WindowManagerImpl#addView->ViewRootImpl#setView
//设置Activity的callbacks回调
// 2.但是如果DecorView复用时, 需要主动告诉ViewRootImpl callbacks可能发生变化
            if (impl != null) {
                impl.notifyChildRebuilt();
            }
        }
        // 当Activity可见时, 添加DecorView到WindowManagerImpl
        //或回调Window属性变化方法
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    } else if (!willBeVisible) {
        r.hideForNow = true;
    }
 
    // Get rid of anything left hanging around.
    cleanUpPendingRemoveWindows(r, false /* force */);
 
    if (!r.activity.mFinished && willBeVisible &&
     r.activity.mDecor != null && !r.hideForNow) {
        // 调用Activity.onConfigurationChanged方法
        if (r.newConfig != null) {
            performConfigurationChangedForActivity(r, r.newConfig);
            r.newConfig = null;
        }
        // 更新DecorView的LayoutParams
        WindowManager.LayoutParams l = r.window.getAttributes();
        if ((l.softInputMode & 
        WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
        != forwardBit) {
            l.softInputMode = (l.softInputMode &
            (~WindowManager.LayoutParams.
            SOFT_INPUT_IS_FORWARD_NAVIGATION)) |
             forwardBit;
            if (r.activity.mVisibleFromClient) {
                ViewManager wm = a.getWindowManager();
                View decor = r.window.getDecorView();
                wm.updateViewLayout(decor, l);
            }
        }
 
        r.activity.mVisibleFromServer = true;
        mNumVisibleActivities++;
        // 设置Activity可见
        if (r.activity.mVisibleFromClient) {
            r.activity.makeVisible();
        }
    } 
    r.nextIdle = mNewActivities;
    mNewActivities = r;
    Looper.myQueue().addIdleHandler(new Idler());
}

在 Activity 创建过程中,会创建和添加 PhoneWindow.

Activity 中 PhoneWindow 的创建和添加过程:
1.handleLaunchActivity 过程

  • 创建 Activity 和 Application 实例,调用 Activity.attach 方法将 Activity attach 到 Application
  • 在 Activity 中创建 PhoneWindow,PhoneWindow 又创建 DecorView。设置 WindowManagerImpl 到 PhoneWindow.
  • 调用 Activity.onCreate -> setContentView(layoutResID) 将 layoutResId 添加到 DecorView 中 R.id.content 位置

2.handleResumeActivity 过程

  • PhoneWindow 中的 DecorView 添加到 WindowManager
  • 设置 DecorView 可见

四 PhoneWindow&DecorView详解

我们已经分析了 Activity 中 PhoneWindow 的创建和添加过程,在 Activity#onCreate 中会调用PhoneWindow#installDecor 创建 DecorView 和子 View。接下来我们来分析下 PhoneWindow 和 DecorView 的组成。

4.1 PhoneWindow

frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

4.1.1 PhoneWindow.installDecor

// 创建DecorView及其子View
private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        // 创建DecorView
        mDecor = generateDecor(-1);
    mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        ......
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        // 根据设置的Window相关属性, 设置PhoneWindow特性
        //给PhoneWindow的根DecorView添加子View, 并返回ContentView
        mContentParent = generateLayout(mDecor); 
        ...... 
        // 对于R.layout.screen_simple没有该元素
        // 但对于R.layout.screen_action_bar包含该元素
        final DecorContentParent decorContentParent = 
   (DecorContentParent) mDecor.findViewById(R.id.decor_content_parent); 
        // 如果根View为ActionBarOverlayLayout, 则设置
        // ActionBarOverlayLayout的title/icon/logo/menu等
        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);
            }
 
            PanelFeatureState st = 
            getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!isDestroyed() && (st == null || st.menu == null)
            && !mIsStartingWindow) {
                invalidatePanelMenu(FEATURE_ACTION_BAR);
            }
        } else {
            // 如果有title元素, 更新title
            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);
                }
            }
        }
        ......
    }
}

4.1.2 PhoneWindow.generateDecor

protected DecorView generateDecor(int featureId) {
        ......
        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());
}

4.1.3 PhoneWindow.generateLayout

protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    TypedArray a = getWindowStyle();
 
    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);
    } 
    ...... 
    final Context context = getContext();
    ......
    WindowManager.LayoutParams params = getAttributes();
    ...... 
    // 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) {
        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) {
       ......
    } 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();
    // DecorView添加子View. 从父到子依次是:DecorView -> 
    //DecorCaptionView(不一定包含) -> root(即layoutResource对应的View)
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
 
    // 获取R.id.content对应的ContentView
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); 
    ...... 
    // 仅最顶层Window执行
    if (getContainer() == null) {
        mDecor.setWindowBackground(mBackgroundDrawable);
 
        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;
}

4.2 DecorView

frameworks/base/core/java/com/android/internal/policy/DecorView.java

4.2.1 onResourcesLoaded

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    if (mBackdropFrameRenderer != null) {
        loadBackgroundDrawablesIfNeeded();
        mBackdropFrameRenderer.onResourcesLoaded(
                this, mResizingBackgroundDrawable, ......);
    }
 
    // 创建DecorCaptionView(即包含系统按钮如最大化,关闭等的标题)
    mDecorCaptionView = createDecorCaptionView(inflater);
    final View root = inflater.inflate(layoutResource, null);
    if (mDecorCaptionView != null) {
        // 从父到子依次是:DecorView -> DecorCaptionView -> root
        if (mDecorCaptionView.getParent() == null) {
            addView(mDecorCaptionView,
            new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mDecorCaptionView.addView(root,
        new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
    } else {
        // 从父到子依次是:DecorView -> root
        addView(root, 0,
        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}
 
private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
    DecorCaptionView decorCaptionView = null;
    for (int i = getChildCount() - 1; i >= 0 &&
            decorCaptionView == null; i--) {
        View view = getChildAt(i);
        if (view instanceof DecorCaptionView) {
// The decor was most likely saved from a relaunch - so reuse it.
            decorCaptionView = (DecorCaptionView) view;
            removeViewAt(i);
        }
    }
    final WindowManager.LayoutParams attrs = mWindow.getAttributes();
    final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
            attrs.type == TYPE_APPLICATION ||
            attrs.type == TYPE_DRAWN_APPLICATION;
    final WindowConfiguration winConfig =
    getResources().getConfiguration().windowConfiguration;
    if (!mWindow.isFloating() && isApplication &&
    winConfig.hasWindowDecorCaption()) {
        if (decorCaptionView == null) {
            decorCaptionView = inflateDecorCaptionView(inflater);
        }
        decorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
    } else {
        decorCaptionView = null;
    } 
    // Tell the decor if it has a visible caption.
    enableCaption(decorCaptionView != null);
    return decorCaptionView;
}

总结 PhoneWindow.installDecor:
该方法在 Activity#setContentView(layoutResID) 里调用,用来在 PhoneWindow 创建 DecorView 及其子 View。

PhoneWindow#installDecor 具体步骤:
1.generateDecor:创建 DecorView (是一个 FrameLayout )
2.generateLayout:根据设置的 Window 相关属性,设置 PhoneWindow 特性。给 PhoneWindow 的根 DecorView 添加子 View,并返回 ContentView

  • 读取配置文件中的 Window 相关的属性< declare-styleable name=“Window”>
  • 根据设置的 Window 各属性值,设置 PhoneWindow 的特性(例如requestFeature/setFlags/WindowManager.LayoutParams),以及选择对应的 layout (默认为 screen_simple)
  • DecorView 添加子 View。从父到子依次是:DecorView -> DecorCaptionView (不一定包含) -> root (即上述 layout对应的 View)
  • 返回 root (即 R.layout.screen_simple ) 中 R.id.content 对应的 ContentView (即一个 FrameLayout )

DecorCaptionView 即包含系统按钮如最大化,关闭等的标题,此处不做详细介绍。
frameworks/base/core/res/res/layout/screen_simple.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub
	     android:id="@+id/action_mode_bar_stub"
         android:inflatedId="@+id/action_mode_bar"
         android:layout="@layout/action_mode_bar"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

五 Activity&PhoneWindow&DecorView关系图

在这里插入图片描述
上图是根据源码总结出来的各个类的关系图。主要包含一下三部分:
1.StatusBar
状态栏 (系统 UI ),其根 View 为 StatusBarWindowView,通过 WindowManagerImpl.addView 添加到窗口管理器

2.DecorView
(1) 应用程序 View. 通过 WindowManagerImpl.addView 添加到窗口管理器. 一个页面对应一个 Activity,一个 Activity 包含一个 PhoneWindow,PhoneWindow 的根 View 即为 DecorView,DecorView 为 FrameLayout
(2) 根据创建 Activity 时设置的 Window 属性的不同,选择不同的 layout 布局,并将该 layout 布局添加到 DecorView 中。最简单的为 R.layout.screen_simple
(3) 上述 layout 布局 R.layout.screen_simple 为 LinearLayout,包含2个元素: @id/action_mode_bar_stub 和 @android:id/content。即标题栏和内容体。在 Activity#onCreate -> setContentView(layoutResID) 调用时,会将layoutResID 填充到 @android:id/content

3.NavigationBar
通常指的手机底部的虚拟按键。

上面我们已经知道 StatusBar 和 DecorView 都会添加到 WindowManagerImpl,通过窗口管理器进行管理。因此开发者可以定制状态栏和应用程序的 UI 样式及他们之间的显示关系。
下面以沉浸式状态栏实现为例:
在这里插入图片描述
我们实现这样的效果需要遵循几个步骤:
(1) 状态栏透明
(2) DecorView 占满整个屏幕
(3) NavigationBar 的控制

实现代码如下:

private static void transparentStatusBar(final Activity activity) {
    Window window = activity.getWindow();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        // Android 5.0及以上, 设置DecorView全屏和系统状态栏透明
        window.addFlags(WindowManager.
        LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
        int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
        window.getDecorView().setSystemUiVisibility(option);
        window.setStatusBarColor(Color.TRANSPARENT);
    } else {
        // Android 4.4 设置系统状态栏透明(不能实现DecorView全屏)
        // 由于Android 4.4不存在PhoneWindow#setStatusBarColor这个方法.
        // 因此设置透明状态栏需要在DecorView添加一个AlphaStatusBarView
        addStatusBarAlpha(activity, 0)
        window.addFlags(WindowManager.
        LayoutParams.FLAG_TRANSLUCENT_STATUS);
    }
}

// 仅仅用于Android 4.4
private static void addStatusBarAlpha(final Activity activity,
      final int alpha) {
    ViewGroup parent = (ViewGroup) 
    activity.getWindow().getDecorView()
    View fakeStatusBarView = parent.findViewWithTag(TAG_ALPHA);
    if (fakeStatusBarView != null) {
        if (fakeStatusBarView.getVisibility() == View.GONE) {
            fakeStatusBarView.setVisibility(View.VISIBLE);
        }
        fakeStatusBarView.setBackgroundColor(
        Color.argb(alpha, 0, 0, 0));
    } else {
        parent.addView(createAlphaStatusBarView(
        parent.getContext(), alpha));
    }
}

// 仅仅用于Android 4.4
private static View createAlphaStatusBarView(final Context context,
final int alpha) {
    View statusBarView = new View(context);
    statusBarView.setLayoutParams(new LinearLayout.LayoutParams(
    ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight()));
    statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0));
    statusBarView.setTag(TAG_ALPHA);
    return statusBarView;
}

系统 UI 的可见性 :
系统 UI (如 StatusBar 和 NavigationBar),可以在 Activity 中通过 DecorView#setSystemUiVisibility 控制。
(1) 设置 DecorView 全屏
View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
(2) 隐藏 NavigationBar
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
(3) 其他参考 View.SYSTEM_UI_FLAG_XXX

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值