一呢岗、繼承 Activity 的流程
1.1请敦、PhoneWindow 的創(chuàng)建
首先ActivityThread
的performLaunchActivity
方法中進(jìn)行activity的attach
進(jìn)行參數(shù)綁定
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo; //獲取AcitivityInfo對象
if (r.packageInfo == null) { //獲取APK文件的描述類LoadeApk
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
ComponentName component = r.intent.getComponent(); //創(chuàng)建要啟動Activity的上下文環(huán)境
if (component == null) {
component = r.intent.resolveActivity(
mInitialApplication.getPackageManager());
r.intent.setComponent(component);
}
if (r.activityInfo.targetActivity != null) {
component = new ComponentName(r.activityInfo.packageName,
r.activityInfo.targetActivity);
}
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader(); //用類加載器來創(chuàng)建該Activity的實例
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);//創(chuàng)建Application
if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
if (localLOGV) Slog.v(
TAG, r + ": app=" + app
+ ", appName=" + app.getPackageName()
+ ", pkg=" + r.packageInfo.getPackageName()
+ ", comp=" + r.intent.getComponent().toShortString()
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (r.overrideConfig != null) {
config.updateFrom(r.overrideConfig);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
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); //初始化Activity
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
checkAndBlockForNetworkAccess();
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); //調(diào)用Activity的onCreate
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
r.activity = activity;
}
r.setState(ON_CREATE);
synchronized (mResourcesManager) {
mActivities.put(r.token, r);
}
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to start activity " + component
+ ": " + e.toString(), e);
}
}
return activity;
}
我們再看Activity#attach
中具體做了些什么事贸营。
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
//創(chuàng)建抽象類 window 的實現(xiàn)類 PhoneWindow贾漏,對View進(jìn)行管理
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this); //設(shè)置回調(diào),向 Activity 分發(fā)點擊或狀態(tài)改變等事件
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mAssistToken = assistToken;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
//給 Window 設(shè)置 WindowManager 對象
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
mWindow.setPreferMinimalPostProcessing(
(info.flags & ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING) != 0);
setAutofillOptions(application.getAutofillOptions());
setContentCaptureOptions(application.getContentCaptureOptions());
}
1.2具伍、setContentView 流程
首先進(jìn)入Activity#setContentView
方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
上面通過getWindow()
獲取Window
的實現(xiàn)類PhoneWindow
//在attach方法里面通過 PhoneWindow 創(chuàng)建
private Window mWindow;
public Window getWindow() {
return mWindow;
}
然后調(diào)用PhoneWindow#setContentView
進(jìn)行渲染視圖
private DecorView mDecor;
ViewGroup mContentParent;
主要目的是創(chuàng)建 DecorView
拿到 Content
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//創(chuàng)建 DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//移除 ViewGroup 中所有的 View
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//R.layout.xxx 渲染到 mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
1.2.1翅雏、創(chuàng)建 DecorView
private void installDecor() {
mForceDecorInstall = false;
//當(dāng)mDecor為空時創(chuàng)建 DecorView
if (mDecor == null) {
//創(chuàng)建 DecorView
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//如果 mContentParent 為空時,用系統(tǒng)提供的layout初始化
mContentParent = generateLayout(mDecor);
...
}
}
}
當(dāng)mDecor == null
時人芽,調(diào)用PhoneWindow#generateDecor
進(jìn)行初始化
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, this);
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
//創(chuàng)建 DecorView
return new DecorView(context, featureId, this, getAttributes());
}
接下來看generateLayout
里面怎么初始化 mContentParent
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
protected ViewGroup generateLayout(DecorView decor) {
//設(shè)置主題
TypedArray a = getWindowStyle();
....
//系統(tǒng)提供的xml文件
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
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) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
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) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
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;
}
// 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(FrameLayout)里面
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource););
//獲取 contentParent (FrameLayout)
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
.....
return contentParent;
}
再看DecorView
的onResourcesLoaded
方法望几,主要初始化根布局
ViewGroup mContentRoot;
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
//獲取布局的 View
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
1.2.2、渲染到 mContentParent
執(zhí)行LayoutInflater
里的inflate
方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
//resource: 要加載的XML布局資源的ID
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
//獲取Resources萤厅, Resources是安卓的資源管理類
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
//通過反射獲取編譯后 View
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
//解析xml
XmlResourceParser parser = res.getLayout(resource);
try {
//添加到 root (mContentParent) 跟布局中
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
boolean attachToRoot) {
if (!mUseCompiledView) {
return null;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");
// Try to inflate using a precompiled layout.
String pkg = res.getResourcePackageName(resource);
String layout = res.getResourceEntryName(resource);
try {
Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
Method inflater = clazz.getMethod(layout, Context.class, int.class);
View view = (View) inflater.invoke(null, mContext, resource);
if (view != null && root != null) {
// We were able to use the precompiled inflater, but now we need to do some work to
// attach the view to the root correctly.
XmlResourceParser parser = res.getLayout(resource);
try {
AttributeSet attrs = Xml.asAttributeSet(parser);
advanceToRootNode(parser);
ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
if (attachToRoot) {
root.addView(view, params);
} else {
view.setLayoutParams(params);
}
} finally {
parser.close();
}
}
return view;
} catch (Throwable e) {
if (DEBUG) {
Log.e(TAG, "Failed to use precompiled view", e);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return null;
}
二橄抹、繼承 AppCompatActivity 的流程
進(jìn)入AppCompatActivity#setContentView
方法
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
getDelegate()
獲取AppCompatDelegate
的實現(xiàn)類,具體相當(dāng)于代理的作用
private AppCompatDelegate mDelegate;
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
最終會調(diào)用AppCompatDelegate
的實現(xiàn)類AppCompatDelegateImpl
里面的setContentView
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
public void setContentView(int resId) {
//步驟一
// 創(chuàng)建并初始化DecorView
ensureSubDecor();
// 找到subDecor中的content id
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
// 移除content中的所有view
contentParent.removeAllViews();
//步驟二
// 通過LayoutInflater加載用戶定義的布局惕味,內(nèi)部實現(xiàn)是通過獲得類名反射出每一個View楼誓,我們可以通過setFactory2()接管創(chuàng)建View的過程來自定義LayoutInflater
LayoutInflater.from(mContext).inflate(resId, contentParent);
// 內(nèi)容改變回調(diào)
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
@Override
public void setContentView(View v, ViewGroup.LayoutParams lp) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v, lp);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
@Override
public void addContentView(View v, ViewGroup.LayoutParams lp) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.addView(v, lp);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
createSubDecor
方法, 根據(jù)應(yīng)用的樣式主題(Theme)設(shè)置根布局DecorView的樣式, 并執(zhí)行初始化. 當(dāng)未含有AppCompatTheme_windowActionBar
屬性時, 則認(rèn)為主題未設(shè)置, 并拋出異常IllegalStateException.
2.1、步驟一
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
//創(chuàng)建mSubDecor
mSubDecor = createSubDecor();
// If a title was set before we installed the decor, propagate it now
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
if (mDecorContentParent != null) {
mDecorContentParent.setWindowTitle(title);
} else if (peekSupportActionBar() != null) {
peekSupportActionBar().setWindowTitle(title);
} else if (mTitleView != null) {
mTitleView.setText(title);
}
}
//窗口大小設(shè)置
applyFixedSizeWindow();
onSubDecorInstalled(mSubDecor);
mSubDecorInstalled = true; //是否初始化的標(biāo)志位
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!mIsDestroyed && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}
private ViewGroup createSubDecor(){
//設(shè)置屬性主題
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
//這個錯誤是不是曾經(jīng)見到過名挥,如果用的Theme不是AppCompatTheme的就會報錯
if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
a.recycle();
throw new IllegalStateException(
"You need to use a Theme.AppCompat theme (or descendant) with this activity.");
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
}
mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();
//1慌随、從Activity 拿 PhoneWindow
ensureWindow();
// 2、獲取DecorView
mWindow.getDecorView();
...
// 通過LayoutInflater inflate subDecor的視圖躺同,這里有好幾種,我挑了其中一個繼續(xù)分析
LayoutInflater inflater = LayoutInflater.from(mContext);
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_dialog_title_material, null);
//我們得到了DecorView丸逸,而DecorView里面已經(jīng)有了content內(nèi)容蹋艺,現(xiàn)在這里新建了subDecor,所以需要把content的視圖migrate黄刚,之后再重新設(shè)置 content id
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(R.id.action_bar_activity_content);
....
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
// 也許已經(jīng)有Views加載到windowContentView中捎谨,需要把這些視圖重新添加到contentView中,并設(shè)置id
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
//將原始的 content id 置為 NO_ID
windowContentView.setId(View.NO_ID);
//subDecerView R.id.action_bar_activity_content --> 置為 content
contentView.setId(android.R.id.content);
}
// 用subDecor填充Window的內(nèi)容視圖
mWindow.setContentView(subDecor);
contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
@Override
public void onAttachedFromWindow() {}
@Override
public void onDetachedFromWindow() {
dismissPopups();
}
});
return subDecor;
}
- 1
private void ensureWindow() {
// We lazily fetch the Window for Activities, to allow DayNight to apply in
// attachBaseContext
if (mWindow == null && mHost instanceof Activity) {
attachToWindow(((Activity) mHost).getWindow());
}
if (mWindow == null) {
throw new IllegalStateException("We have not been given a Window");
}
}
- 2
執(zhí)行PhoneWindow#getDecorView
方法
public final @NonNull View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
//初始化 DecorView
installDecor();
}
return mDecor;
}
下面具體流程Activity的流程
private void installDecor() {
...
}
窗口大小設(shè)置
private void applyFixedSizeWindow() {
//加載布局
ContentFrameLayout cfl = (ContentFrameLayout) mSubDecor.findViewById(android.R.id.content);
//獲取 DecorView
final View windowDecor = mWindow.getDecorView();
//設(shè)置ContentFrameLayout 布局的間距
cfl.setDecorPadding(windowDecor.getPaddingLeft(),
windowDecor.getPaddingTop(), windowDecor.getPaddingRight(),
windowDecor.getPaddingBottom());
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor());
a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor());
if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) {
a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor,
cfl.getFixedWidthMajor());
}
if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) {
a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor,
cfl.getFixedWidthMinor());
}
if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) {
a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor,
cfl.getFixedHeightMajor());
}
if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) {
a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor,
cfl.getFixedHeightMinor());
}
a.recycle();
cfl.requestLayout();
}
通過我們設(shè)置的style選出一個布局文件,這一步好像在installDecor中已經(jīng)做過了涛救,這樣好像重復(fù)了畏邢,為什么有這樣的一個重復(fù)?有這樣一個重復(fù)是為了不影響原來的代碼的同時,把一部分對style處理的邏輯轉(zhuǎn)移到AppCompatDelegateImpl中检吆,例如對windowTitle的隱藏與顯示舒萎,這里可看出來設(shè)計師的設(shè)計,通過下面的學(xué)習(xí)慢慢體會蹭沛,先看一下這個布局文件:
<androidx.appcompat.widget.FitWindowsLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/action_bar_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
<androidx.appcompat.widget.ViewStubCompat
android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/abc_action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include layout="@layout/abc_screen_content_include" />
</androidx.appcompat.widget.FitWindowsLinearLayout>
abc_screen_content_include的布局文件:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.appcompat.widget.ContentFrameLayout
android:id="@id/action_bar_activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</merge>
把這個布局文件inflate成view賦值給subDecor臂寝,subDecor是一個ViewGroup:
2.2、步驟二摊灭, View 創(chuàng)建
執(zhí)行LayoutInflater#inflate
方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
標(biāo)簽
private static final String TAG_MERGE = "merge";
private static final String TAG_INCLUDE = "include";
private static final String TAG_1995 = "blink";
private static final String TAG_REQUEST_FOCUS = "requestFocus";
private static final String TAG_TAG = "tag";
初始化布局
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
...
if (TAG_MERGE.equals(name)) {
//root == null || attachToRoot == true 拋出異常
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 通過反射創(chuàng)建View --- 布局的rootView
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// 從 xml 中解析我們布局中根布局設(shè)值得大小
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// 設(shè)置View在父布局下Params
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// 遍歷子節(jié)點
rInflateChildren(parser, temp, attrs, true);
//如果Root存在并且attachToRoot為true咆贬,即與父View綁定
//這里在解析的同時,就會將其添加至父View上
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//如果父Viewwe為null或者沒有綁定父View都會將當(dāng)前解析的View返回帚呼,大小有自己確定
if (root == null || !attachToRoot) {
result = temp;
}
}
}
return result;
}
}
- 總結(jié):由上可的分為三種情況
LayoutInflate的參數(shù)的作用
// 方式一:將布局添加成功
View view = inflater.inflate(R.layout.inflate_layout, ll, true);
// 方式二:報錯掏缎,一個View只能有一個父親(The specified child already has a parent.)
View view = inflater.inflate(R.layout.inflate_layout, ll, true); // 已經(jīng)addView
ll.addView(view);
// 方式三:布局成功,第三個參數(shù)為false
// 目的:想要 inflate_layout 的根節(jié)點的屬性(寬高)有效煤杀,又不想讓其處于某一個容器中
View view = inflater.inflate(R.layout.inflate_layout, ll, false);
ll.addView(view);
// 方式四:root = null眷蜈,這個時候不管第三個參數(shù)是什么,顯示效果一樣
// inflate_layout 的根節(jié)點的屬性(寬高)設(shè)置無效怜珍,只是包裹子View端蛆,
// 但是子View(Button)有效,因為Button是出于容器下的
View view = inflater.inflate(R.layout.inflate_layout, null, false);
ll.addView(view);
由上面可得當(dāng)父布局 root != null酥泛,的時候我們設(shè)置的布局才有效
調(diào)用createViewFromTag
方法創(chuàng)建 View
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
try {
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//(1)今豆、是否是sdk里面的控件,如:LinearLayout
view = onCreateView(context, parent, name, attrs);
} else {
//(2)柔袁、自定義View
//如:name = androidx.constraintlayout.widget.ConstraintLayout
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
...
}
- (1)
會執(zhí)行PhoneLayoutInflater
里面的onCreateView
方法
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
接下來執(zhí)行LayoutInflater#createView
方法
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
// 具體實現(xiàn)見下:
}
- (2)
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Objects.requireNonNull(viewContext);
Objects.requireNonNull(name);
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
//獲取全類名呆躲,通過反射創(chuàng)建 View 對象
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
//獲取構(gòu)造方法
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
}
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
//執(zhí)行構(gòu)造方法,創(chuàng)建 View
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
//返回構(gòu)造的view
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
...
}
}
接下來看inflate
中調(diào)用的rInflateChildren
方法
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
// include 不能作為根結(jié)點
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
// merge 只能作為根結(jié)點
throw new InflateException("<merge /> must be the root element");
} else {
//創(chuàng)建 View
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
上面方法所用到的標(biāo)簽
//優(yōu)化布局捶索,必須作為rootView
private static final String TAG_MERGE = "merge";
//不能作為 root_View
private static final String TAG_INCLUDE = "include";
private static final String TAG_1995 = "blink";
private static final String TAG_REQUEST_FOCUS = "requestFocus";
private static final String TAG_TAG = "tag";
private static final String ATTR_LAYOUT = "layout";
- include 布局
private void parseInclude(XmlPullParser parser, Context context, View parent,
AttributeSet attrs) throws XmlPullParserException, IOException {
int type;
//判斷 Include標(biāo)簽是否在 ViewGroup容器之內(nèi)插掂,因為 include 標(biāo)簽只能存在于 ViewGroup 容器之內(nèi)
if (!(parent instanceof ViewGroup)) {
throw new InflateException("<include /> can only be used inside of a ViewGroup");
}
//提取出 include 的 thme 屬性,如果設(shè)置了 them 屬性腥例,那么include 包裹的View 設(shè)置的 theme 將會無效
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
final boolean hasThemeOverride = themeResId != 0;
if (hasThemeOverride) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
//如果這個屬性是指向主題中的某個屬性辅甥,我們必須設(shè)法得到主題中l(wèi)ayout 的資源標(biāo)識符
//先獲取 layout 屬性(資源 id)是否設(shè)置
int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
if (layout == 0) {
//如果沒直接設(shè)置布局的資源 id,那么就檢索?attr/name這一類的 layout 屬性
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
if (value == null || value.length() <= 0) {
throw new InflateException("You must specify a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />");
}
//從 ?attr/name 這一類的屬性中燎竖,獲取布局屬性
layout = context.getResources().getIdentifier(
value.substring(1), "attr", context.getPackageName());
}
//這個布局資源也許存在主題屬性中璃弄,所以需要去主題屬性中解析
if (mTempValue == null) {
mTempValue = new TypedValue();
}
if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
layout = mTempValue.resourceId;
}
if (layout == 0) {
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
throw new InflateException("You must specify a valid layout "
+ "reference. The layout ID " + value + " is not valid.");
}
final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
(ViewGroup) parent, /*attachToRoot=*/true);
if (precompiled == null) {
final XmlResourceParser childParser = context.getResources().getLayout(layout);
try {
final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
while ((type = childParser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty.
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(getParserStateDescription(context, childAttrs)
+ ": No start tag found!");
}
final String childName = childParser.getName();
if (TAG_MERGE.equals(childName)) {
// The <merge> tag doesn't support android:theme, so
//解析 Meger 標(biāo)簽
rInflate(childParser, parent, context, childAttrs, false);
} else {
//根據(jù) name名稱來創(chuàng)建View
final View view = createViewFromTag(parent, childName,
context, childAttrs, hasThemeOverride);
final ViewGroup group = (ViewGroup) parent;
//獲取 View 的 id 和其 Visiable 屬性
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.Include);
//include 布局的 id
final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
final int visibility = a.getInt(R.styleable.Include_visibility, -1);
a.recycle();
//設(shè)置 include 包裹內(nèi)容的通用 Params
ViewGroup.LayoutParams params = null;
try {//include 的 params
params = group.generateLayoutParams(attrs);
} catch (RuntimeException e) {
// Ignore, just fail over to child attrs.
}
if (params == null) { //子視圖的 params
params = group.generateLayoutParams(childAttrs);
}
//include設(shè)置了params用include的,否則用子視圖的
view.setLayoutParams(params);
// 解析子標(biāo)簽
rInflateChildren(childParser, view, childAttrs, true);
//如果不等于 View.NO_ID 重新設(shè)置 ID
if (id != View.NO_ID) {
//將include的id設(shè)置給view
view.setId(id);
}
// 加載include內(nèi)容時构回,需要直接設(shè)置其 可見性
switch (visibility) {
case 0:
view.setVisibility(View.VISIBLE);
break;
case 1:
view.setVisibility(View.INVISIBLE);
break;
case 2:
view.setVisibility(View.GONE);
break;
}
//添加至父容器中
group.addView(view);
}
} finally {
childParser.close();
}
}
LayoutInflater.consumeChildElements(parser);
}