上篇博客,讲述了一些基本的知识点,
《Android进阶从零学习自定义View——概念基础》
不熟悉的小伙伴,可以先看下上篇博客。
对于工作3-5年的开发者而言,相信绝大部分被问到过这个问题:了解自定义View吗?
这短短的一句话,你可能会说:了解啊,自定义View需要重写onMeasure,onLayout, onDraw三个方法。
没了,没了,对,没下文了, 那这个问题十有八九是跪了,不用想。
那么此时怎么回答呢? 个人觉得首先从宏观的角度点出:View树这个概念,然后再从View的源码层面一步步分析到onMeasure,onLayout, onDraw。
本篇博客就分享一下View树的绘制机制。
我们知道,View的视图结构就是:PhoneWindow ——> DecorView ——> 自己的布局,因此就延伸出来了如下图:
其实通过上篇博客学习,我们知道了,其实顶层真正的跟View是DecorView,而View树的绘制是有ViewRootImpl这个类去完成的,ViewRootImpl的主要作用是管理View树,负责将WindowManager和DecorView连接起来,每个DecorView都是有一个ViewRootImpl关联的,而他们之间是有联系是有WindowManager去管理的。
此外,我们需要记住一点,一定要牢记:每一次测量绘制,都是从根节点,一层一层测量绘制的,也就是说,先测量绘制顶部的DecorView,在测量绘制子ViewGroup,是自上而下进行遍历的。
可能有疑问了? 为什么呢?
其实我们自定义View不难,记得之前我学会自定义View之后,总感觉缺少了点什么,那种感觉不好说,反正就是少点什么,后来才知道,是缺少熟悉源码流程。
但是呢,源码说实话,看的真的是云里雾里,我看源码的心得就是:找到想要的知识点即可,点到为止,切记切记不可深陷源码!
在分享绘制树的流程之前,我们先抛出几个问题:
关于上述五个问题,我准备从第五点——分享“View添加显示的整个流程”来作为切入点。首先,Android系统中,新建window的视图有:Activity和Dialog, 那么我们就以Activity为示例,分享整个流程。
PS:Dialog是个window窗口?是不是颠覆认知了? 不理解的看这个博客:传送门
不熟悉Activity启动流程的可以看下这篇博客:传送门
首先从AWS那边发送消息说:可以创建一个Activity了,那么会进入到:ActivityThread中的performLaunchActivity方法:
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
........
//调用了Activity的attach
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
.......
//调用了Instrumentation的callActivityOnCreate方法,用来执行Activity中的onCreate回调
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
........
return activity;
}
实际上,上面的东西做了两个操作:一是执行Activity的attach方法,二是执行Instrumentation的callActivityOnCreate方法;
先看第一步: 进入到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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
//创建了PhoneWindow对象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
//设置回调对象是Activity自己
mWindow.setCallback(this);
......
//Window和WindowManager关联
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
........
}
其实在attach方法中,基本都是一些Activity的初始化操作,我们关注的是:Window对象就是在这时候创建的,Window对象其实就是PhoneWindow对象,PhoneWindow继承自抽象类Window。创建PhoneWindow之后,紧接着把Window对象和WindowManager关联了起来,那么WindowManager是什么呢?
看一下Window的setWindowManager方法:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
//这里直接创建了WindowManagerImpl对象
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
//通过静态单例的wm创建关联了PhoneWindow的WindowManager
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
实际上上面两个步骤,第一步是通过SystemServiceRegistry类中的静态代码块初始化了一个:wm, 这里需要补充下,Context的代理类是ContextWrapper,而真正的实现类是:ContextImpl。 因此在ContextImpl中可发现最终getSystemService方法进入到了SystemServiceRegistry类中,而里面包含了一个静态代码块,类加载时直接创建对象。不懂这一块的可看:传送门
第一步创建的仅仅是WindowManagerImpl仅仅是关联了Contenxt,自行查看源码可知。 那么为什么第二步又创建了WindowManagerImpl对象呢? 看一下源码:
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
这里创建的WindowManagerImpl多了个参数,那就是:初始化了Window属性,也就是说,WindowManagerImpl有了自己要管理的Window对象。
总结:通过上述源码流程分析,我们知道了,在Activity的attach方法中,首先创建了PhoneWindow对象,然后又创建了WindowManagerImpl对象,并且把二者关联起来。 至于Activity和PhoneWindow,二者也是关联了起来,因为创建Window的时候,Activity把自己传递了进去。
PhoneWindow创建完了之后,再次回到performLaunchActivity方法,可以看到紧接着调用了:Instrumentation的callActivityOnCreate方法。那么这个是干什么的呢?
既然Window和WindowManager创建好了,那么视图的真正载体:DecorView就登场啦。因为DecorView才是真正的View,才是用户能看到的视图。
其实看过Activity源码的小伙伴其实都清楚,callActivityOnCreate其实内部调用了Activity的onCreate回调。怎么知道呢?
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
//调用了Activity的performCreate方法
activity.performCreate(icicle);
postPerformCreate(activity);
}
在callActivityOnCreate方法内部,直接调用了Activity的performCreate方法,自己基本啥事没干,其实很多Activity的方法都是从Instrumentation转发的,因此,可以理解Instrumentation其实就是转发方法的这么个类,既然是转发,那么肯定应该是测试部门hook测试代码的重点对象。
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
mCanEnterPictureInPicture = true;
restoreHasCurrentPermissionRequest(icicle);
if (persistentState != null) {
//
onCreate(icicle, persistentState);
} else {
//调用了onCreate方法。
onCreate(icicle);
}
.........
}
看到这里,我们清楚了,当创建完了PhoneWindow和WindowManagerImpl之后,执行了Activity的onCreate方法,怎么还是没看见DecorView的影子的?
在onCreate方法内,是百分之百执行了setContentView方法,最终又进入到了Activity的setContentView方法内:
public void setContentView(@LayoutRes int layoutResID) {
//此处调用了Window的setContentView方法。
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
setContentView在window中是个抽象类,而实现类是PhoneWindow:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//顾名思义:安装DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
..........
}
进入到installDecor方法:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) { //初始化进入,DecorView肯定是null
//通过nenerateDecor方法创建了DecorView对象。
mDecor = generateDecor(-1);
.......
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//初始化DecorView视图
mContentParent = generateLayout(mDecor);
........
}
}
protected DecorView generateDecor(int featureId) {
........
return new DecorView(context, featureId, this, getAttributes());
}
其实上面重点是通过generateDecor方法创建了DecorView对象。首先这里的DecorView我们找到了,答案是有了,至于generateLayout方法也是很重要的,不了解的可以看下这篇博客:传送门。
好了DecorView我们有了,现在的View是不可见的,那么什么时候可见了呢? ViewRootImpl登场了。
现在View都装载好了,但是还没有显示,怎么显示呢? 还要进入到ActivityThread中:
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
........
if (r.window == null && !a.mFinished && willBeVisible) {
.........
if (a.mVisibleFromClient) { //是否已经展示给用户
if (!a.mWindowAdded) { //是否Window已经添加
a.mWindowAdded = true;
//调用WindowManagerGlobal的addView方法
wm.addView(decor, l);
}
}
........
if (r.activity.mVisibleFromClient) { //是否展示给前台
//调用Activity的makeVisible方法
r.activity.makeVisible();
}
}
......
}
这里首先调用了WindowManagerImpl的addView方法,而WindowManagerImpl的实际执行者是:WindowManagerGlobale对象,所以进入到:addView方法内:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
ViewRootImpl root;
.......
root = new ViewRootImpl(view.getContext(), display);
.......
root.setView(view, wparams, panelParentView);
.........
}
}
终于找到了,在这里创建了ViewRootImpl对象,并且直接调用了ViewRootImpl的setView方法,第一个参数就是:DecorView,换句话说,WindowManagerGlobal把DecorView传递给了ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
......
requestLayout();
.........
view.assignParent(this);
.........
}
省略很多代码 = - =, 我们需要重点关注的就两点:调用了requestLayout方法;并且调用了DecorView的assignParent方法,参数是:ViewRootImpl。这里猜想一下:为什么把自己传递给DecorView ?
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//线程检查
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
......
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
//这个方法才是我们要找的 = - =
doTraversal();
}
}
void doTraversal() {
......
performTraversals();
.......
}
终于进入到了performTraversals方法,这个方法巨长,我们的measure,layout,draw就是从这开始的。至于怎么调用的,这里不再分享。到这里我们知道了,其实真正的绘制测量是从:DecorView开始的,并且是有ViewRootImpl发起调用的。
是不是还缺少一个问题呢? 对,就是该测量绘制完了之后,该显示了。回到handleResumeActivity方法,在方法内调用了Activity的makeVisible方法:
void makeVisible() {
.......
mDecor.setVisibility(View.VISIBLE);
}
对,就是直接让视图DecorView显示!
额,问题说完了,流程也分析完了,总结一下:
首先在Activity的attach方法内,创建了PhoneWindow和WindowManagerImpl, 然后通过setContentView方法的调用创建并初始化了DecorView,并且把我们自己的视图塞进了DecorView中。此时视图是不可见的,因为还没测量绘制。因此,紧接着创建了在WindowManagerGlobal的addView方法内,创建并初始化了ViewRootImpl对象,并调用其setView方法进行测量绘制。等待测量绘制完了之后,绘制直接调用Activity的makeVisible方法,让DecorView显示出来。
其实分析到这里,可能还是有点迷糊的:为什么说View的测量绘制的从DecorView递归遍历测量绘制的呢?
这个问题,我也纠结了好久,最终我想通是通过这个方法:当我们添加一个View的时候,是不是要重新测量绘制,那么我就分析这个源码流程!
动态给ViewGroup添加View的时候,我们肯定调用过addView方法,那么添加进去之后,View就显示了出来,但是具体的源码流程是怎么样的呢?
PS:这个流程就是我们要解决的答案:为什么View的测量绘制是从根View开始的。
public void addView(View child, int index, LayoutParams params) {
//测量和放置(measure和layout方法)
requestLayout();
//绘制draw方法
invalidate(true);
}
这里调用了ViewGroup的requestLayout方法,而ViewGroup继承自View,自己又没有重写该方法,所以进入到View的requestLayout方法:
public void requestLayout() {
.......
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
......
}
这里调用了mParent的requestLayout方法,mParent是啥? 什么时候初始化的?
(⊙o⊙)…, 还记得ViewRootImpl方法吧,在源码里面有这句话:view.assignParent(this);
,上文没解释这句话,这里的view就是DecorView,而this就是ViewRootImpl。 因此我们进入到DecorView的assignParent方法中,但是发现DecorView并没有重写该方法,最后在顶层的父类View中又该方法:
void assignParent(ViewParent parent) {
......
mParent = parent;
....
}
所以调用mParent的requestLayout方法,也就是调用的ViewRootImpl的requestLayout方法,因为mParent就是ViewRootImpl,而ViewRootImpl实现了ViewParent接口,所以进入到ViewRootImpl的requestLayout方法。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
额,是不是又回来了,对,又返回到了这里,至此,我们就得到了答案:每一次测量绘制都是从顶层的根DecorView开始的
接下来就解决这个问题:为什么是从DecorView递归循环遍历测量绘制子View的?
在ViewRootImpl最终调用的是:performTraversals方法,而在该方法内,调用了:performMeasure, performLayout, performDraw这三个方法,而这三个方法对应的ViewGroup的内部是:measure,layout, draw,这里我们以performMesure为切入点分析。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
....
}
这里调用了DecorView的measure方法,我们进入查看之后,发现其并没有measure方法,那么只可能在父类中了,最终找到了在View.java中:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
.......
//
onMeasure(widthMeasureSpec, heightMeasureSpec);
.......
}
这里调用了onMeasure方法,而子类DecorView重写了该方法,因此需要查看DecorView的onMeasure方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
......
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
........
}
这里面省略了很多代码,省略的代码,之后的博客会分享; 紧接着调用了FrameLayout了onMeasure方法,注意:DecorView继承自FrameLayout:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
//这个标记,是父布局的宽高的测量模式是否有一个是:EXACTLY。
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
//这里面保存的是:子View大小要求是MATCH_PARENT的
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
//循环遍历子View
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//测量每一个子View
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
......
//将是MATCH_PARENT的子View添加到集合中,下文再次测量
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
.......
}
其实上面的代码主流程就是:遍历DecorView下的子View,并且测量每一个子View, 这是核心东西,至于为什么还要区分子View是否是match_parent,这个问题之后的博客分享。提示:往影响子View真实大小的方面考虑,也就是MeasureSpec和LayoutParams共同决定子View的大小。
继续我们的额主流程,有必要看下measureChildWithMargins这个方法是干什么, 顾名思义:测量子View包含Margin:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//获取子View的MeasureSpec
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);
// 再次调用子View的measure
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
在该方法中,主要做的就一件事:再次调用子View的measure。
好,到这里,我们重新捋一下:DecorView测量自己完了之后,开始测量子View,然后有调用了子View的measure方法,那么试想一下,如果子View也是ViewGroup,那么是不是就一直递归下去了? 答案是肯定的! 还看不懂? 那你再捋一下,静静的捋一下。
好了,到这我们也得到了答案:每次测量绘制View都是从DecorView开始的。
总结:
其实到这里,基本的流程是可以了,但是还有个细节,那就是:具体View的测量到底是怎么测量的?这个问题我放到下篇博客中讲述,因为这个问题是《自定义流式布局》的一部分,贴切实际分享,可能更容易理解。
纯手打,文中如有错误,欢迎指正。