抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

Activity显示流程

1
2
3
4
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}

上面这段代码是我们新建项目适合默认创建的,我们来看一下setContentView()究竟做了什么能让页面显示出来,点进去看一眼

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 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();
}

可以看到,调用了了getWindow()setContentView()方法,那我们看一下getWindow()拿到的是什么

1
2
3
4
5
6
7
8
9
10
11
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}

getDelegate()返回的是mWindow,我们看一下mWindow是什么类型以及在哪初始化的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private Window mWindow;

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) {
//省略部分代码
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
//省略部分代码
...
}

可以看到mWindow实际上是PhoneWindow,那就来看看PhoneWindowsetContentView()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@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.
//注释1
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;
}

我们先来看注释1的地方,此处判断了mContentParent是否存在,如果不存在则调用installDecor()方法,找一下mContentParent赋值的地方发现只有一处,就是installDecor()方法,我们点进去看一眼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
//省略部分代码
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
//省略部分代码
...
}
}

可以看到里面的逻辑也很简单,mDecor没有的话就generate一个,mContentParent没有的话也generate一个,先看一下mDecor的生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}

逻辑很简单,直接new了一个DecorView返回回来了,我们再看一下mContentParent的生成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
protected ViewGroup generateLayout(DecorView decor) {
//省略部分代码
int layoutResource;
int features = getLocalFeatures();
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.onResourcesLoaded(mLayoutInflater, layoutResource);

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//省略部分代码
return contentParent;
}

可以看到mContentParent即为contentParent,是直接通过findViewById查找id为ID_ANDROID_CONTENT(com.android.internal.R.id.content)查找出来的,诶?window也有findViewById方法吗,看一眼咋写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Finds a view that was identified by the {@code android:id} XML attribute
* that was processed in {@link android.app.Activity#onCreate}.
* <p>
* This will implicitly call {@link #getDecorView} with all of the associated side-effects.
* <p>
* <strong>Note:</strong> In most cases -- depending on compiler support --
* the resulting view is automatically cast to the target class type. If
* the target class type is unconstrained, an explicit cast may be
* necessary.
*
* @param id the ID to search for
* @return a view with given ID if found, or {@code null} otherwise
* @see View#findViewById(int)
* @see Window#requireViewById(int)
*/
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}

可以看到直接调用的DecorViewfindViewById,这里我们回到PhoneWindowgetDecorView方法

1
2
3
4
5
6
7
@Override
public final @NonNull View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}

其实不用看也可以猜出,返回的就是我们刚刚看到的那个mDecor,等等,好像哪里有点不对,mDecor不是刚刚new出来的吗?,难道它里面默认添加了子布局?我们快速回去看一眼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
super(context);
mFeatureId = featureId;

mShowInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.linear_out_slow_in);
mHideInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.fast_out_linear_in);

mBarEnterExitDuration = context.getResources().getInteger(
R.integer.dock_enter_exit_duration);
mForceWindowDrawsBarBackgrounds = context.getResources().getBoolean(
R.bool.config_forceWindowDrawsStatusBarBackground)
&& context.getApplicationInfo().targetSdkVersion >= N;
mSemiTransparentBarColor = context.getResources().getColor(
R.color.system_bar_background_semi_transparent, null /* theme */);

updateAvailableWidth();

setWindow(window);

updateLogTag(params);

mResizeShadowSize = context.getResources().getDimensionPixelSize(
R.dimen.resize_shadow_size);
initResizingPaints();

mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK);
}

这里面也没有添加子布局的代码啊?那一定是后add进去的,还记得刚刚findViewById之前有个方法叫onResourcesLoaded吗?进去看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}

mDecorCaptionView = createDecorCaptionView(inflater);
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();
}

果然是在这里addView的,那么这个根据layoutResourceinflate出来的root里一定是有刚刚的id,我们看下这个layoutResource传进来的值是多少,回到刚刚的generateLayout方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
protected ViewGroup generateLayout(DecorView decor) {
//省略部分代码
int layoutResource;
int features = getLocalFeatures();
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.onResourcesLoaded(mLayoutInflater, layoutResource);

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//省略部分代码
return contentParent;
}

可以看到layoutResource是根据一堆条件判断出来的,我们随便取其中的R.layout.screen_simple看下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<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>

可以看到确实是存在id为content的子布局,到这里我们就把installDecor()的整个流程分析完了,我们回到setContentView方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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;
}

我们可以看到判断了是否有FEATURE_CONTENT_TRANSITIONS这个Feature,这个是跟动画相关的,我们直接看elseelse里的操作很简单,就是调用了mLayoutInflaterlayoutResID布局加载到我们刚刚初始化的mContentParent里去,我们看一下他到底是怎么加载的布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy (if
* <em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if <em>attachToRoot</em> is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}

