Android视图加载到显示(基于API 29)分析
LayoutInflate是如何创建的?
mLayoutInflater = LayoutInflater.from(context);
//可见,LayoutInflate是一个服务,因为要加载app里面的资源,当然需要用服务去搞事情。
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;}
常见的添加布局是如何搞的?
mLayoutInflater.inflate(layoutResID, mContentParent);
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
//这边传入的就是 id,ViewParent,true.
return inflate(resource, root, root != null); }
final Resources res = getContext().getResources();
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
//这个res的实现是ResoursesImpl,其中使用了AssertManager去获取这个布局,先确定这个布局资源是存在的,然后,加载这个资源布局
XmlResourceParser parser = res.getLayout(resource);
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);
//loadXmlResourceParser
//load这个xml资源的时候,ResourcesImpl中有一个大小为4的数组,用于缓存
//native 方法去寻找这个资源布局
final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
if (block != null) {
//缓存处理
final int pos = (mLastCachedXmlBlockIndex + 1) % num;
inflate(parser, root, attachToRoot); 已经找到这个资源文件,并且转换成XmlResourceParser,下一步,inflate
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final AttributeSet attrs = Xml.asAttributeSet(parser);
View result = root;
try {
advanceToRootNode(parser);
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
//这一步主要是生成根View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
//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);
}
}
//开始解析指定布局的xml文件
rInflateChildren(parser, temp, attrs, true);
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
//布局文件生成的View添加进根Root
root.addView(temp, params);
}
// 仅仅是根据布局文件生成View,就返回这个View
if (root == null || !attachToRoot) {
result = temp;
}
}
return result;
}
}
rInflateChildren() 调用了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;
//解析tag的循环
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();
...else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException(" cannot be the root element");
//解析我们经常使用的include标签
parseInclude(parser, context, parent, attrs);
}
else{
//解析xml文件里面的控件
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//本身外层是while循环,而这下一句,产生递归效果,因为,可能这个节点是一个ViewGroup,那么就需要进去遍历
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
//注意这个onFinishInflate(),它代表了布局解析完毕,自定义ViewGroup,有时就会用到这个方法。
if (finishInflate) {
parent.onFinishInflate();
}
}
createViewFromTag()
View view = tryCreateView(parent, name, context, attrs);
//以下为tryCreateView()的代码
if (mFactory2 != null) {
//这个Factory为LayoutInflate的一个接口,返回的是个View,也就是更具名字生成View,具体怎么生成,这个过程交给了这个工厂,我们去找一下在哪里实现的
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
return view;}
寻找Factory在哪里初始化进来的
//同时我们在LayotInflate中还发现了setFactory()和setFactory2()来设置的方法。
public void setFactory2(Factory2 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;
找了不一会儿,我们发现AppCompatActivity,里面的:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
//这里installViewFactory()
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
//在onCreate之前设置Factory, 所以,你想根据自己的规则创建View,你需要在onCreate()的super之前设置就没问题了。
super.onCreate(savedInstanceState);
}
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
//为空才设置,也就是我们自己可以创建自己的Factory,自己去创建View
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}}}
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
//创建了 AppCompatDelegateImpl
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
//而这个AppCompatDelegateImpl实现了LayoutInflater.Factory2接口
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return createView(parent, name, context, attrs);
}
onCreateView中创建了一个类:AppCompatViewInflate,并且 mAppCompatViewInflater.createView(),来到这个createView看一看:
switch (name) {
case "TextView":
// new AppCompatTextView(context, attrs),new 出来了我们需要用的TextView
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
........
//创建我们的自定View,所以自定义View需要写上全路径,因为需要用到反射。
if (view == null && originalContext != context) {
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check its android:onClick
checkOnClickListener(view, attrs);
}
return view;
那么解释我们经常使用LayoutInfalte.inflate(R.layout.xxx,root,true/faalse)第二个参数和第三个参数的意义下面这几行代码就差不多了:
// 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;}
到此,我们的setContView就差不多了,
分析了传入布局
生成DecorView
将布局使用LayoutInflate.inflate添加进R.id.content的一些过程。
View显示过程。假装你已经知道Activity的生命周期,并且也知道代码在哪里执行。那我们直接来到应用入口点的那个类。
小明同学,请等等····是哪个类??呜,那就告诉你吧,是ActivityThread
来到执行resume的方法:handleResumeActivity()
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
//onNewIntent(),以及调用Activity的resume()生命周期函数,都在下面这个方法执行
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
...
//获取到PhoneWindow,
r.window = r.activity.getWindow();
//获取DecorView
View decor = r.window.getDecorView();
//decorView设置为不可见
decor.setVisibility(View.INVISIBLE);
//拿到windowManagerImpl
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
...
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//WindowManagerImpl的addView方法,传入参数为decorView,和Window的属性
//故事就从这里开始了
wm.addView(decor, l);
...
//调用Activity里面的makeVisisble()使得视图可见
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
//你说了解Handler,那来说说IdleHandler吧。
Looper.myQueue().addIdleHandler(new Idler());
ok,可以看到上面一系列“熟悉”的流程。我们重点关注上面的wm.addView(DecorView,WindowManager.LayoutParams). 进入WindowManagerImpl.
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
//单例的WindowMagerGlobal.
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerGlobal
//创建ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//集合缓存
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
//调用ViewRootImpl的setView方法:
root.setView(view, wparams, panelParentView);
来到ViewRootImpl的setView,很快我们看到了一个名为requestLayout的方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
//上面的checkThread
void checkThread() {
还是那个熟悉的提示,原来这么多年来,提示非主线程不能更新Ui的提示,都在这里孤孤单单,今天我终于来看她了。
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
//上面的scheduleTraversals()
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}}
注意这个mTraversalRunnable
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal(); //调用了doThrversal()
}}
doThaversal()中调用了performTraversals() 好的,我们的故事开始了~~
performTraversals中一直往下走,走啊走,你会看到一行代码:
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
再走:
performLayout(lp, mWidth, mHeight);
再接再厉
performDraw();
完毕之后,我们看
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//这个mView就是DecorView
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
DecorView也是继承自View,来到View中的measure(),我们看到:
调用了onMeasure,decorView的父类是FragmLayout
onMeasure(widthMeasureSpec, heightMeasureSpec);
FrameLayout##onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//注意到,开始循环遍历ViewGroup里面包含的子View
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
//不为Gone的不去测算,为InVISibility也需要测算的
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);}
.......
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
measureChildWithMargins()
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//拿到孩子写在布局里面的宽高属性
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//测算子孩子的大小
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//看到再次调用child.measure(),measure中会调用onMeasure()
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}
由父类和子类确定子类的模式和大小。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
父布局是Exactly
子布局如果为AT_MOST(wrap_content), 那么模式为AT_MOST,其他都是Exactly父布局是AT_MOST
子View写为准确值(100dp),那么模式是Exactly
子View是wrap_content或match_parent,都是AT_MOST模式。
上面两句话读起来有点晦涩难懂,记住Exactly是已经确定了,AT_MOST就是待计算。
父类确定(Exactly)了,子类确定(100dp和match_parent)那么子类也是确定(Exactly)
父类不确定(AT_MOST), 子类确定(100dp)那么是Exactly,否则,子类都不确定。
举一个小栗子,就是ScrollView包裹ListView ,你会发现只显示了一条数据。网上给出的解决方案很多都是: 计算ListView 的高度,然后更改List的LayoutParams。竟然把所有的ListView所有的孩子都拿来累加高度。
这个问题是因为ScrollView,传过来的模式是:UNSPECIFIED,ListView中的onMeasure,对于UNSPECIFIED的处理是:
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
所以正确的打开方式:
//继承一下ListView,然后,重写onMeasure()方法:
val height = MeasureSpec.makeMeasureSpec(heightMeasureSpec, MeasureSpec.AT_MOST)
super.onMeasure(widthMeasureSpec, height)
另外,我们还看到有performLayout()和performDraw()方法,调用也都是大同小异,完成各自的功能。
performMeasure – > measure – > onMeasure
performLayout – > layout – > layout
performDraw – > draw – > drawbackground – > ondraw.
作者:聪明的殷先生
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341