在网上也看了一些牛人的博客。结合自身的理解,将常用的知识点总结一下。
1. 我们在做android开发的过程中,几乎每天都要用到setContentView和LayoutInflater。但是我们并不见得对这两个函数非常了解。今天就从源码的角度分析一下,我们调用了这两个函数以后,android给我们做了什么。我们一般都在OnCreate里边调用setContentView。
那么我们就去Activity中去看看这个方法。
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initActionBar();
}
另外还有两个重载的方法:
public void setContentView(View view) {
getWindow().setContentView(view);
initActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initActionBar();
}
通过这个方法我们不难看出,不管我们是调用哪一个方法,最终都是getWindow()返回window对象,调用window对象中setContentView的方法。window类是个啥呢。通过源码我们发现Window这个类是抽象类,那么在Activity中是如何实例化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) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
.......
}
可以看见11条代码,是在这个地方进行实例化的,其实返回的是phoneWindow,12行代码setCallback后续会用到。我们要搞清楚Window、phoneWindow、DecorView、Activity到底之间是什么关系呢。在网上发现一个哥们总结的就特别好,我就抄过来了。
window相当于显示屏,phoneWindow相当于手机的屏幕,DecorView相当于手机屏幕要显示的内容,Activity是要显示屏幕的载体。专业术语的解释为,window是一个抽象类,phoneWindow是window的子类,DecorView是phoneWindow一个成员类,是整个界面的根,并且
Decorview是FrameLayout子类。这一部分总算是搞清楚window/phonewindow/decorview之间的区别和联系了。
2. 接下来我们就仔细看看phoneWindow这个类中setContentView都做了什么吧。
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);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
如果是第一次调用setContentView的话,mContentParent就一定为null,这时就会调用installDecor()方法。hasFeature(FEATURE_CONTENT_TRANSITIONS)默认是返回false的,也就是说我们在Activity中可以多次调用setContentView,反正都会清空所有的子view。在16行我们会发现通过LayoutInflater将布局文件转化为view,追加到mContentParent中。
那我们先去看看installDecor()方法都做了什么呢。
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
.....方法太长
}
如果mDecor为空的话,创建一个Decor对象,这个就是界面的根,然后就是一些属性的初始化。我们看看第十一行代码。
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
// 根据在AndroidManifest.xml的theme设置style或者requestWindowFeature设置feature。然后确定layoutResource
<span style="white-space:pre"> </span>// 而这些layoutresource早就定义好的,然后通过layoutinflate创建view,add到decorview对象中去。通过findviewbyid找打id为ID_ANDROID_CONTENT的view,这个view就是我们mContentParent。
int layoutResource;
int features = getLocalFeatures();
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
....
return contentParent;
}
而我们传递过来的布局id或者是自定义的view,最后都放在了mContentParent的下边了。其他的重载setContentView都大同小异。
我们再看看phonewindow中的setContentView方法的最后部分:
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
在当前phonewindow中并没有getCallback方法。最后我们发现在父类window中有。那是谁调用了setCallback呢。我们就需要去Activity中创建mWindow时看看了,我们可以看到mWindow.setCallback(this);说明在phonewindow中onContentChanged的回调是在activity中实现的。事实上在
Activity中onContentChanged方法为空方法。
总结一下setContentView的知识点。
2.1 通过在Activity调用setContentView方法,最终都在调用phoneWindow的setContentView方法。
2.2 在phoneWindow中,我们自己的布局或者自定义的view都添加到了mContentParent下边。
2.3 在phonewindow中,是通过theme和feature来确定使用哪一个layoutresource的,确定以后,就可以将这个layoutresource生成的view添加到跟mDecor中。通过id为ID_ANDROID_CONTENT的找到mContentParent。
2.4 setContentView可以调用多次。
2.5 我们平时的一些findviewbyid都可以放在onContentChanged中实现,因为setContentView执行完之后会回调这个方法。
3. 接下来我们来看看LayoutInflater的用法。我相信做过android开发的人,这个类不可能没有用过,比如在listview中,我们将一个item对应的layout转化为一个view,添加到listview中。
先看看如何实例化LayoutInflater对象。
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
可以看出LayoutInflater就是一个系统服务。但是android也提供一个静态方法,创建实例:
/**
* Obtains the LayoutInflater from the given context.
*/
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;
}
再看看我们平时经常使用几个方法。
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(XmlPullParser parser, ViewGroup root) {
return inflate(parser, root, root != null);
}
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();
}
}
使用更多的第二、三方法。最终调用的这个方法,那么我们就重点读读这个方法:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
<span style="white-space:pre"> </span>// 生成根节点的属性
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
<span style="white-space:pre"> </span>// 判断xml布局文件的合法性
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
<span style="white-space:pre"> </span>// 如果是merge,root必须不为空,attachToRoot必须为true,否则抛异常
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, attrs, false, false);
} else {
<span style="white-space:pre"> </span>// 创建最外层的view
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, attrs, false);
ViewGroup.LayoutParams params = null;
<span style="white-space:pre"> </span>// 我们都知道在android开发时,在xml中设置一下属性时都是带layout开头,比如layout_width,表示在父布局中占有多宽。这也是为啥前边带有layout字眼的原因吧。
<span style="white-space:pre"> </span>// 如果root不为空的话,根据在xml中根节点设置的属性和root节点本身,进一步确定布局根节点的布局参数LayoutParams
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");
}
<span style="white-space:pre"> </span>// 递归生成根节点的子view
// Inflate all children under temp
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.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) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (IOException e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}
我们可以分四种情况解析:
3.1 当root != null,attachToRoot=true时,生成的temp时,会根据xml布局的属性和root创建LayoutParams,最后将root.addView(temp, params)到root中,最后返回root。
3.2 当root != null,attachToRoot=false时,生成的temp时,会根据xml布局的属性和root创建LayoutParams,并temp.setLayoutParams(params);设置到temp中,最后返回temp。
3.3 当root = null,attachToRoot=true时,生成temp,并且返回temp。
3.4 当root = null,attachToRoot=false时,生成temp,并且返回temp。
那么看一个Demo吧。在网上发现一牛人举的例子就特别好:
package com.example.test;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
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;
}
}
Actvity的代码很简单,我就不贴了。看看运行结果吧:
我的item布局有两种,textview_layout如下:
<?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="textview_layout"
android:background="#ffa0a00c"/>
textview_layout_parent如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="100dp"
android:layout_height="40dp"
android:background="#ffa0a00c"
android:text="textview_layout_parent" />
</LinearLayout>
分析这八个item出现的原因:
1. 对应我们上面说的第四种情况,就是单纯的创建view,并没有布局参数,当getview返回这个inflate转换的view时,layoutparams是为null,那么listview默认就给这个view一个宽和高,我们写死的宽高并没有生效。
2. 对应第二种情况,创建一个view,并且根据listview和布局参数,设置了layoutparams,所以,我们在布局文件中写死的宽高生效了。
3. 同1.
4. 同1,所有第三、四item写死的宽和高没有生效。
5. 对应第四种情况,生成的view没有布局参数,listview就给出默认值,但是内部的textview内部的布局参数确实可以的。
6. 对应第二种情况,能够生成布局参数,并set到view里边,所有这个view写的wrap_content是生效的。
7. 同5
8. 同5 布局中写的wrap_content是没有生效的。
通过以上的分析,算是对LayoutInflate比较了解吧。