【面试总结】Android- 开发者值得深入思考的几个面试问答分享
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
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 的区别:
评论