APP运行界面:
看见没有,上面我们将主题设置为NoTitleBar,所以在generateLayout方法中的layoutResource变量值为R.layout.screen_simple
,所以我们看下系统这个screen_simple.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” />
布局中,一般会包含ActionBar,Title,和一个id为content的FrameLayout,这个布局是NoTitle的。
再来看下上面这个App的hierarchyviewer图谱,如下:
看见了吧,通过这个App的hierarchyviewer和系统screen_simple.xml文件比较就验证了上面我们分析的结论,不再做过多解释。
然后回过头可以看见上面PhoneWindow类的setContentView方法最后通过调运mLayoutInflater.inflate(layoutResID, mContentParent);
或者mContentParent.addView(view, params);
语句将我们的xml或者java View插入到了mContentParent(id为content的FrameLayout对象)ViewGroup中。最后setContentView还会调用一个Callback接口的成员函数onContentChanged来通知对应的Activity组件视图内容发生了变化。
2-4 Window类内部接口Callback的onContentChanged方法
上面刚刚说了PhoneWindow类的setContentView方法中最后调运了onContentChanged方法。我们这里看下setContentView这段代码,如下:
public void setContentView(int layoutResID) {
…
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
看着没有,首先通过getCallback获取对象cb(回调接口),PhoneWindow没有重写Window的这个方法,所以到抽象类Window中可以看到:
/**
- Return the current Callback interface for this window.
*/
public final Callback getCallback() {
return mCallback;
}
这个mCallback在哪赋值的呢,继续看Window类发现有一个方法,如下:
public void setCallback(Callback callback) {
mCallback = callback;
}
Window中的mCallback是通过这个方法赋值的,那就回想一下,Window又是Activity的组合成员,那就是Activity一定调运这个方法了,回到Activity发现在Activity的attach方法中进行了设置,如下:
final void attach(Context context, ActivityThread aThread,
…
mWindow.setCallback(this);
…
}
也就是说Activity类实现了Window的Callback接口。那就是看下Activity实现的onContentChanged方法。如下:
public void onContentChanged() {
}
咦?onContentChanged是个空方法。那就说明当Activity的布局改动时,即setContentView()或者addContentView()方法执行完毕时就会调用该方法。
所以当我们写App时,Activity的各种View的findViewById()方法等都可以放到该方法中,系统会帮忙回调。
2-5 setContentView源码分析总结
可以看出来setContentView整个过程主要是如何把Activity的布局文件或者java的View添加至窗口里,上面的过程可以重点概括为:
-
创建一个DecorView的对象mDecor,该mDecor对象将作为整个应用窗口的根视图。
-
依据Feature等style theme创建不同的窗口修饰布局文件,并且通过findViewById获取Activity布局文件该存放的地方(窗口修饰布局文件中id为content的FrameLayout)。
-
将Activity的布局文件添加至id为content的FrameLayout内。
至此整个setContentView的主要流程就分析完毕。你可能这时会疑惑,这么设置完一堆View关系后系统是怎么知道该显示了呢?下面我们就初探一下关于Activity的setContentView在onCreate中如何显示的(声明一下,这里有些会暂时直接给出结论,该系列文章后面会详细分析的)。
2-6 setContentView完以后Activity显示界面初探
这一小部分已经不属于sentContentView的分析范畴了,只是简单说明setContentView之后怎么被显示出来的(注意:Activity调运setContentView方法自身不会显示布局的)。
记得前面有一篇文章《Android异步消息处理机制详解及源码分析》的3-1-2小节说过,一个Activity的开始实际是ActivityThread的main方法(至于为什么后面会写文章分析,这里站在应用层角度先有这个概念就行)。
那在这一篇我们再直接说一个知识点(至于为什么后面会写文章分析,这里站在应用层角度先有这个概念就行)。
当启动Activity调运完ActivityThread的main方法之后,接着调用ActivityThread类performLaunchActivity来创建要启动的Activity组件,在创建Activity组件的过程中,还会为该Activity组件创建窗口对象和视图对象;接着Activity组件创建完成之后,通过调用ActivityThread类的handleResumeActivity将它激活。
所以我们先看下handleResumeActivity方法一个重点,如下:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
…
// TODO Push resumeArgs into the activity for consideration
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
…
// If the window hasn’t yet been added to the window manager,
// and this guy didn’t finish itself or start another activity,
// then go ahead and add the window.
…
// If the window has already been added, but during resume
// we started another activity, then don’t yet make the
// window visible.
…
// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
…
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
…
} else {
// If an exception was thrown when trying to resume, then
// just end this activity.
…
}
}
看见r.activity.makeVisible();
语句没?调用Activity的makeVisible方法显示我们上面通过setContentView创建的mDecor视图族。所以我们看下Activity的makeVisible方法,如下:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
看见没有,通过DecorView(FrameLayout,也即View)的setVisibility方法将View设置为VISIBLE,至此显示出来。
到此setContentView的完整流程分析完毕。
【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】
3 Android5.1.1(API 22)看看LayoutInflater机制原理
上面在分析setContentView过程中可以看见,在PhoneWindow的setContentView中调运了mLayoutInflater.inflate(layoutResID, mContentParent);
,在PhoneWindow的generateLayout中调运了View in = mLayoutInflater.inflate(layoutResource, null);
,当时我们没有详细分析,只是告诉通过xml得到View对象。现在我们就来分析分析这一问题。
3-1 通过实例引出问题
在开始之前我们先来做一个测试,我们平时最常见的就是ListView的Adapter中使用LayoutInflater加载xml的item布局文件,所以咱们就以ListView为例,如下:
省略掉Activity代码等,首先给出Activity的布局文件,如下:
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“vertical”>
<ListView
android:id=“@+id/listview”
android:dividerHeight=“5dp”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
给出两种不同的ListView的item布局文件。
textview_layout.xml文件:
<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“100dp”
android:layout_height=“40dp”
android:text=“Text Test”
android:background=“#ffa0a00c”/>
textview_layout_parent.xml文件:
<?xml version="1.0" encoding="utf-8"?><LinearLayout
android:layout_height=“wrap_content”
android:layout_width=“wrap_content”
xmlns:android=“http://schemas.android.com/apk/res/android”>
<TextView xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“100dp”
android:layout_height=“40dp”
android:text=“Text Test”
android:background=“#ffa0a00c”/>
ListView的自定义Adapter文件:
public class InflateAdapter extends BaseAdapter {
private LayoutInflater mInflater = null;
public InflateAdapter(Context context) {
mInflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return 8;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//说明:这里是测试inflate方法参数代码,不再考虑性能优化等TAG处理
return getXmlToView(convertView, position, parent);
}
private View getXmlToView(View convertView, int position, ViewGroup parent) {
View[] viewList = {
mInflater.inflate(R.layout.textview_layout, null),
// mInflater.inflate(R.layout.textview_layout, parent),
mInflater.inflate(R.layout.textview_layout, parent, false),
// mInflater.inflate(R.layout.textview_layout, parent, true),
mInflater.inflate(R.layout.textview_layout, null, true),
mInflater.inflate(R.layout.textview_layout, null, false),
mInflater.inflate(R.layout.textview_layout_parent, null),
// mInflater.inflate(R.layout.textview_layout_parent, parent),
mInflater.inflate(R.layout.textview_layout_parent, parent, false),
// mInflater.inflate(R.layout.textview_layout_parent, parent, true),
mInflater.inflate(R.layout.textview_layout_parent, null, true),
mInflater.inflate(R.layout.textview_layout_parent, null, false),
};
convertView = viewList[position];
return convertView;
}
}
当前代码运行结果:
PS:当打开上面viewList数组中任意一行注释都会抛出异常(java.lang.UnsupportedOperationException: addView(View, LayoutParams) is not supported in AdapterView)。
你指定有些蒙圈了,而且比较郁闷,同时想弄明白inflate的这些参数都是啥意思。运行结果为何有这么大差异呢?
那我告诉你,你现在先别多想,记住这回事,咱们先看源码,下面会告诉你为啥。
3-2 从LayoutInflater源码实例化说起
我们先看一下源码中LayoutInflater实例化获取的方法:
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError(“LayoutInflater not found.”);
}
return LayoutInflater;
}
看见没有?是否很熟悉?我们平时写应用获取LayoutInflater实例时不也就两种写法吗,如下:
LayoutInflater lif = LayoutInflater.from(Context context);
LayoutInflater lif = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
可以看见from方法仅仅是对getSystemService的一个安全封装而已。
3-3 LayoutInflater源码的View inflate(…)方法族剖析
得到LayoutInflater对象之后我们就是传递xml然后解析得到View,如下方法:
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
继续看inflate(int resource, ViewGroup root, boolean attachToRoot)方法,如下:
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, “INFLATING from resource: “” + res.getResourceName(resource) + “” (”
- Integer.toHexString(resource) + “)”);
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
这个方法的第8行获取到XmlResourceParser接口的实例(Android默认实现类为Pull解析XmlPullParser)。接着看第10行inflate(parser, root, attachToRoot);
,你会发现无论哪个inflate重载方法最后都调运了inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
方法,如下:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, “inflate”);
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
//定义返回值,初始化为传入的形参root
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//如果一开始就是END_DOCUMENT,那说明xml文件有问题
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
- “: No start tag found!”);
}
//有了上面判断说明这里type一定是START_TAG,也就是xml文件里的root node
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)) {
//处理merge tag的情况(merge,你懂的,APP的xml性能优化)
//root必须非空且attachToRoot为true,否则抛异常结束(APP使用merge时要注意的地方,
//因为merge的xml并不代表某个具体的view,只是将它包起来的其他xml的内容加到某个上层
//ViewGroup中。)
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
- “ViewGroup root and attachToRoot=true”);
}
//递归inflate方法调运
rInflate(parser, root, attrs, false, false);
} else {
// Temp is the root view that was found in the xml
//xml文件中的root view,根据tag节点创建view对象
final View temp = createViewFromTag(root, name, attrs, false);
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
//根据root生成合适的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)
//如果attachToRoot=false就调用view的setLayoutParams方法
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println(“-----> start inflating children”);
}
// Inflate all children under temp
//递归inflate剩下的children
rInflate(parser, temp, attrs, true, 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非空且attachToRoot=true则将xml文件的root view加到形参提供的root里
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) {
//返回xml里解析的root view
result = temp;
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
推荐学习资料
-
Android进阶学习全套手册
-
Android对标阿里P7学习视频
-
BAT TMD大厂Android高频面试题
力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-nYyxsgUd-1711630724349)]
[外链图片转存中…(img-j1roWSki-1711630724350)]
[外链图片转存中…(img-XJxoob0R-1711630724350)]
[外链图片转存中…(img-UkpINsMB-1711630724350)]
[外链图片转存中…(img-9vryTTAU-1711630724351)]
[外链图片转存中…(img-JzdoOKwn-1711630724351)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-axWqFKla-1711630724352)]
推荐学习资料
-
Android进阶学习全套手册
[外链图片转存中…(img-BInXZIIP-1711630724352)]
-
Android对标阿里P7学习视频
[外链图片转存中…(img-BVPqFhQF-1711630724352)]
-
BAT TMD大厂Android高频面试题
[外链图片转存中…(img-Za4jQi6Z-1711630724353)]