Viewペイントシステム知識整理(2)-setContentViewソース解析
11706 ワード
一、概説 Activity
では、レイアウトを初期化するためにsetContentView
メソッドを呼び出すのが一般的です.
二、ContentViewに関する方法 Activity
では、ContentView
に関連する関数は次のとおりです.まず、それらの注釈の説明を見てみましょう. /**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
/**
* Set the activity content to an explicit view. This view is placed
* directly into the activity's view hierarchy. It can itself be a complex
* view hierarchy. When calling this method, the layout parameters of the
* specified view are ignored. Both the width and the height of the view are
* set by default to {@link ViewGroup.LayoutParams#MATCH_PARENT}. To use
* your own layout parameters, invoke
* {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
* instead.
*
* @param view The desired content to display.
*
* @see #setContentView(int)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
/**
* Set the activity content to an explicit view. This view is placed
* directly into the activity's view hierarchy. It can itself be a complex
* view hierarchy.
*
* @param view The desired content to display.
* @param params Layout parameters for the view.
*
* @see #setContentView(android.view.View)
* @see #setContentView(int)
*/
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
/**
* Add an additional content view to the activity. Added after any existing
* ones in the activity -- existing views are NOT removed.
*
* @param view The desired content to display.
* @param params Layout parameters for the view.
*/
public void addContentView(View view, ViewGroup.LayoutParams params) {
getWindow().addContentView(view, params);
initWindowDecorActionBar();
}
上記の注記から、この4つの方法の用途がわかります.
Activity
では、ContentView
に関連する関数は次のとおりです.まず、それらの注釈の説明を見てみましょう. /**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
/**
* Set the activity content to an explicit view. This view is placed
* directly into the activity's view hierarchy. It can itself be a complex
* view hierarchy. When calling this method, the layout parameters of the
* specified view are ignored. Both the width and the height of the view are
* set by default to {@link ViewGroup.LayoutParams#MATCH_PARENT}. To use
* your own layout parameters, invoke
* {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
* instead.
*
* @param view The desired content to display.
*
* @see #setContentView(int)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
/**
* Set the activity content to an explicit view. This view is placed
* directly into the activity's view hierarchy. It can itself be a complex
* view hierarchy.
*
* @param view The desired content to display.
* @param params Layout parameters for the view.
*
* @see #setContentView(android.view.View)
* @see #setContentView(int)
*/
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
/**
* Add an additional content view to the activity. Added after any existing
* ones in the activity -- existing views are NOT removed.
*
* @param view The desired content to display.
* @param params Layout parameters for the view.
*/
public void addContentView(View view, ViewGroup.LayoutParams params) {
getWindow().addContentView(view, params);
initWindowDecorActionBar();
}
上記の注記から、この4つの方法の用途がわかります.
layouResId
のトップレベルactivity
に追加します.View
をView
のレイアウトに追加します.デフォルトの幅はactivity
です.ViewGroup.LayoutParams#MATCH_PARENT
が指定されています.LayoutParams
を指定する必要があります.既存のLayoutParams
は削除されません.これらの4つの方法はいずれも
View
を呼び出した方法であり、ソースコードによってPhoneWindow.java
とsetContentView(View view, ViewGroup.LayoutParams params)
のステップは基本的に同じであることが分かるが、レイアウトに追加する際、前者はsetContentView(@LayoutRes int layoutResID)
の例を得たため、View
の方法を用い、後者は先にaddView
を用いる必要があるため、inflate
を用いる.三、LayoutInflater方法
次に、setContentView
を例に、具体的な実装手順を見てみましょう.
3.1 setContentView(@LayoutRes int layoutResID)
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
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;
}
まず、setContentView
が空であるかどうかを判断します.追加されたコードから、このmContentParent
は実はmContentParent
が最後にレンダリングされたレイアウトに対応する親コンテナであり、このlayoutResId
が空である場合、ContentParent
が呼び出され、installDecor
が中で初期化されていることがわかります.
3.2 mContentParent
private void installDecor() {
// DecorView , , FrameLayout。
if (mDecor == null) {
mDecor = generateDecor();
}
// `ContentParent` , , `DecorView`
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
}
}
}
installDecor()
はmDecor
であり、FrameLayout
との関係はmContentParent
によって生成されることが分かる.
3.3 mContentParent = generateLayout(mDecor)
protected ViewGroup generateLayout(DecorView decor) {
//... , `layoutResource` .
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);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//...
return contentParent;
}
上記の値を割り当てる過程で、私たちは主に以下のいくつかの変数に注目します.generateLayout(DecorView decor)
:
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
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;
}
private void installDecor() {
// DecorView , , FrameLayout。
if (mDecor == null) {
mDecor = generateDecor();
}
// `ContentParent` , , `DecorView`
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
}
}
}
protected ViewGroup generateLayout(DecorView decor) {
//... , `layoutResource` .
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);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//...
return contentParent;
}
mContentRoot/mContentParent/mDecorContent
は必ずmContentRoot
の次のサブコンテナです.mDecor
はmContentParent
のうちmDecor
はid
のR.id.content
であるが、ViewGroup
との具体的な階層関係は不確定であり、mDecor
がどのmContentRoot
でレンダリングされるかに依存する.xml
は必ず入ってきたmContentParent
が行ってlayoutResId
が完成した後の親容器で、それはきっと空ではありません.そうしないと異常を投げ出します.私たちinflate
方法が入ってきたレイアウトは、その子setContentView(xxx)
です. // This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
// 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.
private ViewGroup mContentParent;
View
はmDecorContent
のうちmDecor
はid
のdecor_content_parent
であるが、ViewGroup
の中にこのmDecor
のid
がない可能性もあり、これは私たちのView
とどのmContentRoot
を使用してxml
を使用したのかに依存する必要がある.さらに前の
inflate
の場所に戻って、下を見続け、setContentView
が空でない場合、その下のすべてのサブmContentParent
が除去されます.その後、View
メソッドが呼び出され、受信したmLayoutInflater.inflate(layoutResID, mContentParent);
がView
に追加され、最後にリスニングがコールバックされます.final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
3.4 mContentParentフラグビット
mContentParentExplicitlySet
の最後に、setContentView
という変数をmContentParentExplicitlySet
に設定します.この変数は実はtrue
に使用されます.つまり、requestFeature
を呼び出す前にsetContentView
を呼び出さなければなりません.そうしないと、次の異常が放出されます. @Override
public boolean requestFeature(int featureId) {
if (mContentParentExplicitlySet) {
throw new AndroidRuntimeException("requestFeature() must be called before adding content");
}
return super.requestFeature(featureId);
}
したがって、
requestFeature
は、requestFeature(xxx)
を呼び出す前に呼び出さなければならない.三、setContentView(xxx)
次に、
addContentView(View view, ViewGroup.LayoutParams params)
メソッドを見てみましょう. @Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
}
mContentParent.addView(view, params);
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
これと
addContentView
メソッドの違いは、set
に追加する前に、mContentParent
のすべてのサブmContentParent
を削除するのではなく、それを直接追加することであり、レイアウト分析ソフトウェアを通じて、View
のタイプはmContentParent
であり、実際にはContentFrameLayout
であるため、FrameLayout
の既存のサブmContentParent
に上書きされることがわかります.四、追加したレイアウトとViewのActivityを関連付ける
上記の解析では、
Window
を初期化し、設定されたDecorView
属性に基づいて、入力されたStyle
に基づいてサブレイアウトを初期化しただけであるが、この場合にはContentView
のActivity
と本当に関連付けられ、関連付けられた場所はWindow
にある.final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
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;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
} else if (!willBeVisible) {
if (localLOGV) Slog.v(
TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
} else {
}
}
ソースコードから分かるように、
ActivityThread.java
が実行されたときに、以前handleResumeActivity
がDecorView
に追加されなかった場合、その最初の追加はWindowManager
メソッドが実行された後に追加される.