1 從我們自己新建的一個Activity的setContentView 就開始
public class ActivityTest extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
↓
↓ setContentView(R.layout.activity_test);
↓
public class AppCompatActivity extends ... {
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
↓
↓ getDelegate().setContentView(layoutResID);先找getDelegate()
↓ getDelegate()也在AppCompatActivity 中
↓
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
}
↓
↓ getDelegate() = AppCompatDelegate.create(this, this);
↓
public abstract class AppCompatDelegate {
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}
}
所以最后呼叫的是
2 AppCompatDelegateImpl 中的 setContentView 方法:
class AppCompatDelegateImpl extends ...{
public void setContentView(int resId) {
this.ensureSubDecor();
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
contentParent.removeAllViews();
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
this.mOriginalWindowCallback.onContentChanged();
}
}
接著去看 ensureSubDecor 方法:
2.1 ensureSubDecor
private void ensureSubDecor() {
if (!this.mSubDecorInstalled) {
this.mSubDecor = this.createSubDecor();//核心代碼
//省略其它代碼,,,
}
}
2.2 createSubDecor
private ViewGroup createSubDecor() {
?這里是獲取Activity的theme,并根據主題風格為subDecor確定加載哪個布局
TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
if (!a.hasValue(styleable.AppCompatTheme_windowActionBar)) {
a.recycle();
throw new IllegalStateException("You need to use a Theme.AppCompat theme (or descendant) with this activity.");
} else {
this.mIsFloating = a.getBoolean(styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();
this.mWindow.getDecorView(); ---> ???step1 創建DecirView
LayoutInflater inflater = LayoutInflater.from(this.mContext);
ViewGroup subDecor = null; ???Step2 開始
if (!this.mWindowNoTitle) {
if (this.mIsFloating) {
subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null);
} else if (this.mHasActionBar) {
subDecor = (ViewGroup)LayoutInflater.from((Context)themedContext).inflate(layout.abc_screen_toolbar, (ViewGroup)null);
}
} else {
if (this.mOverlayActionMode) {
subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);
} else {
subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);
}
//其它代碼省略...
}
? 通過上面subDecor = inflate(布局檔案)可以看出,subDecor 的布局檔案是下面四種布局檔案之一:
1 abc_dialog_title_material
2 abc_screen_toolbar
3 abc_screen_simple_overlay_action_mode
4 abc_screen_simple
if (subDecor == null) {
? 如果subDecor為空就拋出例外,這個例外看起來是不是很熟悉
throw new IllegalArgumentException("AppCompat does not support the current theme features: { windowActionBar: " + this.mHasActionBar + ", windowActionBarOverlay: " + this.mOverlayActionBar + ", android:windowIsFloating: " + this.mIsFloating + ", windowActionModeOverlay: " + this.mOverlayActionMode + ", windowNoTitle: " + this.mWindowNoTitle + " }");
} else {
if (this.mDecorContentParent == null) {
this.mTitleView = (TextView)subDecor.findViewById(id.title);
}
ViewUtils.makeOptionalFitsSystemWindows(subDecor);
//? 上面說了 subDecor 是四個布局檔案中的一個創建,
// ?每個布局檔案都有一action_bar_activity_content
ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);
if (windowContentView != null) {
while(windowContentView.getChildCount() > 0) {
View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
windowContentView.setId(-1);
contentView.setId(16908290);--->???Step2 結束 contentView的id被設定成16908290
if (windowContentView instanceof FrameLayout) {
((FrameLayout)windowContentView).setForeground((Drawable)null);
}
}
this.mWindow.setContentView(subDecor);---> ???Step3 把subDecor加載到DecorView布局檔案中的容器中
contentView.setAttachListener(new OnAttachListener() {
public void onAttachedFromWindow() {
}
public void onDetachedFromWindow() {
AppCompatDelegateImpl.this.dismissPopups();
}
});
return subDecor; ? 把subDecor回傳賦值給mSubDecor
}
}
}
總結一下createSubDecor方法,一共做了三件事:
1 this.mWindow.getDecorView(); 創建Decorview,并為它加載一個布局檔案,找到這個布局檔案中 R.id.content 的容器,賦值給 mContentParent,
這樣我們就準備好了一個DecorView和其布局中id為R.id.content 的容器,
2 給ViewGroup subDecor根據主題、style選擇合適布局檔案并加載到subDecor中:
ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(R.id.content);? 這里就是上一步里面那個布局檔案的R.id.content 容器
windowContentView.setId(View.NO_ID); ? 把windowContentView的id設定為View.NO_ID 即 -1
contentView.setId(android.R.id.content); ? 把contentView 的id設定為R.id.content
這樣我們準備好了subDecor和其布局中 id為action_bar_activity_content的容器,并把這個容器的id改成 R.id.content
3 this.mWindow.setContentView(subDecor); 將第2步的subDecor添加到 第1步準備好的DecorView的容器mContentParent中,
下面分析這三步驟
Step1 this.mWindow.getDecorView(); this.mWindow 這個是啥呢?看下面
AppCompatDelegateImpl(Context context, Window window, AppCompatCallback callback) {
......
mWindow 的初始化是在AppCompatDelegateImpl建構式里
↓
this.mWindow = window;
.....
}
想要知道mWindow是啥就要找到AppCompatDelegateImpl(context,window,callback)這個
建構式初始化的時候傳入的window是啥
還記得最開始我們從setContentView點進來的代碼么
↓
↓
↓
***********************************************************************
public class ActivityTest extends AppCompatActivity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
↓
↓ setContentView(R.layout.activity_test);
↓
public class AppCompatActivity extends ... {
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
↓
↓ getDelegate().setContentView(layoutResID);先找getDelegate()
↓ getDelegate()也在AppCompatActivity 中
↓
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
}
↓
↓ getDelegate() = AppCompatDelegate.create(this, this);
↓
public abstract class AppCompatDelegate {
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
在這里初始化的,activity就是AppCompatActivity ,window就是activity.getWindow()
return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}
}
***************************************************************************************
window就是AppCompatActivity.getWindow(),但是AppCompatActivity中沒有getWindow()方法,
getWindow()是在其父類Activity中實作
public class Activity extends ... ... {
private Window mWindow;
final void attach(Context context, ......) {
attachBaseContext(context);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
......
}
public @Nullable Window getWindow() {
return mWindow;
}
}
最后找到window就是PhoneWindow物件,
那就到里PhoneWindow面看一下 getDecorView()是如何實作的
PhoneWindow.java:
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor(); ---> ?呼叫installDecor()
}
return mDecor;
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1); ---> ? generateDecor就是創建DecorView物件
....省略其它代碼....
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor); ---> ? 給DecorView物件加載布局檔案
并準備好R.id.content容器
....省略其它代碼.....
}
}
先來看 mDecor = generateDecor(-1); generateDecor方法很簡單就是創建了一個DecorView物件并回傳賦值給 mDecor
protected DecorView generateDecor(int featureId) {
...其它代碼省略...
return new DecorView(context, featureId, this, getAttributes());
}
接著看 mContentParent = generateLayout(mDecor);
1:mContentParent是一個ViewGroup;
2 :generateLayout(mDecor);將上面剛剛創建的DecorView作為引數傳了進去
protected ViewGroup generateLayout(DecorView decor) {
? 獲取windowStyle,給DecorView加載哪種布局就是根據這個來判斷的!
TypedArray a = getWindowStyle();
...其它代碼省略...
// Inflate the window decor.
? 這里精簡保留的,layoutResource = R.layout.布局檔案
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
if (mIsFloating) {
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
if (mIsFloating) {
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;
}
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
? layoutResource就是上面根據WindowStyle決定的布局檔案之中的一個,
然后加載到mDecor即DecorView中
↓
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
? 而上面布局檔案中它們都有一個id是content的
? 而且這個findViewById是在PhoneWindow直接使用?它是Activity?文章最后分析
↓
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
return contentParent; ← ?回傳DecorView的布局檔案中id為content的控制元件
}
到這里 step1 this.mWindow.getDecorView() 就完成了,PhoneWindow新建了一個DecorView并為其加載好布局檔案,并將布局檔案中R.id.content容器準備好,
Step2 具體分析已在上面代碼中注釋
step3 this.mWindow.setContentView(subDecor)
接著我們就來看看3 this.mWindow.setContentView(subDecor);同樣是this.mWindow我們就到PhoneWindow中找setContentView方法
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
?還記得 step1 的時候準備好的mContentParent,現在就是把subDecor加載到其中
↓
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
mContentParent.addView(view, params);
|--- mContentParent 就是上面DecorView中加載布局檔案的 content 控制元件
|--- view 就是我們傳遞進來的subDecor
到這里Step3 就結束了,
再來看最開始的setContentView
public void setContentView(int resId) {
this.ensureSubDecor(); ? 我們上面這么久的呼叫就是走完這一行代碼,想不到吧,
?找到id為R.id.content的控制元件,即subDecor布局中的控制元件
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(R.id.content);
contentParent.removeAllViews();
?把我們的布局檔案加載到R.id.content的控制元件中即subDecor布局中
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
this.mOriginalWindowCallback.onContentChanged();
}
我們自己的布局檔案被加載到了subDecor布局中id為 action_bar_activity_content的 ContentFrameLayout中,
而subDecor被加載到了DecorView布局中id為content的FrameLayout中;
DecorView是由PhoneWindow 創建
PhoneWindow 在Activity attach方法中初始化,
這樣整個加載程序就串起來了,
是時候放上這樣一張圖了

DecorView加載的xml檔案為下面中的一個:
screen_swipe_dismiss
screen_title_icons
screen_progress
screen_custom_title
screen_action_bar
screen_title
screen_simple_overlay_action_mode
screen_simple
而他們都有一個id為content的FrameLayout控制元件用來加載subDcor.
subDcor加載的xml檔案為下面中的一個:
abc_dialog_title_material
abc_screen_toolbar
abc_screen_simple_overlay_action_mode
abc_screen_simple
而他們都有一個id為action_bar_activity_content 的 ContentFrameLayout 控制元件用來加載我們自定義的布局檔案,就是我們創建Activity時候創建的xml檔案,
上面還有一個問題PhoneWindow為啥可以直接findViewById? 點進去就會發現是在其父類Window中實作的
public abstract class Window {
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
public abstract View getDecorView();
}
getDecorView的具體實作又回到了PhoneWindow 中;
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
這個方法看起來是不是很熟悉,就是我們初始化DecorView物件的方法,回傳的就是我們初始化并加載了布局的DecorView.
所以PhoneWindow進行findViewById其實就是對其持有的DecorView進行操作,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/254088.html
標籤:其他
