天天看点

Android View 系统 2 - View树与Window关联View树与Window关联

View树与Window关联

前面介绍的是View树的创建,但是创建好的View树只有添加到一个Window里才会显示到屏幕上,因为

WindowManagerService

服务会为每一个Window创建一块Surface作为画布,View树里所有的View都会绘画到这块Surface上,最后

SurfaceFlinger

服务会将所有Window创建的的Surface传递给GPU,最终显示到屏幕上。

创建Window

通过

WindowManager.addView

接口就可以申请创建一个新的Window并添加一个View树,弹出菜单、浮动窗口等自定义的窗口都是通过这个接口显示出来的:

//获得WindowManager服务
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

//设置Window参数
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.setTitle("FloatWindow");

//创建View树
View rootView = getLayoutInflater().inflate(R.layout.float_layout, null);

//创建Window并关联View树
windowManager.addView(rootView, params);
           

应用中使用的

WindowManager

其实是一个

WindowManagerImpl

类的实例:

frameworks/base/core/java/android/view/WindowManagerImpl.java

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;
    ...
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }
}
           

如上面的代码,

WindowManagerImpl.addView()

实现会调用

WindowManagerGlobal.addView()

,实际的请求创建Window的逻辑也在这个类里实现:

frameworks/base/core/java/android/view/WindowManagerGlobal.java

public final class WindowManagerGlobal {
    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>();
    ...
    public void addView(View view, ViewGroup.LayoutParams params,
                        Display display, Window parentWindow) {
        ...
        ViewRootImpl root;

        synchronized (mLock) {
            ...
            root = new ViewRootImpl(view.getContext(), display);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(params);
            try {
                root.setView(view, params, panelParentView);
            } catch (RuntimeException e) {
                throw e;
            }
        }
    }
}
           

WindowManagerGlobal

类里有这三个

ArrayList

列表:

  • mViews

    保存着每个Window的根View
  • mRoots

    保存每个根View对应的

    ViewRootImpl

  • mParams

    保存的是每个Window的窗口配置参数

    WindowManager.LayoutParams

WindowManagerGlobal.addView()

的实现里会为每一个Window创建一个

ViewRootImpl

实例,以及窗口配置参数

WindowManager.LayoutParams

实例,并将传递过来的根View实例保存在上面提到的三个列表里。

addView()

实现里最后调用的

root.setView()

就是通过

ViewRootImpl

将Window与View树关联起来,同时

ViewRootImpl

还有控制View树的刷新、显示以及输入事件分发的作用。

创建Activity主界面

Android 系统中的Activity组件也是通过View树与Window显示出来的,只是将创建View树与Window的逻辑封装到了生命周期的处理流程里了。

所有Activity的窗口都是用一个

PhoneWindow

类的实例表示的,每个

PhoneWindow

都会创建一个内部类

DecorView

作为这个窗口中View树的根View。下面的结构图表示的就是

Activity

PhoneWindow

以及

DecorView

之间的关系,下面我们通过这个结构图结合相关代码来研究一下Activity主界面的View树的创建过程。

Android View 系统 2 - View树与Window关联View树与Window关联
  • 应用里Activity一般会在

    onCreate

    里通过调用

    setContentView()

    设置自己主界面的View树布局,如下面的代码:
public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
           

setContentView()

有多个重载实现,既可以传递布局资源ID也可以直接传递创建好的View。

Activity

里会将

setContentView()

设置过来的View传递给

PhoneWindow

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

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

getWindow()返回的就是PhoneWindow的实例。

  • PhoneWindow里的setContentView实现如下面的代码:
frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java

private DecorView mDecor;
private ViewGroup mContentParent;
public void setContentView(int layoutResID) {
    ...
    if (mContentParent == null) {
        installDecor();
    } else {
        mContentParent.removeAllViews();
    }
    mLayoutInflater.inflate(layoutResID, mContentParent);
    ...
}
           

mContentParent

是用作为

ContentView

的父View的,

setContentView()

设置过来的Layout 资源ID通过

LayoutInflater

解析后会被添加到

mContentParent

的子View树里

  • 如果这个Activity是第一次启动,那么

    mContentParent

    应该是

    null

    的,需要在

    installDecor()

    里初始化所有的装饰View:
private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor();
        ...
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
        ...
    }
}
           
  • generateDecor()

    的作用是创建出

    DecorView

    的实例,如下面:
protected DecorView generateDecor() {
    ...
    return new DecorView(getContext(), -);
}
           
  • generateLayout()

    的作用是根据Activity设置的各种feature、Theme创建

    ContentParent

    ActionBar

    等Activity的基础视图框架,如下面:
protected ViewGroup generateLayout(DecorView decor) {
    ...
    // Inflate the window decor.
    int layoutResource;
    ...
    mDecor.startChanging();

    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;

    ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
    ...
    mDecor.finishChanging();

    return contentParent;
}
           

如果

Activity

设置了需要

ActionBar

,那么就会加载有

ActionBar

的布局资源:

if ((features & ( << FEATURE_ACTION_BAR)) != ) {
    layoutResource = R.layout.screen_action_bar;
} else {
    layoutResource = R.layout.screen_title;
}
           

ActionBar

的界面会使用

R.layout.screen_action_bar

布局资源,没有

ActionBar

会使用

R.layout.screen_title

(5.0引入Material主题后有新引入了一个

R.layout.screen_toolbar

):

frameworks/base/core/res/res/layout/screen_action_bar.xml

<com.android.internal.widget.ActionBarOverlayLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/decor_content_parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:splitMotionEvents="false"
    android:theme="?attr/actionBarTheme">
    <FrameLayout android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
    <com.android.internal.widget.ActionBarContainer
        android:id="@+id/action_bar_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        style="?attr/actionBarStyle"
        android:transitionName="android:action_bar"
        android:touchscreenBlocksFocus="true"
        android:gravity="top">
        ......
</com.android.internal.widget.ActionBarContainer>
......
</com.android.internal.widget.ActionBarOverlayLayout>
           

contentParent

会取其中ID为

R.id.content

的View,应用的Activity里设置的ContentView会被添加到

contentParent

的子View里

上面是Activity的View树的创建过程,而Activity的Window需要等到Activity resume的时候才会创建,同样也是调用

WindowManager.addView

接口,而根View就是前面创建的

DecorView

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

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume) {
    ...
    ActivityClientRecord r = performResumeActivity(token, clearHide);

    if (r != null) {
        final Activity a = r.activity;
        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            ...
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            }
        }
    } 
}