---
title: 'Activity 布局'
disqus: kyleAlien
---
Activity 布局
===
## OverView of Content
以下會追尋 **android 7.1** 源碼,重點部份使用 `@` 標明
[TOC]
## Activity & Window
1. Activity 與我們常常接觸的 Context、Application 很有關係,我們可以從這裡開始下手,最終就會找到與 Window 的關係
2. Window 是抽象類,它的實做類是 PhoneWindow,負責解析 & 放置布局
先上完整的時序圖
> 
### Activity & Context & Application 創建
* **我們從 ActivityThread 類,其當中的 ++performLaunchActivity 方法開始++**(從頭開始就是從 Activity#main 開始,但是那就涉及到了 Binder 機制,這就不是我們的重點了,會另外挑章節說明,暫且當為一個黑盒子)
```java=
/**
* ActivityThread.java
*/
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
// @ 是否有夾帶 Intent 資訊
ComponentName component = r.intent.getComponent();
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);
}
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
//1. Activity object
// Create Activity, used empty construct
activity = mInstrumentation.newActivity( //@ reflection create
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 {
// 2. Application Create
Application app = r.packageInfo.makeApplication(false, mInstrumentation); //@ get appliction object, also use reflection to create application object
... Log 訊息
if (activity != null) {
// 3. Context Create
Context appContext = createBaseContextForActivity(r, activity);
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);
... 省略部份,在 Activity 連接 Context Application 時會在解釋
}
r.paused = true;
// @ put in cache
mActivities.put(r.token, r);
} /* 省略 catch */
return activity;
}
```
1. **首先會先創建 Activity,這個創建是使用了反射來創建**,創建的 Activity 是使用無參數構造函數
```java=
/**
* ActivityThread.java
* performLaunchActivity 方法
*/
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity( //@ reflection create
cl, component.getClassName(), r.intent);
/**
* Instrumentation.java
*/
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
// ClassLoader 讀取 class,再使用反射創建
return (Activity)cl.loadClass(className).newInstance();
}
```
* className 對應到 AndroidManifest Activity 中設定的 Name
> 
2. **創建 Application 對象,同樣使用反射創建,創建完立刻呼叫 Application attach 函數。這裡會創建 ^1^ Application 的 Context,^2^ Applicatoin 實體**
```java=
/**
* ActivityThread.java
* performLaunchActivity 方法
*/
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
/**
* LoadedApk.java
*/
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
// 已創建 Application 對象就不再創建
if (mApplication != null) {
return mApplication;
}
...
Application app = null;
// AndroidManifest 設定的 Application Name
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
...
initializeJavaContextClassLoader();
}
// @ Create ContextImpl
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
// @ Create Application
// 查看 newApplication 函數
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
...省略
return app;
}
/**
* Instrumentation.java
*/
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
// ClassLoader load target class
return newApplication(cl.loadClass(className), context);
}
static public Application newApplication(Class<?> clazz, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
// 根據 class 反射創建 Application 對象
Application app = (Application)clazz.newInstance();
// 呼叫 Application#attach 方法
app.attach(context);
return app;
}
```
3. **創建 Context 物件,==Context 是抽象,實體類是 ContextImpl==**
```java=
/**
* ActivityThread.java
* performLaunchActivity 方法
*/
Context appContext = createBaseContextForActivity(r, activity);
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
int displayId = Display.DEFAULT_DISPLAY;
try {
displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
//@ Create Context
// 分析 createActivityContext 方法
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.token, displayId, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;
... 省略
return baseContext;
}
/**
* ContextImpl.java
*/
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread, packageInfo, activityToken, null, 0,
null, overrideConfiguration, displayId);
}
```
> 
### Activity 連接 & 創建 Window
* **將 Activity、Context、Application 連接在一起的是 ==Activity#attch 函數==,並且會在裡面創建 Window 實體對象 (有就是 ++PhoneWindow++)**
```java=
/**
* ActivityThread.java
*/
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
... 省略,上面以解說
try {
... 省略,上面以解說 (創建 Activity、Application、ContextImpl... 等等對象)
if (activity != null) {
...
// Window object
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
//@ Create Phone window in {@link attach} function,
// find Phonewindows !!
activity.attach(appContext, this, getInstrumentation(), r.token, //@ let ^2^appliction & ^3^context bind on ^1^activity
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window);
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
// @ 2. 呼叫 Activity 的 onCreate 方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
... 省略部份
}
r.paused = true;
// @ put in cache
mActivities.put(r.token, r);
} /* 省略 catch */
return activity;
}
/**
* Activity.java
* 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) {
// @ 3. 創建 AppCompatDelegateImp 對象
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
// @ 1. Creaet Winodw object
// 假設當前帶入的 window is null
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
}
```
1. 這裡的重點在 Window 是抽象類,**在 Activity#attach 方法中會創建 Window 的實體類,也就是 PhoneWindow**
2. 呼叫完 Activity#attach 方法後,就會呼叫 Activity#onCreate 方法
```java=
/**
* Instrumentation.java
*/
public void callActivityOnCreate(Activity activity, Bundle icicle) {
// 前置作業
prePerformCreate(activity);
activity.performCreate(icicle);
// 後續作業
postPerformCreate(activity);
}
/**
* Activity.java
*/
final void performCreate(Bundle icicle) {
restoreHasCurrentPermissionRequest(icicle);
// 這裡就會呼叫我們 Override 的 onCreate 方法
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
```
> 
3. 還有第三點,**attachBaseContext 創建 AppCompatDelegateImp**,**這是下一個小節開始的重點**
## AppCompatActivity
AppCompatActivity 是啥?這是 Android 在幫我們創建 Activity 時,預設的父類它的繼承關係如下(你也可以把它當成一個 Activity)
```java=
// AppCompatActivity 也是繼承於 Activity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
```
> 
### [AppCompatActivity](https://github.com/guardianproject/android-support-library/blob/master/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java) & PhoneWindws 關係
* 現在我們要把 `AppCompatActivity` & `PhoneWindow` 串在一起,看看這兩者之間是如何產生關係的,**這跟上面所解說的 Activity & Window 有關係,尤其是在 Activity#attach 函數的 ==attachBaseContext== 有關係**
- 以下是完整的時序圖
> 
1. **attachBaseContext**:會創建出 AppCompatDelegateImpl,而傳入的 Window 為 null,所以在這個步驟並不會有 Windows
> 
```java=
/**
* Activity.java
*/
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) {
// AppCompatActivity 覆寫了這個方法
attachBaseContext(context);
...省略
}
/**
* AppCompatActivity.java
*/
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
@Override
protected void attachBaseContext(Context newBase) {
// 追尋 getDelegate 方法
super.attachBaseContext(getDelegate().attachBaseContext2(newBase));
}
@NonNull
public AppCompatDelegate getDelegate() {
// 一開始進來為 null
if (mDelegate == null) {
// 創建 AppCompatDelegate (AppCompatDelegate 是抽象)
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
}
/**
* AppCompatDelegate.java(抽象)
*/
public abstract class AppCompatDelegate {
@NonNull
public static AppCompatDelegate create(@NonNull Activity activity,
@Nullable AppCompatCallback callback) {
// 工廠方法
// AppCompatDelegate 時做類是 AppCompatDelegateImpl
return new AppCompatDelegateImpl(activity, callback);
}
}
/**
* AppCompateDelegateImpl.java
*/
class AppCompatDelegateImpl extends AppCompatDelegate
implements MenuBuilder.Callback, LayoutInflater.Factory2 {
// 建構函數
AppCompatDelegateImpl(Activity activity, AppCompatCallback callback) {
// 第二個參數 window is null
this(activity, null, callback, activity);
}
private AppCompatDelegateImpl(Context context, Window window, AppCompatCallback callback,
Object host) {
mContext = context;
mAppCompatCallback = callback;
mHost = host;
// 模式判斷
if (mLocalNightMode == MODE_NIGHT_UNSPECIFIED
&& mHost instanceof Dialog) {
final AppCompatActivity activity = tryUnwrapContext();
if (activity != null) {
mLocalNightMode = activity.getDelegate().getLocalNightMode();
}
}
if (mLocalNightMode == MODE_NIGHT_UNSPECIFIED) {
// Try and read the current night mode from our static store
final Integer value = sLocalNightModes.get(mHost.getClass().getName());
if (value != null) {
mLocalNightMode = value;
// Finally remove the value
sLocalNightModes.remove(mHost.getClass().getName());
}
}
// 目前傳入的 Window 是 null
if (window != null) {
attachToWindow(window);
}
AppCompatDrawableManager.preload();
}
// 連接 Window 對象
private void attachToWindow(@NonNull Window window) {
if (mWindow != null) {
throw new IllegalStateException(
"AppCompat has already installed itself into the Window");
}
... 省略部分
// * 此處為 window 賦值
mWindow = window;
}
}
```
* Activity#attach 是最終會透過建構 `AppCompatDelegateImpl` 來賦予 window 對象,但是最初傳入的 window 是 null (也空對象),**所以第一步驟目的是建構 AppCompatDelegateImpl 對象,==這個步驟則是在建構 Activity 時就自動做==**
2. onCreate 透過 setContent(),賦予 Window 對象 (可以當成懶加載)
> 
```java=
/**
* AppCompatActivity.java
*/
@Override
public void setContentView(@LayoutRes int layoutResID) {
// @ 先看 getDelegate,再看 setContentView
getDelegate().setContentView(layoutResID);
}
@NonNull
public AppCompatDelegate getDelegate() {
// @ 這時 mDelegate 就不為空
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate; // 對象就是 AppCompatDelegateImpl
}
/**
* AppCompatDelegateImpl
*/
@Override
public void setContentView(int resId) {
// @ 確認 Decor 布局
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
// 建立 mSubDecor View
mSubDecor = createSubDecor();
...省略部分
}
}
// @ 這裡會檢查很多布局屬性
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.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.");
}
// @ 追隨 ensureWindow 方法,確認 window 對象
ensureWindow();
mWindow.getDecorView();
...省略部分
// @ PhoneWindow 的布局分析,分析 PhoneWindow#setContentView 方法
mWindow.setContentView(subDecor);
}
private void ensureWindow() {
// @ 如果對象是 Activity 則 window 就使用 ++懶加載++
if (mWindow == null && mHost instanceof Activity) {
// Activity 的 window 就是 PhoneWindow
attachToWindow(((Activity) mHost).getWindow());
}
if (mWindow == null) {
throw new IllegalStateException("We have not been given a Window");
}
}
/**
* Activity.java
*/
public Window getWindow() {
// @ attach 方法就建立了 PhoneWindow
return mWindow;
}
// 回憶 mWindow 是在 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*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...省略部分
}
```
* 第二部分的重點在 **^1^ Activity 是透過手動在 onCreate 呼叫 setContext 來獲取 Activity 的 Window 對象 (達到懶加載),而這個對象則是在創建 Activity 時就建立好的++ PhoneWindow 對象++,^2^ ==呼叫 PhoneWindow 的 setContentView==**
## Activity 布局分析
我們上面分析到 AppCompatDelegateImpl#createSubDecor 方法,這個方法**最後會掉用到 PhoneWindow 的 setContentView 上**
```java=
private ViewGroup createSubDecor() {
// @ 確認 window 對象
ensureWindow();
mWindow.getDecorView();
...省略部分
// @ PhoneWindow 的布局分析
mWindow.setContentView(subDecor);
}
```
有關於布局分析,有興趣可以先看看 [**LayoutInflate**](https://hackmd.io/EPfgb14TS-Kcd-Pv7tDZ-w?view#%E6%95%B4%E9%AB%94%E6%A6%82%E5%BF%B5%E5%9C%96) 這篇文章,這邊稍加帶過
### PhoneWindow - setContentView
1. **首先會先建構 Decor View**
2. **進行 inflate 分析 ++mContentParent++ 布局**
```java=
/**
* PhoneWindow.java
*/
@Override
public void setContentView(int layoutResID) {
// @ 一開始 mContentParent 就是 null
if (mContentParent == null) {
// @ 會在 installDecor 才賦予值
// 追尋 ++ installDecor 方法++
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
... 有 Scene 轉場動畫,省略
} else {
// @ 大部份是直接 inflate 分析
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
```
### PhoneWindow - installDecor
* 如其名 PhoneWindow#installDecor,也就是安裝(建構)DecorView
```java=
/**
* PhoneWindow.java
*/
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// @ 產生 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) {
// @ 分析 Window Style 產生對應的 Layout
mContentParent = generateLayout(mDecor);
... 省略部份
}
}
/**
* 創建 DecorView & 包裝 Context
*/
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();
}
// @ 建構 DecorView
return new DecorView(context, featureId, this, getAttributes());
}
/**
* 對應的 ViewGroupdroid.internal.R.id.content;
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
...以下有許多類似的判斷省略
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
...省略
} else {
// 預設 layout 布局
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
// 分析布局 & addView
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 返回 ViewGroup of FrameLayout
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
... 以下省略
return contentParent;
}
```
1. 這個步驟主要目的在建構 DecorView(並對 Context 包裝),並載入預設布局(R.layout.screen_simple)
> 
2. screen_simple 布局可以在源碼中的 Framework 資料夾內找到,這個布局很簡單,只有 ViewStub(填充)、FrameLayout 布局
```xml=
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
```
3. 最後它會找到 FrameLayout,並將其作為 **ViewGroup 賦予 mContentParent,==之後將會使用 LayoutInflate 對它進行解析==**
### LayoutInflater 加載
* 更詳細的解析分細在 [**LayoutInflate**](https://hackmd.io/EPfgb14TS-Kcd-Pv7tDZ-w?view#%E6%95%B4%E9%AB%94%E6%A6%82%E5%BF%B5%E5%9C%96) 這篇文章,這邊帶入重點部份,**^1^ 第一個是創建 View,^2^ 產生 LayoutParams 對象,依照使用者傳入的參數決定這個 View 跟 Parent View 的關係**
```java=
/**
* LayoutInflate.java
*/
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
advanceToRootNode(parser);
// @ Xml Tag
final String name = parser.getName();
...Debug 資訊
if (TAG_MERGE.equals(name)) {
// @ 分析 Merge 標籤
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// @ 產生 LayoutParams 對象
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
...Debug 資訊
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch(...) {
... 省略
} finally {
... 省略
}
}
```
* **創建 View:關注的是 ==createViewFromTag== 方法**,它會 ^1^ 看看使用者是否有自己寫 Factory,^2^ 分析是否是自定義布局,^3^ 反射 View 的第二個 constructor 並將 View 創建
```java=
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 {
// @ 1. 嘗試創建 View
View view = tryCreateView(parent, name, context, attrs);
// @ 若是上一個步驟仍然沒有創建 View 則
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// 2. 分析是否是自定義布局
if (-1 == name.indexOf('.')) {
// 原生布局,其實最終還是後呼叫 createView
view = onCreateView(context, parent, name, attrs);
} else {
// 自定義布局
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (...) {
throw e;
}
/**
* 創建 View
*/
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) {
// Class not found in the cache, see if it's real, and try to add it
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);
}
}
// 3. 找尋符合條件的建構函數,這是 ++兩參++ 的 View 構造函數
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
... 省略
}
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
// @ 創建 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]));
}
// @ 返回反射創建的 View
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
} catch (NoSuchMethodException e) {
...以下省略
}
```
1. 檢查是否有字定義創建 View,也就是這裡可以注入三個類,分別是 Factory、Factory2(第三個 mPrivateFactory 也是 Factory2)
```java=
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
...省略部份
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;
}
```
2. 使用反射創建時,會找到規定的建構函數 clazz.getConstructor(mConstructorSignature)
```java=
// 這個就是我們自定義 View 時必須覆寫的建構函數 (應該很熟悉?...
// 就是自定義 View 時必須寫的建構函數)
static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
```
---
* 再來看看我們關注個第二點,有關於到使用者傳入的參數 **root、attachToRoot**,這會產生以下狀況
| root | attachToRoot | 狀況 |
| -------- | -------- | -------- |
| null | true / false | 建構的 View 是獨立個體,屬性無效 |
| 有 ParentView | true | ParentView 屬性有效,產生的 View 是子布局,使用 addView 實現 |
| 有 ParentView | false | ParentView 屬性有效,產生的 View 不是子布局,將 ParentView 的 LayoutParams 設定給 View |
```java=
// 如果 ParentView (root) 為空,直接返回該 View
if (root == null || !attachToRoot) {
result = temp;
}
// 如果 ParentView (root) 不為空 && attachToRoot true
if (root != null && attachToRoot) {
// 透過對 ParentView 使用 addView
root.addView(temp, params);
}
// 如果 ParentView (root) 不為空 && attachToRoot false
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// ParentView 的屬性給 View 使用
// params 是 ParentView 的 LayoutParams
temp.setLayoutParams(params);
}
}
```
## Appendix & FAQ
:::info
:::
###### tags: `Android Framework`