最近看了些 View
相关的源码,相比以前,有一些新的认知。争取经过一次整理,能系统了解 Android View
加载和显示的相关过程,记录下来,共勉。接下来的全部源码基于 Android API 27 Platform
。java
对于 View
建立,通俗说其实就两种方式,一种是直接经过 new
关键词直接建立对象,另外就是经过 xml
填充一个 View
。第一种方式写起来最简易,可是,也有一些代价,好比说全部属性都要一个个设置,通用 style 也没办法使用。第二种方式最传统,也是接下来重点关注的方式。android
写过自定义 View
都知道,咱们通常须要实现三个构造方法,固然,若是你使用 Kotlin
以后,这种状况能够有一些改善,相似这样:程序员
class TestView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = -1)
canvas
第一个参数上下文,这个没啥问题,第二个参数,AttributeSet
属性集合,第三个参数,defStyleAttr
应该是默认 style 的 id。缓存
反正至少得有这三个参数,并且,通常来讲,咱们第三个参数也没怎么使用,默认使用的 -1 来占位,第二个参数通常咱们也是使用 null 来默认占位。它们到底有什么用呢?能够不写对应的构造方法吗?app
若是咱们自定义 View
,只有上下文那个构造方法时,经过 xml 方式填充时就会出错:async
Caused by: android.view.InflateException: Binary XML file line #12: Error inflating class com.lovejjfg.circle.TestView
Caused by: java.lang.NoSuchMethodException: <init> [class android.content.Context, interface android.util.AttributeSet]
复制代码
简单说就是找不到两个参数的那个构造方法,那么这个构造方法到底在哪里被调用呢?ide
###LayoutInflater 使用 xml 填充布局,就必须得使用 LayoutInflater
,等等,Activity
设置布局是经过 setContentView()
更新的,看看它的代码呢。布局
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
复制代码
/**
* 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;
}
复制代码
LayoutInflater
也是一个系统提供的远程服务。ui
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
复制代码
这个方法接收三个参数,一路点进去,首先会先经过传入的 layoutId 构建 XmlParser
:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
复制代码
XML 解析不展开说,接下来开始真正的 inflate()
方法:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
//1.AttributeSet 在这里建立出来
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
final String name = parser.getName();
//2.merge 标签的注意事项
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
//3.真正的建立方法
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// 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);
}
}
//4.建立子View
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
//5.attachToRoot 参数做用
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.
//5.attachToRoot 参数做用
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
...
} finally {
...
}
return result;
}
}
复制代码
有五个注意点,已经分别在代码中加上对应注释,第一,View
建立的第二个参数 AttributeSet
,在这个方法中被建立出来了。第二,merge
标签在这里首次现身,详细放到下面「特殊标签处理」展开讲。第三, createViewFromTag()
该方法才是真正建立 tempView
的方法。第四,rInflateChildren()
方法用于填充子 View
的方法。第五,attachToRoot
参数决定是否把 temp 直接加到 rootView
上,决定是返回 rootView
仍是填充出来的 tempView
。
接着看真正建立 tempView
的 createViewFromTag()
方法。
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
...
//1.彩蛋
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
try {
View view;
//2. 各类 factory
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);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//3.自定义View的差别
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (Exception e) {
...
}
}
复制代码
三个点,第一,竟然看到一个彩蛋, private static final String TAG_1995 = "blink"
Google 人 一下注释,你会看到这个提交地址 戳戳戳,若是解析到这个标签的话,会直接建立出 BlinkLayout
返回,blink
就是闪烁的意思,看注释 // Let's party like it's 1995!
,哈哈那种一闪一闪的感受。那么这个效果到底怎么实现的呢?直接看代码:
public BlinkLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == MESSAGE_BLINK) {
if (mBlink) {
mBlinkState = !mBlinkState;
makeBlink();
}
invalidate();
return true;
}
return false;
}
});
}
private void makeBlink() {
Message message = mHandler.obtainMessage(MESSAGE_BLINK);
mHandler.sendMessageDelayed(message, BLINK_DELAY);
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (mBlinkState) {
super.dispatchDraw(canvas);
}
}
复制代码
其实很简单,就是经过 Handler
来控制是否调用 dispatchDraw()
方法,不调用,就啥都不绘制,调用就会绘制出来,那这就是一闪一闪亮晶晶的效果咯,真是程序员的小巧思啊。
另外注意这里 Handler
的建立方式,使用的是 Callback
,并非建立一个匿名内部类,复写 handleMessage()
方法。
彩蛋说完,回归总体,第二,出现了 factory. onCreateView()
方法。并且吧,这个factory还不止一个。那这是什么操做呢?仔细看下 public interface Factory2 extends Factory
private static class FactoryMerger implements Factory2
它们是这么定义,Factory
中只有一个方法:
public View onCreateView(String name, Context context, AttributeSet attrs);
复制代码
Factory2
其实重载了一个新的方法:
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
复制代码
至于 FactoryMerger
其实就是用于咱们添加咱们指定的 Factory
去建立对应 View
。
那么问题来了,为何要整两个 Factory
呢?
看看 Factory
的具体实现类,首先有两个须要重点关注,一个是 Activity
,一个是FragmentManager
。
在 Activity
中,看到有这两个方法的实现:
/**
* Standard implementation of
* {@link android.view.LayoutInflater.Factory#onCreateView} used when
* inflating with the LayoutInflater returned by {@link #getSystemService}.
* This implementation does nothing and is for
* pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB} apps. Newer apps
* should use {@link #onCreateView(View, String, Context, AttributeSet)}.
*
* @see android.view.LayoutInflater#createView
* @see android.view.Window#getLayoutInflater
*/
@Nullable
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
/**
* Standard implementation of
* {@link android.view.LayoutInflater.Factory2#onCreateView(View, String, Context, AttributeSet)}
* used when inflating with the LayoutInflater returned by {@link #getSystemService}.
* This implementation handles <fragment> tags to embed fragments inside
* of the activity.
*
* @see android.view.LayoutInflater#createView
* @see android.view.Window#getLayoutInflater
*/
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (!"fragment".equals(name)) {
return onCreateView(name, context, attrs);
}
return mFragments.onCreateView(parent, name, context, attrs);
}
复制代码
简单理解就是,Factory
是用于低版本,高版本是 Factory2
,而后,Factory2
在 Activity
中主要用于解析 fragment
标签,其余它不 care(到这里,你可能有个疑问,Activity 实现了这个接口,可是是啥时候设置直接到 LayoutInflater 中的呢?这个问题也放下面单独讲)。
这么说下来,若是不是 fragment
标签 ,那就会到刚刚的第三点,额,战线有点儿长了,若是都已经忘记第三点就往上面翻再看下。在第三点以前,还有一个 mPrivateFactory
拦路虎,它还能够再浪一把,这个咱们也先跳过,假定到这里都没建立 View
,开始第三点。
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
复制代码
若是不包含 .
,就使用 onCreateView()
,这个方法其实就是给它把对应路径补全。使用系统控件时,咱们并无写出全路径,例如 TextView
,而咱们自定义 View
时都是写的全路径,因此就直接执行 createView(name, null, attrs)
方法。
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//1.缓存中取 Constructor
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
//2. 加载对应的 Class
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
...
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
//3.加入缓存
sConstructorMap.put(name, constructor);
}
...
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
//4.指定参数
Object[] args = mConstructorArgs;
args[1] = attrs;
//5.反射建立
final View view = constructor.newInstance(args);
//6.ViewStub处理
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;
} catch (Exception e) {
...
}
}
复制代码
看到 final
时,隐约就以为应该找到真正建立的方法。总的来讲就是经过 ClassLoader
拿到字节码,而后获得构造方法 Constructor
对象,由于反射是有额外成本消耗,因此这里有作缓存。接下来就是真正的反射建立,注意,反射建立时,使用的是两个参数的构建方法,第一个是 Context
上下文,第二个就是第一步就建立出来的 AttributeSet
,这个老将在这里终于派上用场。这也解释了开头提出那个问题,若是不指定带有 Context
AttributeSet
两个参数的构造方法,LayoutInflator
是没法建立出对应的 View
,反射建立会在这里抛出上文提到那个异常。
到这里,tempView
终于建立成功。能够先简单总结下:LayoutInflator
填充 View
的过程,第一步加载布局资源,生 XmlParser
和 AttributeSet
,而后根据不版本和不一样标签,选择是经过 Factory
的实现类去建立(fragment标签就是让Activity去建立)仍是本身建立。本身建立的话,就是经过反射,调用View
的两个参数的构造方法建立。
tempView
建立后,还要解析它的子 View
,过程固然重复相似,咱们知道在 View
建立填充完毕后中,有一个 onFinishInflate()
回调,看看它啥时候被调用。回到 inflate()
方法中的第四点,rInflateChildren(parser, temp, attrs, true)
。
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
复制代码
这个方法最后调用 rInflate()
,接下来再看看这个方法的实现。
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) {
...
final String name = parser.getName();
//1. focus 标签
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
//2. tag 标签
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
//3. include 标签
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
//4. merge 标签
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
//5. 建立 view 递归解析
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();
}
// 6.回调 onFinishInflate
if (finishInflate) {
parent.onFinishInflate();
}
}
复制代码
额,先忽略那些 if 条件,直接先看 else,以前的套路建立 View
后再递归调用 rInflateChildren()
,不过须要注意再从新调用 rInflateChildren()
时,parent
参数已是刚刚新建立的 view
啦。最后回调onFinishInflate()
方法。
接着,再说说前面的这些 if 语句,除了咱们熟悉的 include
merge
标签检查,这里竟然还有什么 tag
requestFocus
等冷门标签, 我反正有点儿震惊,层度不低于那个彩蛋。
<tag
android:id="@id/test1"
android:value="testTagValue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
复制代码
而后尝试了下 tag
标签,结果是 OK 的,我能够直接在父布局中使用 getTag(R.id.test1)
拿到我在 xml 中设置的 value
。 不过具体使用场景我着实没有想到,requestFocus
也是如此。
咱们知道,merge
标签用于减小层级,必须是顶级标签,从上面代码就能够看到对顶级标签的检测。减小层级的话,就又要回到 inflate()
方法中第二点。
//2.merge 标签的注意事项
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);
}
复制代码
若是解析到 merge
标签,会直接调用 rInflate()
方法填充下一层级,parent
参数也不会变,因此,merge
标签下面的内容直接就加到了 rootView
中。因此,这种状况,上一层确定不能为空,传入的 parent
确定不能为空。
private void parseInclude(XmlPullParser parser, Context context, View parent,
AttributeSet attrs) throws XmlPullParserException, IOException {
int type;
if (parent instanceof ViewGroup) {
...
final XmlResourceParser childParser = context.getResources().getLayout(layout);
try {
final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
...
final String childName = childParser.getName();
//1. merge 标签直接填充
if (TAG_MERGE.equals(childName)) {
// The <merge> tag doesn't support android:theme, so
// nothing special to do here.
rInflate(childParser, parent, context, childAttrs, false);
} else {
final View view = createViewFromTag(parent, childName,
context, childAttrs, hasThemeOverride);
final ViewGroup group = (ViewGroup) parent;
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.Include);
//2.include 标签上的 id
final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
//3.include 标签上的 visibility
final int visibility = a.getInt(R.styleable.Include_visibility, -1);
a.recycle();
ViewGroup.LayoutParams params = null;
try {
params = group.generateLayoutParams(attrs);
} catch (RuntimeException e) {
// Ignore, just fail over to child attrs.
}
if (params == null) {
params = group.generateLayoutParams(childAttrs);
}
view.setLayoutParams(params);
// Inflate all children.
rInflateChildren(childParser, view, childAttrs, true);
//4.覆盖 id
if (id != View.NO_ID) {
view.setId(id);
}
//5.设置可见性
switch (visibility) {
case 0:
view.setVisibility(View.VISIBLE);
break;
case 1:
view.setVisibility(View.INVISIBLE);
break;
case 2:
view.setVisibility(View.GONE);
break;
}
group.addView(view);
} finally {
childParser.close();
}
}
} else {
throw new InflateException("<include /> can only be used inside of a ViewGroup");
}
...
}
复制代码
在 parseInclude()
方法中,若是是 merge
标签,直接再次解析,而后会取出 include
标签上的 id
和 visibility
属性,若是 include
标签上面有 id
,那么会从新设置给 View
,那么以前设置的 id
就会失效,而后更新 visibility
属性。
咱们知道,ViewStub
标签是用来占位,实现 View
懒加载。那么到底实现的呢?先看代码。
...
//6.ViewStub处理
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
复制代码
根据这个代码,明显看出 ViewStub
标签和 include
或者 merge
不同,它是 View
的子类,是一个真实 View
。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(0, 0);
}
@Override
public void draw(Canvas canvas) {
}
@Override
protected void dispatchDraw(Canvas canvas) {
}
复制代码
ViewStub
默认宽高都是 0 ,draw()
(注意是 draw()
而不是 onDraw()
方法)等方法都是空实现,真就是一个壳。接着看它的 inflate ()
方法实现。
public View inflate() {
final ViewParent viewParent = getParent();
...
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
//1.填充真实布局
final View view = inflateViewNoAdd(parent);
//2.替换本身
replaceSelfWithView(view, parent);
//3.建立弱引用
mInflatedViewRef = new WeakReference<>(view);
...
return view;
}
...
}
private View inflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
...
//1.填充真实布局
final View view = factory.inflate(mLayoutResource, parent, false);
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
return view;
}
private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this);
//1.移除本身
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
//2.添加真实布局
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
}
复制代码
看完仍是那话,ViewStub
就是一个壳,先占一个坑,在调用 inflate()
以后才加载真实布局,而后替换掉本身,从而实现懒加载。说到这里,还要看一下它的 setVisibility()
方法。
@Override
@android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
public void setVisibility(int visibility) {
//1.调用 inflate() 以后 mInflatedViewRef 不为空
if (mInflatedViewRef != null) {
View view = mInflatedViewRef.get();
if (view != null) {
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
super.setVisibility(visibility);
//2.第一次设置可见时触发 inflate()
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
复制代码
第一次看到这个方法时,我在想,咱们能够直接经过 ViewStub
的 VISIBLE
GONE
来控制显示和消失啊,为何还要拿到真实布局来控制呢?后面尝试以后才意识到一个问题,上面的 replaceSelfWithView()
方法已经将本身删除,因此,当咱们调用 viewStub.setVisibilty(View.VISIBLE)
以后,viewStub
这个对象已经被置空,不能再次使用。这个想法无法实现,并且更尴尬的是,若是你直接调用viewStub.setVisibilty(View.INVISIBLE)
以后,viewStub
置空,可是你又没有真实 view
引用,你就不能直接让它再次展现出来了。是否是以为这里有个坑?其实这个时候你可使用findView查找了,因此这个坑不存在。不过这也解释了 ViewStub
为何要用弱引用来持有真实 View
。
来填一填上文 Factory
的坑,以前说到 Activity
实现了 Factory
接口,可是何时,怎么把本身设置到 LayoutInflator
中的呢?咱们直接到 Activity
的 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) {
...
mWindow.getLayoutInflater().setPrivateFactory(this);
...
}
复制代码
在 Activity
的 attach()
方法中,会调用 setPrivateFactory(this)
方法把本身设置到 Layout Inflator
中。
/**
* @hide for use by framework
*/
public void setPrivateFactory(Factory2 factory) {
if (mPrivateFactory == null) {
mPrivateFactory = factory;
} else {
mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
}
}
复制代码
看这个代码,它是设置的 mPrivateFactory
,这个优先级是最低的,前面介绍时第二点各类 factory
中,首先是 mFactory2
和 mFactory
,这两个 factory
是提供方法让咱们咱们设置更改的,不过须要注意只能设置一次,因此,先打印看看 Activity
中设置状况。
println("factory2:${LayoutInflater.from(this).factory2}")
println("factory:${LayoutInflater.from(this).factory}")
com.lovejjfg.circle I/System.out: factory2:null
com.lovejjfg.circle I/System.out: factory:null
复制代码
Activity
中,默认都没有设置,因此你彻底能够调用 setFactory()
方法设置咱们指定的Factory
来解析对应 View
。注意:上面演示时使用的是 Activity,但咱们通常不会直接继承 Activity
,由于新的 appcompat
包中的那些新控件例 Toolbar
等等,都须要使用 AppCompatActivity
搭配上 appcompat
主题。这种状况下,再看看相关日志输出。
com.lovejjfg.circle I/System.out: factory2:android.support.v7.app.AppCompatDelegateImplN@5307686
com.lovejjfg.circle I/System.out: factory:android.support.v7.app.AppCompatDelegateImplN@5307686
复制代码
已经都设置了,并且这个变量只能设置一次,设置时会有检查,因此在这种状况下,咱们基本上没办法再去设置新的 Factory
。既然它已经设置过,那么就弄明白两个问题,第一,哪里设置,第二,有什么特别的用途。也不卖关子,第一个问题,在 AppcompatActivity
的 onCreate()
方法中。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
...
}
复制代码
再贴一个 LayoutInflaterCompat
代码片断,这里强调有 framework
bug 修复,已经经过 反射 强制更新 Factory
。
/**
* For APIs < 21, there was a framework bug that prevented a LayoutInflater's
* Factory2 from being merged properly if set after a cloneInContext from a LayoutInflater
* that already had a Factory2 registered. We work around that bug here. If we can't we
* log an error.
*/
static void forceSetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
if (!sCheckedField) {
try {
sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2");
sLayoutInflaterFactory2Field.setAccessible(true);
} catch (NoSuchFieldException e) {
Log.e(TAG, "forceSetFactory2 Could not find field 'mFactory2' on class "
+ LayoutInflater.class.getName()
+ "; inflation may have unexpected results.", e);
}
sCheckedField = true;
}
if (sLayoutInflaterFactory2Field != null) {
try {
sLayoutInflaterFactory2Field.set(inflater, factory);
} catch (IllegalAccessException e) {
Log.e(TAG, "forceSetFactory2 could not set the Factory2 on LayoutInflater "
+ inflater + "; inflation may have unexpected results.", e);
}
}
}
复制代码
第二点,有什么用呢,前面提过,Activity
中,其实就判断是不是 fragment
标签,不是的话,就返回空,不操做。在 AppcompatActivity
中,createView()
会执行到 AppCompatViewInflater
中的 createView()
方法。
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
...
default:
// The fallback that allows extending class to take over view inflation
// for other tags. Note that we don't check that the result is not-null.
// That allows the custom inflater path to fall back on the default one
// later in this method.
view = createView(context, name, attrs);
}
...
return view;
}
@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
复制代码
能够看到,这里把 TextView
标签原本应该建立的 TextView
换成了 AppCompatTextView
类。
直接贴个图,简单理解,Google 官方推出 AppCompat
组件以后,新增一些新特性。出于对咱们开发者关照(让你一个个 xml 去替换估计你也不会干),因此就想出经过 LayoutInflator
中 setFactory()
这个方法直接添加本身的转换工厂,这样神不知鬼不觉的就让你的旧控件就能使用新特性(咱们就能够偷懒)。因此,在 AppCompatActivity
和 AppCompaDialog
中,不用刻意去写 AppCompatXxxView
,它会自动转换。截图中最后一句有强调,咱们只须要注意在自定义 View
时才须要额外设置继承 AppCompatXxxView
,到这里,Android Studio 给你警告的缘由也大白。
最后,再补全 Fragment
中 View
的建立过程。 前文分析 Activity
中只解析 fragment
标签。最后会调用到 FragmentManager
中的 onCreateView()
方法。
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (!"fragment".equals(name)) {
return null;
}
...
//建立 Fragment
if (fragment == null) {
fragment = mContainer.instantiate(context, fname, null);
...
fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState);
addFragment(fragment, true);
}
...
if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
moveToState(fragment, Fragment.CREATED, 0, 0, false);
} else {
moveToState(fragment);
}
...
}
复制代码
Fragment
建立不展开说了,用了反射,之后篇章有空再细聊。下面调用 moveToState()
方法,state
设置的是 Fragment.CREATED
。
//FragmentManager
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
...
f.mView = f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
....
}
//Fragment
View performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (mChildFragmentManager != null) {
mChildFragmentManager.noteStateNotSaved();
}
mPerformedCreateView = true;
return onCreateView(inflater, container, savedInstanceState);
}
复制代码
到这里,就回调咱们熟悉的 onCreateView(inflater, container, savedInstanceState)
方法,完工。