Android面试指南(三)

发布于:2025-08-20 ⋅ 阅读:(22) ⋅ 点赞:(0)

目录

一、View的绘制机制

1.1、View树的绘制流程

1.2、measure方法

1.3、layout方法

二、事件分发机制

2.1、为什么会有事件分发机制

2.2、三个重要的事件分发方法

2.3、事件分发流程

三、ListView

3.1、什么是ListView

3.2、ListView的适配器模式

3.3、ListView的recycleBin机制

3.4、ListView的优化

四、RecycleView

4.1、RecyclerView和ListView的区别

4.2、RecyclerView的复用机制

4.2.1、RecyclerView的四级缓存

4.2.2、RecyclerView复用机制

一、View的绘制机制

1.1、View树的绘制流程

  • 三大阶段:measure(测量)-> layout(布局)-> draw(绘制),整个过程在ViewRootImpl类的performTraversals()方法中展开
  • 递归特性:类似树形递归过程,父视图通过measure和layout方法依次测量/摆放所有子视图
  • 触发时机:当Activity获得用户焦点时,由Framework层从根节点开始处理绘制请求

1.2、measure方法

①、ViewGroup.LayoutParams

  • 作用:指定视图高度和宽度的参数,每个视图必须包含
  • 取值类型:
    • 具体数值(如100dp)
    • MATCH_PARENT(与父控件等大,不包括padding值)
    • WRAP_CONTENT(自适应内容大小)

②、MeasureSpec

  • 数据结构:32位int值,高2位表示测量模式(SpecMode),低30位表示尺寸大小
  • 模式分类:
    • UNSPECIFIED:父控件无约束(实际开发极少使用)
    • EXACTLY:父容器确定精确尺寸(对应match_parent或具体数值)
    • AT_MOST:父容器指定最大尺寸(对应wrap_content)
  • 转换规则:系统会将View的LayoutParams根据父容器规则转换为对应的MeasureSpec

③、measure方法中重要的回调函数

  • measure方法
    • 调用链:measure() -> onMeasure() -> setMeasuredDimension()
    • 强制约束:必须调用setMeasuredDimension()设置测量结果,否则会抛出illegalStateException
    • 二次测量机制:当子视图尺寸不符合约束时,父视图会要求重新测量
  • onMeasure方法
    • 核心参数:widthMeasureSpec和heightMeasureSpec包含父容器的约束条件
    • 自定义要点:继承View时必须重写该方法,并通过setMeasuredDimension()保存计算结果
    • 测量流程:自上而下遍历视图树,父容器通过MeasureSpec和子视图的LayoutParams决定子视图尺寸

总结:measure方法开始于父控件ViewGroup,通过不断的遍历子控件的measure方法,然后根据ViewGroup的MeasureSpec和子View的LayoutParams来决定子视图的MeasureSpec测量规格。通过这个测量规格MeasureSpec来进一步获取到子View的测量的宽高,然后一层一层的向下传递,不断的保存整个父控件的测量宽高。整个measure测量的调用流程,就是一个树形的递归过程,为整个View树计算实际的大小。每一个View视图控件的实际的宽和高都是由父视图和它本身的LayoutParams所决定的。

1.3、layout方法

Layout的作用是 ViewGroup用来确定子元素的位置,当 ViewGroup的位置被确定后, 它在onLayout中会遍历所有的子元素并调用其 layout方法,在 layout方法中 onLayout 方法又会被调用。layout方法确定View 本身的位置,而 onLayout方法则会确定所有子元素的位置。

onLayout()方法:

  • 位置基准:子视图位置都是相对于父视图的坐标系
  • 实现要求:自定义ViewGroup必须实现onLayout()来摆放子视图
  • 测量依赖:layout过程会使用measure阶段得到的尺寸(getMeasuredWidth/Height)

1.4、draw方法

Draw的作用是将 View 绘制到屏幕上面。

View 的绘制过程遵循 如下几步:

(1)绘制背景 background.draw(canvas)。

(2)绘制自己(onDraw)。

(3)绘制 children (dispatchDraw)。

(4)绘制装饰(onDraw ScrollBars)。

这里需要注意的是有两个容易混淆的方法:

  • invalidate():
    • 仅触发draw流程
    • 视图尺寸未变化时不触发measure和layout
  • requestLayout():
    • 会触发measure和layout流程
    • 不自动触发draw流程
    • 常用于视图尺寸/方向发生变化时

