源码分析android 系统framework(二)之view的布局加载流程
我们基本写android UI都是采用xml的方式来写的,那么我们写的xml到底是怎么展示到屏幕上的,今天我们就来看下这个流程。
在上一篇文章中介绍了window,view和activity的关系。知道最后我们在activity中的setContentView最终是调用到PhoneWindow 的setContentView。我们今天就从这看起:
@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);
// 这里这个方法就是将资源文件,转换为view,这个方法大家应该比较熟悉,在fragment中应该经常用到。
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
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) + ")");
}
final XmlResourceParser parser = res.getLayout(resource); // 1
try {
return inflate(parser, root, attachToRoot);// 2
} finally {
parser.close();
}
}
以上1处,我们看到是在解析xml,2处才是真正加载创建加载view
@NonNull
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
@NonNull
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return impl.loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ " type #0x" + Integer.toHexString(value.type) + " is not valid");
} finally {
releaseTempTypedValue(value);
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
...............
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException(" 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);
............
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
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();
}
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
try {
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;
}
// 这里这两个mFactory2和Factory比较重要,可以看到加载时首先是会通过它们两来创建view,
//如果他们两都会空的话,则才会执行下面的onCreateView。这两个比较重要,
//我们可以通过他两来搞一些事情,这个后面再说
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
public interface Factory {
/**
* Hook you can supply that is called when inflating from a LayoutInflater.
* You can use this to customize the tag names available in your XML
* layout files.
*这里的注释,hook这个单词已经道破了所有
*
* Note that it is good practice to prefix these custom names with your
* package (i.e., com.coolcompany.apps) to avoid conflicts with system
* names.
*
* @param name Tag name to be inflated.
* @param context The context the view is being created in.
* @param attrs Inflation attributes as specified in XML file.
*
* @return View Newly created view. Return null for the default
* behavior.
*/
public View onCreateView(String name, Context context, AttributeSet attrs);
}
public interface Factory2 extends Factory {
/**
* Version of {@link #onCreateView(String, Context, AttributeSet)}
* that also supplies the parent that the view created view will be
* placed in.
*
* @param parent The parent that the created view will be placed
* in; note that this may be null.
* @param name Tag name to be inflated.
* @param context The context the view is being created in.
* @param attrs Inflation attributes as specified in XML file.
*
* @return View Newly created view. Return null for the default
* behavior.
*/
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
*/
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
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 = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, 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 = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
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]));
}
mConstructorArgs[0] = lastContext;
return view;
..................
}
构造函数一路往下。我们从上面的createView来看,最后的 view的由来是通过反射的方法拿到的,然后拿到具体的view后,就是view自己ondraw ,onlayout来画了。这部分就不说了。到此view的加载流程我们就清楚了。这里有几点思考:
我们从上面看到加载xml布局文件时,有几个耗时操作:
解析xml IO耗时
反射获取view 对象耗时
故如果想要在view的加载上提升下性能,可以使用直接new 的方式,例如,new TextView()。我们公司的大部分UI代码都是new 出来,我起初看到一个android 工程中没有xml 布局文件。差点都要崩溃了。
虽然我们追求了性能,但是对于代码的可读性,以及我们的维护来说又反而太差了。所以就又了下面牛逼的一个 库,我们通过xml写布局,然后这个库默认帮我们转成new 的方式。真是叼炸天了。
推荐x2c框架
如下,还是上面中那个代码。我们可以看到,我们布局中的每一个view,都需要先经过Factory2,然后再经过factory,最后再是默认的CreatView,但是前两个默认是空的。所以呢,我们就可以自己去实现这个方法。然后每次当加载系统默认的ui 组件的时候,都会先走我们自己的creatView.
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;
}
所以我们可以如下:
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
//这里说一下,我们为什么要用LayoutInflaterCompat,因为后面加Compat的基本都是兼容包,考虑到兼容,我们就用这个,然后我们这里用Factory2。另外,一定要在super.onCreate() 上面
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
Log.d("niubi", "s:" + name);
int n = attrs.getAttributeCount();
for (int i=0;i<n;i++){
Log.d("niubi",attrs.getAttributeName(i)+attrs.getAttributeValue(i));
}
return null;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_appcompart);
}
}
我们随便写了个布局文件activity_appcompart.xml。然后从从上面的打印如下:
D/niubi: s:LinearLayout
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: orientation1
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: fitsSystemWindowstrue
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_height-1
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: s:ViewStub
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: theme?16843825
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: id@16909288
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout@17367070
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: inflatedId@16909289
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_height-2
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: s:FrameLayout
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: id@16908290
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_height-1
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: foreground?16842841
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: foregroundGravity0x37
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: foregroundInsidePaddingfalse
2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.ActionBarOverlayLayout
2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: id@2131165243
2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: fitsSystemWindowstrue
2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: layout_height-1
2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.ContentFrameLayout
2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: id@2131165191
2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: layout_height-1
2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: foreground?16842841
2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: foregroundGravity0x37
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.ActionBarContainer
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: gravity0x30
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: id@2131165192
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: layout_height-2
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: layout_alignParentToptrue
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: touchscreenBlocksFocustrue
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: style?2130837509
2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.Toolbar
2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: id@2131165190
2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: layout_height-2
2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: navigationContentDescription@2131427329
2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: style?2130837813
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.ActionBarContextView
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: theme?2130837513
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: id@2131165198
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: visibility2
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: layout_height-2
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: style?2130837531
2020-05-29 22:50:40.574 2170-2170/com.xct.codebase D/niubi: s:LinearLayout
2020-05-29 22:50:40.574 2170-2170/com.xct.codebase D/niubi: layout_width-1
可以看到每一个view 控件都会传到这个回调方法里来。所以我们可以在这个方法写去重写想要替换的view,例如,如果想要将自己UI中所有的textView 全局替换成自己的自定义view。则可以直接在这个方法里面去new 自己定义的view。
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
switch(name){
case "TextView" :
return new CustomTextView(context,attrs)
}
}
});
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_appcompart);
}
}