深入理解View知识系列二- View底层工作原理以及View的绘制流程

Posted 刘镓旗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解View知识系列二- View底层工作原理以及View的绘制流程相关的知识,希望对你有一定的参考价值。

一般我们都知道一个View到展示出来会经过onMeasure、onLayout、onDraw三个方法,但是在分析完了setContentView后发现这几个方法都还没有执行,这篇将会上一篇的基础上继续分析View的工作原理

深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

深入理解View知识系列二- View底层工作原理以及View的绘制流程

深入理解View知识系列三-Window机制、Canvas的由来、Android事件的由来

深入理解View知识系列四-View的测量规则以及三大方法流程


本篇你会学到什么?

  • Activitiy是在哪里开始准备显示View的
  • View的三大方法是在什么时候开始执行的,又是再哪里被调用的
  • 我们经常使用的View.post后就可以获取到View的宽高,为什么呢?
  • View的requestLayout,invalidate请求重绘的原理
  • 子线程真的不能更新ui吗?为什么

上一篇回顾

我们带着问题在上一篇的基础上继续分析,View的绘制流程及工作原理,在正式分析之前我们先回顾一下上一篇的内容.

setContentView

  • 我们知道了Activity的三个setContentView方法内部全部调用了getWindow.setContentView()
  • Activity的getWindow类型是PhoneWindow,而且也是Window的唯一实现类,PhoneWindow在Activity的attach方法中被初始化,并且设置了一些回调接口指向自己,例如Window的Callback接口,这个接口中有不少我们熟悉的方法,例如dispatchTouchEvent等。
  • 在PhoneWindow的setContentView方法中主要执行三个逻辑

1.判断装在我们设置布局的FrameLayout是否为空,如果为空调用installDecor方法,方法中首先会先执行generateDecor()创建DecorView,接着执行generateLayout()方法设置一些Window的样式,根据样式来选择需要加载的布局,然后将布局添加到DecorView中,最后找到id为content的FrameLayout,也就是来装我们设置布局的父View。

2.通过LayoutInflater将我们设置的布局添加到id为content的FrameLayout中

3.回调Callback接口的onContentChanged()方法,这个方法在Activity中是个空实现。

LayoutInflater

  • PhoneWindow中的LayoutInflater是在构造函数中被创建,创建的方式和我们平时使用一样调用的是LayoutInflater.from(content);
  • LayoutInflater.from方法中就是封装了context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  • LayoutInflater中一共有4个inflater方法,其中分为两类,一类是传入资源id的,一类是传入XmlPullParser的,但是最终都会执行带xml解析器的三参方法中,这里注意第三个参数源码中传入的是root!=null,该参数代表了是否将加载后的View添加到父View中,如果使用了两个参数的inflater,并且传入的父view不为空,则默认会将加载后的布局添加到父view,并最后返回的是父view。
  • inflater方法中首先会获取到xml的跟节点,接着判断该节点是否为merger标签,如果是merger标签则继续判断传入的父view是否为空,或者第三个参数是否为flase,如果满足一项则直接抛出异常,因为merger标签只是为了减少布局的嵌套才存在,他并不是View的子类,所以不能单独存在,反之则会执行rInflate方法进行递归加载所有子View。
  • 如果不是merger标签则会走到else方法中,先活着节点的名称,根据名称创建View,在创建View的逻辑中最终全部会调用createView方法,在这之前首先会判断是否存在自定义的加载工厂,如果存在则调用加载工厂的创建View方法,接着通过名称中是否包含 . 来判断是否为android自带的控件,如果是Android自带的控件第二个参数中传入的值为android.view.,反之传入null
  • 在createView方法中创建View的时候会执行如下逻辑

1.首先会判断这个名称的构造函数是否存在,如果存在则还需要验证这个函数所属的ClassLoader是否合法,如果不合法则会置空这个构造函数并清除缓存。

2.接着继续判断构造函数是否为空,如果为空则会通过一个三元运算来加载这个名称的Class对象,这个三元运算主要是用来判断是否需要拼接要加载Class的名字。

3.然后会判断是否存在Filter过滤器,这个过滤器是用来判断是否可以创建这个Class的View对象,通过查看设置过滤器的RemoteViews和AppWeightHostView都是通过clazz.isAnnotationPresent(RemoteView.class)来判断的,如果返回为flase则执行抛出异常。

4.或者该Class的构造函数并存入HashMap中

5.在else逻辑中,主要是用来优化存在过滤器的情况,为了优化性能,系统只会在第一次的时候去验证是否允许创建这个View,并将结果存入HashMap中,下次只会取map中结果进行判断。