总结:

  • 完整流程:measure(计算尺寸)-> layout(确定位置)-> draw(实际绘制)
  • 核心特点:
    • 树形递归结构,从DecorView开始自上而下遍历
    • 父容器通过MeasureSpec约束子视图
    • 自定义View必须实现关键回调方法(onMeasure/onLayout)

二、事件分发机制

2.1、为什么会有事件分发机制

  • 产生背景:安卓中的View以树形结构存在,多个View可能重叠在一起。当点击区域有多个View可响应时,系统需要确定事件分配对象。
  • 核心问题:点击事件可能同时触发多个重叠View的响应,必须通过机制确定最终响应者。
  • 解决方案:引入事件分发机制,通过层级传递和判断决定事件最终处理者。
  • 典型场景:点击View1时,其父容器ViewGroupA和RootView也能响应,需要通过分发机制确定处理者。

2.2、三个重要的事件分发方法

  • dispatchTouchEvent:
    • 作为事件分发链的起点,决定事件由自身处理还是分发给子View
    • 内部会调用onInterceptTouchEvent判断是否拦截
    • 递归调用子View的dispatchTouchEvent
  • onInterceptTouchEvent:
    • 父控件在向子控件下发事件时,通过此方法决定是否拦截
    • 返回true表示拦截,事件将不会传递给子View
    • 仅存在于ViewGroup中,Activity和普通View无此方法
  • onTouchEvent:
    • 处理传递到View的触摸事件
    • 处理四种基本手势:按下(ACTION_DOWN)、移动(ACTION_MOVE)、抬起(ACTION_UP)、取消(ACTION_CANCEL)
    • 返回值决定是否消费事件(true表示消费)
  • 特殊说明:
    • Activity作为原始分发者不能拦截事件,否则会导致全屏无响应
    • View作为最末端要么消费事件,要么回传,无需拦截

2.3、事件分发流程

  • 正向传递流程:
    • Activity → PhoneWindow(通过DecorView传递)
    • DecorView → 最外层ViewGroup
    • ViewGroup逐级向下传递至目标View
  • 反向回传机制:
    • 如果最终View未消费事件,事件会依次回传至Activity
    • 只有所有层级都不处理时,事件才会被抛弃
    • 采用责任链设计模式,保证处理灵活性和有序性
  • 设计特点:
    • 上层View既可拦截处理,也可询问子View
    • 子View不处理时可回传给父View
    • 形成完整的"分发-处理-回传"闭环

2.4、案例分析

  • 场景:点击View1,父控件都不拦截
  • 流程分析:
    • Activity.dispatchTouchEvent → PhoneWindow
    • PhoneWindow通过DecorView.dispatchTouchEvent传递
    • RootView判断不拦截(onIntercept返回false)
    • ViewGroupA同样不拦截,传递至View1
    • View1.onTouchEvent返回true表示消费
  • 回传过程:
    • View1 → ViewGroupA.dispatchTouchEvent返回true
    • 逐级回传至Activity
  • 关键点:
    • 每个层级的dispatchTouchEvent都获得最终返回值
    • 消费标记通过返回值链式传递
    • 整个过程体现责任链模式特点

三、ListView

3.1、什么是ListView

  • 本质:ListView是一个能将数据集合以动态滚动方式展示到用户界面上的View控件
  • 特点:
    • 在Android所有常用控件中属于用法较复杂的
    • 专门用于处理内容元素多且屏幕无法全部展示的情况
    • 通过手指滑动即可查看超出屏幕部分的内容
  • 应用场景:适合以列表形式展示大量数据内容

3.2、ListView的适配器模式

  • 作用:作为数据源和ListView之间的桥梁
  • 核心思想:
    • 实现数据和视图的分离(MVC设计模式)
    • ListView只关心在哪个position显示哪个View
    • 不直接处理数据源,通过统一接口的Adapter处理
  • 工作流程:
    • 为每个数据项创建对应的View
    • 将View交给ListView显示
    • 保证不同数据源都能适配ListView的显示需求
  • 关键方法:
    • getCount():返回数据项总数
    • getView():绘制每个item的视图
  • 绘制原理:
    • 先调用getCount获取数据长度
    • 根据长度循环调用getView绘制每个item
    • 有多少个item就会调用多少次getView

