天天看點

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