6.通过构造函数实例化这个View并返回

  • 在创建了跟节点的View后,接着同样会递归加载所有的子view,在加载的过程中会根据不同的标签进行不同的操作,例如merger、include。如果是merger则直接抛出异常,因为merger只能作为根节点。如果是include则会拿到layout属性设置的布局文件,如果未设置这个属性执行抛出异常,接着也是根据标签进行递归加载逻辑类似。
  • inflater最后根据传入的父view和第三个参数的值进行判断是返回父view还是返回刚加载的View

好了,回顾完了,在上一次讲的setContentView过程中只是初始化了PhoneWindow、DecorView并将我们设置的View入到了id为content的FrameLayout中,这时Activity的界面还是不可见的,因为View还没有开始绘制的流程呢,那么Activity中DecorView绘制的流程到底在哪里开始的呢?其实是在ActivityThread中的handleResumeActivity()方法中,下面正式开始分析。

本篇源码基于Android 7.1.1

1.逻辑开始点:ActivityThread中的handleResumeActivity()

这里又提到了ActivityThread这个类,简单说一下这个类,要知道所有的程序都是需要一个入口的,Android也不例外,ActivityThread就是Android应用的入口类,Activity、Service、ContentProvider几乎都直接或者间接在这里调度。现在暂时知道这么多就好了,先不要纠结这个类,后续的会专门在四大组件系列去讲。

源码位置:/frameworks/base/core/java/android/app/ActivityThread.java

    final void handleResumeActivity(IBinder token,
unscheduleGcIdler()
;
mSomeActivitiesChanged = true;
//ActivityClientRecord 在AMS中代表一个Activity,这个方法中会调用
//Activity的onResume方法
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null)
//得到Activity
final Activity a = r.activity;
if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);
final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
。。。
if (r.window == null && !a.mFinished && willBeVisible)
//获取Activity中的Window,也就是PhoneWindow
r.window = r.activity.getWindow();
//获取PhoneWindow中的DecorView
View decor = r.window.getDecorView();
//设置DecorView隐藏
decor.setVisibility(View.INVISIBLE);
//获取Activity的WindowManager
ViewManager wm = a.getWindowManager();
//获取PhoneWindow的参数
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
//设置展示类型
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient)
//标记添加了
a.mWindowAdded = true;
//在Window中添加DecorView
wm.addView(decor, l);

else if (!willBeVisible)
if (localLOGV) Slog.v(
TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;

if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow)
...
if (r.activity.mVisibleFromClient)
//设置DecorView显示
r.activity.makeVisible();


...
else
try
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null, false);
catch (RemoteException ex)



2. 上面的代码中主要执行如下了三个逻辑。

  1. 首先调用执行了performResumeActivity方法,在这里方法中会调用Activity的onResume方法

  2. 获取Activity的PhoneWindow,接着拿到PhoneWindow中的DecorView并设置为隐藏,获取Activity的中WindowManger并执行addView方法将DecorView添加到Window中。

  3. 执行Activity的makeVisible方法展示DecorView,方法中就是调用了View.VISIBLE

而且我们通过上面的可以总结两个问题。

  1. 其实在Activity的onResume方法执行了以后才开始将DecorView添加到Window中,换句话说,也就是在onResume方法中是不能直接获取View的宽度等参数的,因为这个时候连DecorView都没有添加到Window中呢,所以这时也还没有执行View的三大流程呢,又如何产生这是东西。

  2. DecorView最终也是通过WindowManager来添加的,或者说Activity可以显示内容其实也是通过WindowManager添加View来实现的,其实Android中所有设计View展示的最终全部都是通过WindowManager的addView来添加的,例如PopupWindow、Dialog、Toast等,下一篇会详细分析

3.在上面的代码中,使用了ViewManager执行了addView方法,在上一篇我们简单的说过了,WindowManger是继承自ViewManger的,而且WindowManger中的addView、updateViewLayout、removeView全部都是ViewManger中的方法,下面先简单看一下这两个类的声明

//ViewManager
public interface ViewManager

public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);

//WIndowManager
public interface WindowManager extends ViewManager
....

4.我们可以看到WindowManager也是一个接口,它的具体实现是WindowMangerImpl,那么我们继续上面的逻辑去看一下它的addView方法,顺便看一下ViewManger中的其余两个方法

源码位置:/frameworks/base/core/java/android/view/WindowManagerImpl.java

public final class WindowManagerImpl implements WindowManager 
//WindowManagerGlobal是单例的
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
//addView
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params)
applyDefaultToken(params);
深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

深入理解View知识系列一- setContentView和LayoutInflater源码原理分析

深入理解View知识系列四-View的测量规则以及三大方法流程

深入理解View知识系列四-View的测量规则以及三大方法流程

深入理解View知识系列三-Window机制Canvas的由来Android事件的由来

深入理解View知识系列三-Window机制Canvas的由来Android事件的由来