Android 面试题总结之Android 进阶(二)

Android 之美 从0到1 之Android 进阶(二) 在上一章节中《Android 之美 从0到1 之Android 进阶(一)》中我们已经理解了一些View的基本知识并且知道如何自定义View。那么本章节将继续深入理解View,关于View的绘制流程,View的事件分发。刷新机制等等。 在阅读过程中有任何问题,请及时联系。如需转载请注明 fuchenxuan blog
摘要由CSDN通过智能技术生成

Android 之美 从0到1 之Android 进阶(二)

在上一章节中《Android 之美 从0到1 之Android 进阶(一)》中我们已经理解了一些View的基本知识并且知道如何自定义View。那么本章节将继续深入理解View,关于View的绘制流程,View的事件分发。刷新机制等等。

在阅读过程中有任何问题,请及时联系。如需转载请注明 fuchenxuan blog
本章系《Android 之美 从0到1 – 高手之路》Android 深入理解View的绘制流程。

掌握

  1. Window是什么?
  2. View的绘制流程
  3. View的事件分发机制
  4. View 与SurfaceView,GLSurfaceView

View的绘制流程

上一文章中我们已经自定义View以及View的三大过程,基本操作由三个函数完成:measure()、layout()、draw(),其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法

Android 之美 从0到1

在理解View的绘制流程之前我们应该知道这几个类:

  • View:最基本的UI组件,表示屏幕上的一个矩形区域。
  • Window: 是一个抽象基类,作用于外观用户界面和行为策略表示一个窗口,它包含一个View tree和窗口的layout 参数。View tree的root View可以通过getDecorView得到。还可以设置Window的Content View。其实现类是PhoneWindow。Activity,Dialog,Toast,都包含一个Window,该Window在Activity的attach()函数中mWindow = new PhoneWindow(this);创建。
  • DecorView:该类是PhoneWindow类的内部类,继承自FrameLayout,它是所有应用窗口的根View,PhoneWindow设置DecorView为应用窗口的根视图。
  • PhoneWindow:PhoneWindow对象帮我们创建了一个PhoneWindow内部类DecorView(父类为FrameLayout)窗口顶层视图
  • ViewRootImpl:ViewRootImpl是连接WindowManager与DecorView的纽带,View的整个绘制流程的三大步(measure、layout、draw)以及我们一些addView()的操作,都是通过ViewRootImpl完成的。
  • WindowManager:应用程序界面和窗口管理器
    Android 之美 从0到1
    在Activity onCreate使用的setContentView()就是设置的ContentView,通过LayoutInflater将xml内容布局解析成View树形结构添加到DecorView顶层视图中id为content的FrameLayout父容器上面。

    那么DecorView是如何绘制的呢?我们分两个步骤来理解:

    • DecorView添加到Window的过程
    • DecorView的绘制过程

DecorView添加到Window的过程

我们根据下图步骤来解析DecorView添加到Window的过程,以便让我们更容易的理解。

Android 之美 从0到1

  1. **Activity初始化:**Activity 启动,关于Activity的创建过程啊或者其他细节,因为不是本篇幅重点故不做详细讨论。我们尽量简化理解View的绘制流程。
  2. PhoneWindow的创建:
    Activity对象创建完成后,初始化了PhoneWindow对象,该Window在Activity的attach()函数中mWindow = new PhoneWindow(this);创建,相关代码块如下:

    final void attach(Context context, ActivityThread aThread..){
        ..........
       mFragments.attachHost(null /*parent*/);
                //创建PhoneWindow对象
            mWindow = new PhoneWindow(this);
            mWindow.setCallback(this);
            mWindow.setOnWindowDismissedCallback(this);
            mWindow.getLayoutInflater().setPrivateFactory(this);
                ..........
      }
    
  3. DecorView添加Window:
    ActivityThread.java类会调用handleResumeActivity方法将顶层视图DecorView添加到PhoneWindow窗口,因此通过PhoneWindow的setContentView将Activity与Window进行关联了。

    final void handleResumeActivity(IBinder token,
                boolean clearHide, boolean isForward, boolean reallyResume) {
                 //获得当前Activity的PhoneWindow对象
                  r.window = r.activity.getWindow();
                   //获得当前PhoneWindow内部类DecorView对象
                    View decor = r.window.getDecorView();
                      //设置DecorView为可见
                    decor.setVisibility(View.INVISIBLE);
                    //获取Activity的WindowManager
                    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.mVisibleFromClient) {
                            //标记已添加至Window
                        a.mWindowAdded = true;
                        //添加DecorView到Window
                        wm.addView(decor, l);
                    }
    
                }
    

    接着DecorView通过WindowManager设置到ViewRootImpl中,然后就是下面DecorView的绘制流程了。

因此我们知道在Activity的onCreate和onResume方法中调用View.getWidth()和View.getMeasuredHeight()返回值是0,因为View 还没有开始绘制。

View的绘制过程

ViewRootImpl是连接WindowManager与DecorView的纽带,View的整个绘制流程的三大步(measure、layout、draw)都是通过ViewRootImpl完成的,
绘制是从根节点开始,对布局树进行 measure 和 draw 。整个 View 树的绘图流程在 ViewRootImpl.java 类的 performTraversals() 函数展开,该函数所做 的工作可简单概括为是否需要重新计算视图大小(measure)、是否需要重新安置视图的位置(layout)、以及是否需要重绘(draw),结合DecorView添加至Window过程,整体大概的流程图如下:
这里写图片描述
那么我们围绕图上过程来分析View的绘制流程,首先我们进入ViewRootImpl.java中,查看performTraversals函数,这个函数非常长,View的绘制三大流程将在此展开。

“` private void performTraversals() {
// 缓存DecorView ,因为在下面用的比较多
final View host = mView;
…..
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(TAG,
“And hey let’s measure once more: width=” + width
+ ” height=” + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
…..
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.width和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

    //执行测量操作
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    .....
    //执行布局操作
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ......
    //执行绘制操作
    performDraw();
}

“`

主要分下面三大步骤。

measure

measure操作主要用于计算视图的大小
在前面文章 Android 之美 从0到1 Android 进阶(一)中我们知道View的MeasureSpec由父容器的MeasureSpec和其自身的LayoutParams共同确定,而对于DecorView是由它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同确定。
在ViewRootImpl的performTraversals方法中,完成了创建DecorView的MeasureSpec的过程,相应的代码片段如下:

//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.width和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

我们知道Activity的根视图总是全屏的,因为ViewRootImpl 在创建DecorView的MeasureSpec的过程 测量模式是EXACTLY,而Size是windowSize,相应的代码片段如下:


private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
//匹配父容器时,测量模式为MeasureSpec.EXACTLY,测量大小直接为屏幕的大小,也就是充满真个屏幕
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
......
}
return measureSpec;
}

View 的measure过程

measure在performMeasure开始的,该函数在view中定义为final类型,要求子类不能修改。measure()函数中又会调用onMeasure()函数,相应的代码片段如下:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
      ...........
      //如果上一次的测量规格和这次不一样,重新测量视图View的大小
        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
            resolveRtlPropertiesIfNeeded();
            int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -
  • 13
    点赞
  • 79
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值