View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}

点进去发现inflate有好多重载方法,最终调用的是上面这个方法,可以看到这里有一步优化,tryInflatePrecompiled这个方法顾名思义,尝试实例化已经预编译的,我们先不管这个预编译,直接看没有预编译的情况,这里获取了XmlResourceParser传入到了inflate方法,并且把inflate的结果返回回去了,那么这个inflate应该就是进行我们布局xml解析的地方了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* Inflate a new view hierarchy from the specified XML node. Throws
* {@link InflateException} if there is an error.
* <p>
* <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
* reasons, view inflation relies heavily on pre-processing of XML files
* that is done at build time. Therefore, it is not currently possible to
* use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
*
* @param parser XML dom node containing the description of the view
* hierarchy.
* @param root Optional view to be the parent of the generated hierarchy (if
* <em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if <em>attachToRoot</em> is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
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);
final String name = parser.getName();

if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}

if (TAG_MERGE.equals(name)) {
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 {
// 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) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
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);
}
}

if (DEBUG) {
System.out.println("-----> start inflating children");
}

// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);

if (DEBUG) {
System.out.println("-----> done inflating children");
}

// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}

// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}

} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(inflaterContext, attrs)
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;

Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

return result;
}
}

可以看到这里有一个判断,如果标签名是merge的话走的是rInflate方法,其他情况走rInflateChildren方法,我们先不管merge标签的情况,直接看rInflateChildren

1
2
3
4
5
6
7
8
9
10
11
/**
* Recursive method used to inflate internal (non-root) children. This
* method calls through to {@link #rInflate} using the parent context as
* the inflation context.
* <strong>Note:</strong> Default visibility so the BridgeInflater can
* call it.
*/
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

可以看到rInflateChildren调用的也是rInflate方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

/**
* Recursive method used to descend down the xml hierarchy and instantiate
* views, instantiate their children, and then call onFinishInflate().
* <p>
* <strong>Note:</strong> Default visibility so the BridgeInflater can
* override it.
*/
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) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
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();
}
}

可以看到这个while循环就是我们要找的东西了,在里面判断了各种标签requestFocustagincludemerge,最后的else才是真正解析TextView等控件的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
* Convenience method for calling through to the five-arg createViewFromTag
* method. This method passes {@code false} for the {@code ignoreThemeAttr}
* argument and should be used for everything except {@code &gt;include>}
* tag parsing.
*/
@UnsupportedAppUsage
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}

/**
* Creates a view from a tag name using the supplied attribute set.
* <p>
* <strong>Note:</strong> Default visibility so the BridgeInflater can
* override it.
*
* @param parent the parent view, used to inflate layout params
* @param name the name of the XML tag used to define the view
* @param context the inflation context for the view, typically the
* {@code parent} or base layout inflater context
* @param attrs the attribute set for the XML tag used to define the view
* @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
* attribute (if set) for the view being inflated,
* {@code false} otherwise
*/
@UnsupportedAppUsage
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('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}

return view;
} catch (InflateException e) {
throw e;

} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;

} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}

注意这个地方的开头 有一个解析view标签的地方,注意这个view是小写的,不是我们经常用的那个<View/>,而是<view>,之前没用过,试了下还真可以

1
2
3
4
5
6
<view class="android.widget.TextView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>

用法跟直接用好像差不多,不过我只在xml里预览了一下,没有实际运行来看,不知道这个标签有什么用,我们继续往下看try里面的内容,这里我把代码单独粘贴出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
View view = tryCreateView(parent, name, context, attrs);

if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}