3.3、ListView的recycleBin机制

  • 作用:解决ListView显示大量数据时的内存问题
  • 核心思想:像回收站一样缓存不可见的item视图
  • 重要变量:
    • mActiveViews:存储当前屏幕上可见的View
    • mScrapViews:二维数组,存储所有废弃的View列表
    • mCurrentScrap:当前被划出屏幕的废弃View列表
  • 关键方法:
    • setViewTypeCount():为不同类型数据项建立回收机制
    • fillActiveViews():将指定元素存储到mActiveViews数组
    • getActiveView():获取屏幕上显示的View(获取后该位置会置null)
    • addScrapView():缓存刚被划出屏幕的View
  • 工作流程:
    • item滑出屏幕时存入RecycleBin
    • 新item进入屏幕时从RecycleBin获取可复用的View
    • 通过convertView参数实现View的复用

3.4、ListView的优化

①、convertView

  • 原理:利用convertView参数缓存已滑出屏幕的item视图
  • 实现方式:
    • 判断convertView是否为null
    • 不为null时直接复用,避免重复创建View
    • 初始显示时convertView为null,需创建新View

②、ViewHolder

  • 作用:避免频繁调用findViewById
  • 实现要点:
    • 定义静态内部类ViewHolder
    • 存储item中的控件引用
    • 通过setTag/getTag方法存取ViewHolder
  • 优势:减少View树遍历开销,提升性能

③、三级缓存

  • 应用场景:主要用于图片加载优化(内存、磁盘、网络,使用Picasso或Glide等三方库加载)
  • 注意事项:
    • getView中避免耗时操作
    • 保证滑动流畅性

④、滑动事件监听

  • 优化策略:
    • 监听滑动状态
    • 滑动停止后再加载图片
  • 其他优化点:
    • 避免item布局中使用半透明元素
    • 开启硬件加速提升绘制性能

四、RecycleView

4.1、RecyclerView和ListView的区别

①、布局管理器

  • RecyclerView使用LayoutManager管理布局,支持线性布局、网格布局等更为复杂的布局方式
  • ListView默认只支持垂直线性布局

②、视图复用

  • RecyclerView默认使用ViewHolder模式实现视图复用
  • ListView没有强制使用ViewHolder模式,需要自行实现复用

③、Item装饰和动画

  • RecyclerView通过ItemDecoration和ItemAnimator提供装饰和动画支持,实现分隔线添加和列表动画更加简单
  • ListView需要自定义实现

④、缓存机制

  • RecyclerView是四级缓存,缓存效率更高,且缓存的是ViewHolder
  • ListView是两级缓存,且缓存的是View

⑤、数据更新

RecyclerView提供了notifyItemInserted()、notifyItemRemoved()和notifyItemChanged()等局部更新方法

⑥、性能优化

RecyclerView通过视图复用、预加载、批量处理等机制使得滑动时具有更加高效的性能

4.2、RecyclerView的复用机制

4.2.1、RecyclerView的四级缓存

RecyclerView的Recycler类定义了四级缓存机制。

  • AttachedScrap和ChangedScrap同属第一级缓存,其特点是缓存的ViewHolder在复用时无需重新绑定数据,因为RecyclerView认为这些ViewHolder会再次出现在屏幕上。
  • CachedViews属于第二级缓存,存储滑动出屏幕的ViewHolder,默认容量为2,可通过setItemCacheSize方法扩容。
  • ViewCacheExtension是第三级缓存,允许开发者自定义ViewHolder缓存策略。
  • RecycledViewPool是第四级缓存,当CachedViews容量不足时,ViewHolder会被转移至此,默认每个ViewType对应容量为5,可通过setMaxRecycledViews方法调整。

四级缓存作用场景差异明显:一级缓存(AttachedScrap/ChangedScrap)的ViewHolder保留完整数据状态;二级缓存(CachedViews)需校验位置一致性;三级缓存(ViewCacheExtension)需开发者自定义;四级缓存(RecycledViewPool)按ViewType分类存储。

4.2.2、RecyclerView复用机制

复用机制执行流程为:

  • 一级缓存查询:优先检查AttachedScrap和ChangedScrap
  • 二级缓存查询:检索CachedViews并校验位置一致性
  • 三级缓存查询:查找ViewCacheExtension(通常为空)
  • 四级缓存查询:从RecycledViewPool按ViewType匹配
  • 最终创建:若各级缓存均未命中,则调用Adapter.createViewHolder新建

思考总结:

  • 插拔式设计适用于大型框架设计,比如阿里开源的VLayout是对layout manager的高度定制。
  • RecyclerView性能优化:为列表项指定确切宽高,可减少onMeasure阶段的测量开销。
  • 列表刷新优化:优先使用notifyItemChange而非全局notifyDataSetChanged,结合差分刷新工具可显著提升性能。

OK,今天的内容就先到这里了,下期继续哦!


网站公告

今日签到

点亮在社区的每一天
去签到