写点什么

【面试总结】Android- 开发者值得深入思考的几个面试问答分享

用户头像
Android架构
关注
发布于: 14 小时前

public boolean dispatchTouchEvent(MotionEvent ev) {// ...


final ArrayList<View> preorderedList = buildTouchDispatchChildList();final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();final View[] children = mChildren;for (int i = childrenCount - 1; i >= 0; i--) {final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);...}


如果传进去的 preorderedList 不为空,那么就会直接从它里面去取。


####preorderedList 怎么来?


通过调用 buildOrderedChildList 方法获取的。

buildOrderedChildList 方法是怎么样的?

ArrayList<View> buildOrderedChildList() {final int childrenCount = mChildrenCount;if (childrenCount <= 1 || !hasChildWithZ()) return null;


if (mPreSortedChildren == null) {mPreSortedChildren = new ArrayList<>(childrenCount);} else {// callers should clear, so clear shouldn't be necessary, but for safety...mPreSortedChildren.clear();mPreSortedChildren.ensureCapacity(childrenCount);}


final boolean customOrder = isChildrenDrawingOrderEnabled();for (int i = 0; i < childrenCount; i++) {// add next child (in child order) to end of listfinal int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View nextChild = mChildren[childIndex];final float currentZ = nextChild.getZ();


// insert ahead of any Views with greater Zint insertIndex = i;while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {insertIndex--;}mPreSortedChildren.add(insertIndex, nextChild);}return mPreSortedChildren;}


它里面是通过一个 getAndVerifyPreorderedIndex 方法来获取对应的子 VIew 索引,这个方法要传进去一个叫 customOrder 的 boolean。


这个 customOrder,看名字可以知道,是自定义顺序的意思,如果它为 true 的话,接着会通过 getChildDrawingOrder(int childCount, int i)方法来获取对应的索引,而且,这个方法是 protected 的,所以我们可以通过重写这个方法并根据参数"i"来决定返回哪一个 View 所对应的索引,从而改变分发的顺序。


protected int getChildDrawingOrder(int childCount, int i) {return i;}


那这个 customOrder,什么时候为 true 呢?


在 buildOrderedChildList 方法里可以看到这么一句:


final?boolean?customOrder?=?isChildrenDrawingOrderEnabled();


emmmm,也就是说,如果要自定义这个顺序的话,还需要调用 setChildrenDrawingOrderEnabled(true)来开启。


重新捋一捋流程:


1. setChildrenDrawingOrderEnabled(true)来开启自定义顺序;2. 重写 getChildDrawingOrder 方法来决定什么时候要返回哪个子 View;


###2.?AppCompatTextView 与 TextView 有什么区别?


1. compat 库是如何将 TextView 替换为 AppCompatTextVew 的?2. 为什么要进行替换?3. 根据替换相关原理,我们可以做哪些事情?


####先从第二问开始吧:


AppCompatTextView 继承自 TextView,是对 TextView 的一种扩展,因为在 5.0 中首次推出了 MaterialDesign 这种设计风格。


但是众所周知的,5.0 推出不可能所有的设备全都一下子更新到最新版本,为了在早期版本上实现新的功能(这些新功能比如从源码注释中解读到比如 backgroundTint 属性,根据文本内容自适应大小等).


即为了新特性同样可以兼容老版本,framework 在创建 TextView 实例的时候,自动帮我们进行了替换。


其它的 AppCompatXXX 与 XXX 的关系也是如此。


####第一问:


然后第一问,如何完成替换的,我们这里只拿最直观的流程举例,且尽可能的简化源码过程,在讨论这个问题之前,先了解几个预备知识:


View 是怎么被解析创建出来的:


**1.LayoutInflater:**将布局 XML 文件实例化为其对应的 View 对象,我们在 Activity 中通过 setContentView 传入一个 Layout 的资源文件 id,最终该方法最终会调用到 PhoneWindow 的 setContentView 方法,这个方法里面有调用到


mLayoutInflater.inflate(layoutResID,?mContentParent);


2.inflate 方法,该方法的作用是将指定的 XML 文件填充到 View 的层次结构中去,最终无论通过什么途径调用到 inflate 方法,都会走到三个参数的重载方法这里:


return?inflate(parser,?root,?attachToRoot);


parser 你可以认为持有将 Layout.XML 解析后的数据。后两个参数的意义如下:


1. root 为 null,attchToRoot 无意义,inflate 返回的是当前 XML 对应的根布局。2. root 不为 null 且 attachToRoot 为 true,则整个 XML 对应的布局就设置了根布局是 root。3. root 不为 null 且 attachToRoot 为 false,则会将 root 的 layoutParames 设置给当前 XML 的布局。


知道了 LayoutInflate.inflate 做了什么,再往下,inflate 中会调用 createViewFromTag,从方法名就能知道,继续往下走,我们离答案越来越近了。


createViewFromTag 做的事情非常有意思:



先看到 787 行这个 if-else,条件是 name 中有没有"."字符,如果有我们会执行 onCreateView,如果没有会执行 createView。


####name 啥时候有点?


自定义控件的时候。


当是系统控件的时候,createView 会有一个填充了第二个参数的调用:


createView(name, "android.view.", attrs);补上了 View 控件的全路径名,而自定义控件则不需要,因为传入的 name 就是一个全路径名。


####为什么要全路径名?


因为 View 控件对象的创建是通过反射来实现的:


clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.cla


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


ss);...constructor = clazz.getConstructor(mConstructorSignature);constructor.setAccessible(true);sConstructorMap.put(name, constructor);// ...args[1] = attrs;final View view = constructor.newInstance(args);


下面对这几步做一个总结:


XML 中保存了 ViewTree 的结构和 View 的相关标签信息(包括 View 的类型和一些属性值),然后这些信息会在后面通过反射的方式(如果没有 Factory2 和 Factory 的话)创建实例对象,如果创建的是 ViewGroup,则会对它的子 View 遍历重复创建步骤,创建完 View 对象后,会 add 到对应的 ViewGroup 中。


其中相关方法的调用流程是:


inflate->rInflate->createViewFromTag->createView。


好像还是没有看到替换?


还是上一张图,我们只解释了后半部分,没有解释前半部分,那么什么是 Factory?


继续往下看:


createViewFromTag 中会先判断有没有 Factory 或者 Factory2 的对象,如果有,则调用 Factory 的 onCreateView 方法。


这两个类都是接口,其中 Factory2 是 Factory 的子接口,都只有唯一一个 onCreateView 方法。


不同之处在于 Factory2 的 onCreateView 方法传入了 parentView。


该方法的作用就是你可以借助它来改造 XML 中已经存在了的 Tag 的值。所以 Factory2 可以达到改造 parentView 的目的。


但是我们在日常中根本就没有任何地方接触到了 Factory(2)呀,那么它是不是就直接是 null 呢?


到这里又是一番源码调来调去,为了便于理解,只需要知道,这个东西(Factory2),在最开始 AppCompatActivity(为了兼容低版本,我们现在 Activity 默认都是继承自它)中的 onCreate 方法中就已经通过层层调用被设置好了。


既然现在 Factory2 不为空,那么就应该去走它的 onCreateView 方法了,这里又是层层调用,最终来到了 AppCompatViewInflater****?的 createView 方法:


答案就在这里:



如果创建的是非兼容控件(系统控件那么多,实现兼容的只是常用的一些控件),那么就会是 143 行,在 146 中通过反射创建 View 对象。


啰里啰唆扯了一大堆,还是没回答第一个问题:


####compat 库是如何将 TextView 替换为 AppCompatTextVew 的?


个人对这个的理解:在将 XML 文件解析成包含 ViewTree 信息之后,开始利用这些信息去创建每一个 View 节点,在创建 View 对象的时候,如果发现这个节点是属于支持兼容的控件比如 TextView,那么就会去调用到 new AppCompatTextView()来创建一个兼容的 View 对象,也就是在创建的时候,及已经实现了替换。

第三问:

根据替换相关原理,我们可以做哪些事情?


整个替换从图一所示的源码中可以看到,能够被替换的关键是 Factory(2)存在,那么我觉得,其实问题问的是 Factory(2)可以用来做什么吧?


那么这个时候,就适合去问站长大人了:



###3.?getWidth, getMeasuredWidth 有什么区别?


getWidth 和 getMeasuredWidth 的区别:

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
【面试总结】Android-开发者值得深入思考的几个面试问答分享