可以看到先是调用tryCreateView尝试创建View,我们看下这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* Tries to create a view from a tag name using the supplied attribute set.
*
* This method gives the factory provided by {@link LayoutInflater#setFactory} and
* {@link LayoutInflater#setFactory2} a chance to create a view. However, it does not apply all
* of the general view creation logic, and thus may return {@code null} for some tags. This
* method is used by {@link LayoutInflater#inflate} in creating {@code View} objects.
*
* @hide for use by precompiled layouts.
*
* @param parent the parent view, used to inflate layout params
* @param name the name of the XML tag used to define the view
* @param context the inflation context for the view, typically the
* {@code parent} or base layout inflater context
* @param attrs the attribute set for the XML tag used to define the view
*/
@UnsupportedAppUsage(trackingBug = 122360734)
@Nullable
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, 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;
}

可以看到这里先是判断了标签名是否是TAG_1995这个常量,下面还有一句注释,感觉这里像一个彩蛋,继续往下看,如果设置了mFactory2的话就用mFactory2创建view,否则如果设置了mFactory的话就用mFactory创建view,两个都没设置的话view为null,再看是否设置了mPrivateFactory,如果mPrivateFactory也没设置的话,会返回null,我们来看下这几个东西到底是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
private Factory mFactory;
private Factory2 mFactory2;
private Factory2 mPrivateFactory;

/**
* Attach a custom Factory interface for creating views while using
* this LayoutInflater. This must not be null, and can only be set once;
* after setting, you can not change the factory. This is
* called on each element name as the xml is parsed. If the factory returns
* a View, that is added to the hierarchy. If it returns null, the next
* factory default {@link #onCreateView} method is called.
*
* <p>If you have an existing
* LayoutInflater and want to add your own factory to it, use
* {@link #cloneInContext} to clone the existing instance and then you
* can use this function (once) on the returned new instance. This will
* merge your own factory with whatever factory the original instance is
* using.
*/
public void setFactory(Factory factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = factory;
} else {
mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}
}

/**
* Like {@link #setFactory}, but allows you to set a {@link Factory2}
* interface.
*/
public void setFactory2(Factory2 factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}

@UnsupportedAppUsage
public void setPrivateFactory(Factory2 factory) {
if (mPrivateFactory == null) {
mPrivateFactory = factory;
} else {
mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
}
}

根据setFactorysetFactory2的注释可以得出,Factory会在系统解析每个View之前调用,有点类似于一个拦截器,众所周知系统解析布局是反射生成View的,而反射是耗时的,我们可以在这里自定义自己的Factory,直接返回new出来的View来节省反射这步的时间,但是要注意,Factory只能设置一次,再次设置会抛出异常,所以采用设置Factory方案的框架只能使用一个,我们接下来看如果Factory没有设置的情况,这里我们还是把代码粘贴出来

1
2
3
4
5
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}

这里有个判断是标签名里是否含有.,为什么会这么判断呢,我们先看下如果标签名含.的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/**
* Low-level function for instantiating a view by name. This attempts to
* instantiate a view class of the given <var>name</var> found in this
* LayoutInflater's ClassLoader.
*
* <p>
* There are two things that can happen in an error case: either the
* exception describing the error will be thrown, or a null will be
* returned. You must deal with both possibilities -- the former will happen
* the first time createView() is called for a class of a particular name,
* the latter every time there-after for that class name.
*
* @param viewContext The context used as the context parameter of the View constructor
* @param name The full name of the class to be instantiated.
* @param attrs The XML attributes supplied for this instance.
*
* @return View The newly instantiated view, or null.
*/
@Nullable
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);
}
}
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 {
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]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
} catch (NoSuchMethodException e) {
final InflateException ie = new InflateException(
getParserStateDescription(viewContext, attrs)
+ ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;

} catch (ClassCastException e) {
// If loaded class is not a View subclass
final InflateException ie = new InflateException(
getParserStateDescription(viewContext, attrs)
+ ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(viewContext, attrs) + ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

这里先从sConstructorMap查找了constructor的缓存,没找到的话调用Class.forName找到对应的类,然后获取对应类的constructor并缓存到sConstructorMap里,接下来反射调用构造方法构造出View实例,这样我们就获取到了我们xml中声明的View,接下来我们看下标签名不含.的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* Version of {@link #onCreateView(View, String, AttributeSet)} that also
* takes the inflation context. The default
* implementation simply calls {@link #onCreateView(View, String, AttributeSet)}.
*
* @param viewContext The Context to be used as a constructor parameter for the View
* @param parent The future parent of the returned view. <em>Note that
* this may be null.</em>
* @param name The fully qualified class name of the View to be create.
* @param attrs An AttributeSet of attributes to apply to the View.
*
* @return View The View created.
*/
@Nullable
public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
@NonNull String name, @Nullable AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(parent, name, attrs);
}

/**
* Version of {@link #onCreateView(String, AttributeSet)} that also
* takes the future parent of the view being constructed. The default
* implementation simply calls {@link #onCreateView(String, AttributeSet)}.
*
* @param parent The future parent of the returned view. <em>Note that
* this may be null.</em>
* @param name The fully qualified class name of the View to be create.
* @param attrs An AttributeSet of attributes to apply to the View.
*
* @return View The View created.
*/
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}

/**
* This routine is responsible for creating the correct subclass of View
* given the xml element name. Override it to handle custom view objects. If
* you override this in your subclass be sure to call through to
* super.onCreateView(name) for names you do not recognize.
*
* @param name The fully qualified class name of the View to be create.
* @param attrs An AttributeSet of attributes to apply to the View.
*
* @return View The View created.
*/
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}

可以看到也是调用的相同方法,只是前缀默认拼上了android.view.,我们自己包名的控件拼接上这个肯定就找不到了,所以这个是系统控件,如TextView等会变成android.view.TextView,到这里我们就把整个setContentView的流程梳理完了。

评论