Android-Develops
面试问题收集
一、Bitmap内存优化
Bitmap是内存消耗大户,通过以下方法减少占用:
- 降低色彩解析模式
使用RGB565等低色彩模式,将单个像素的字节大小从32位(ARGB8888)减少到16位,显著节省内存。 - 合理放置资源文件
高分辨率图片应放置在高密度目录(如drawable-xxhdpi
),避免系统自动缩放导致内存浪费。 - 缩小图片尺寸
加载时通过BitmapFactory.Options
动态调整采样率(inSampleSize
),或使用createScaledBitmap()
减少宽高尺寸。
二、ViewModel与LiveData机制
ViewModel和LiveData是Jetpack组件,用于数据生命周期管理和响应式UI更新。
-
粘性事件(Sticky Event)
-
定义:当新观察者订阅
LiveData
时,若已有存储值,会立即收到最后一次更新(旧数据)。 -
示例:屏幕旋转后,新Activity观察LiveData时触发UI更新(旧数据)。
-
来源:基于LiveData的版本号对比机制。代码关键部分如下:
public abstract class LiveData<T> { private int mVersion = START_VERSION; // LiveData当前版本(初始-1) private class LifecycleBoundObserver implements ObserverWrapper { private int mLastVersion = START_VERSION; } void considerNotify(ObserverWrapper observer) { if (observer.mLastVersion < mVersion) { // 核心判断:版本号落后才分发 observer.mLastVersion = mVersion; observer.mObserver.onChanged((T)data); } } }
-
简单解法:使用
Event
包装数据。事件消费后置空值,避免旧数据触发更新。
-
-
ViewModel临时数据保存机制
- 内存中保存
ViewModel对象存储在ViewModelStore
中。当配置变更(如屏幕旋转)时:- Activity/Fragment被销毁重建。
ViewModelStore
被系统保留(绑定到NonConfigurationInstances
)。- 新建Activity/Fragment时自动恢复ViewModel实例。
- 数据范围与最佳实践
- 保留场景:屏幕旋转、分屏切换、系统语言更改。
- 不保留场景:用户退出应用、系统资源不足杀死进程、Activity被finish()。
- 最佳实践:
- ViewModel解决配置变更的临时数据保存(内存级)。
SavedStateHandle
解决进程被杀死时的关键数据持久化。- 复杂数据应使用数据库等持久化方案。
- 避免内存泄漏:勿在ViewModel持有Context/View引用,必要时用
Application Context
代替。
- 内存中保存
三、View基础原理
深入理解View的测量、布局、绘制机制,是优化UI性能的核心。
- MeasureSpec计算与布局优化
- MeasureSpec原理:父容器传递给子View的测量要求,由大小和模式组成,取决于父容器的MeasureSpec和子View的LayoutParams。
- 父布局根据自身MeasureSpec和子View LayoutParams,确定子View的MeasureSpec,再调用
children.measure()
,最终确定自身尺寸。
- 父布局根据自身MeasureSpec和子View LayoutParams,确定子View的MeasureSpec,再调用
- 布局性能优化:
- 缓存MeasureSpec计算结果:固定尺寸View(如按钮)直接调用
setMeasuredDimension()
设置宽高。 - 优化布局流程:减少嵌套层级、懒加载布局、合并重复布局。
- 避免无效重绘:使用局部刷新机制。
- 精确控制绘制范围:通过
Canvas.clipRect()
限制绘制区域。
- 缓存MeasureSpec计算结果:固定尺寸View(如按钮)直接调用
- MeasureSpec原理:父容器传递给子View的测量要求,由大小和模式组成,取决于父容器的MeasureSpec和子View的LayoutParams。
- getMeasuredWidth()与getWidth()区别
- getMeasuredWidth():测量阶段后分配的宽度(含内边距)。
- 使用时机:
onMeasure()
后或layout()
前。 - 特点:反映视图的期望宽度;若布局未强制改变尺寸,可能与
getWidth
相同。
- 使用时机:
- getWidth():布局阶段后的最终可见宽度(屏幕实际值)。
- 使用时机:
onLayout
后。 - 计算方式:
width = right - left
。
- 使用时机:
- getMeasuredWidth():测量阶段后分配的宽度(含内边距)。
- requestLayout()与invalidate()区别
- requestLayout():请求整个视图树的测量(measure)和布局(layout)流程。
- 触发场景:视图尺寸/位置变化、动态添加/移除子视图、
setVisibility()
导致布局结构变化。 - 执行流程:从当前视图向上回溯到根视图(如ViewRootImpl),依次执行measure → layout。
- 触发场景:视图尺寸/位置变化、动态添加/移除子视图、
- invalidate():仅标记视图的局部区域为“脏区”,请求下一帧重绘该区域。
- 触发场景:视图内容变化但不影响尺寸/位置(如
onDraw()
依赖数据更新)。 - 执行流程:标记脏区 → 加入重绘队列 → 下一帧VSync信号时调用
onDraw()
。
- 触发场景:视图内容变化但不影响尺寸/位置(如
- requestLayout():请求整个视图树的测量(measure)和布局(layout)流程。
- View坐标体系
- getX()/getY():相对当前View左上角的局部坐标(触摸点在View内的位置)。
- 特点:与父容器无关;值可为负(如滑动超出View边界)。
- getRawX()/getRawY():相对屏幕左上角的全局坐标。
- 特点:包含状态栏高度(
getRawY()
从屏幕顶部算起)。
- 特点:包含状态栏高度(
- getLocationOnScreen():获取View左上角在屏幕上的绝对坐标。
- getX()/getY():相对当前View左上角的局部坐标(触摸点在View内的位置)。
- View生命周期关键方法
- 构造函数:通过代码或XML创建View实例。
- onAttachedToWindow():View被添加到窗口时调用。
- 用途:初始化资源、注册监听器、启动动画。
- onDetachedFromWindow():View从窗口移除时调用(如Activity销毁)。
- 关键作用:释放资源、停止动画、注销监听器。
- 注意事项:
onVisibilityChanged()
可能在onAttachedToWindow()
前/后调用(如View初始化为GONE
)。onWindowFocusChanged()
可能在onDetachedFromWindow()
后调用(避免在此访问资源)。
- View性能优化
- 过度绘制:todo
- 布局优化:todo
- 绘制优化:todo
- 高级机制与原理
- 硬件加速:将绘制指令交给GPU,但是部分api不支持
- SurfaceView与TextureView:todo
- view.post与Handler:todo
四、事件分发机制
- 滑动实现方式
scrollTo()
/scrollBy()
: todo- 通过
ViewDragHelper
实现复杂拖拽: todo
- 自定义下拉刷新控件
- todo
- 事件分发机制如何提升效率
- todo
- 嵌套滑动处理
- todo
- RecyclerView的滑动冲突处理
- todo
五、Handler
六、性能优化 (todo)
1. **内存管理与泄漏排查**
1. **UI渲染性能(卡顿优化)**
1. **启动速度优化**
1. **功耗优化基础**
1. **包体积优化**
七、常用库与框架(todo)
- 网络请求(如Retrofit)
- 图片加载(如Glide / Picasso)
- Gradle基础
八、网络与后台
- RESTful API概念与使用
- 异步处理深入(线程安全、后台限制)
- 缓存策略
九、架构设计
- MVVM/MVI理解与实践
- 模块化 / 组件化
- 设计模式应用
十、新技术与趋势
- Compose
- KMM / Flutter
十一、JVM / 内存模型基础
- JVM内存结构
- 垃圾回收机制基础
- 常见数据结构与基础算法
Android-Develops
1.
2.
RecyclerView缓存机制
RecyclerView缓存机制
多级缓存体系架构图
TEXT
RecyclerView 缓存系统
├── 1. 屏幕内缓存 (Attached Scrap)
│ └── 存放当前可见的ViewHolder(快速复用)
├── 2. 屏幕外缓存 (Cache)
│ └── 保存最近离开屏幕的ViewHolder(默认容量=2)
├── 3. 扩展缓存 (ViewCacheExtension)
│ └── 开发者自定义缓存(特殊用途)
└── 4. 回收池 (RecycledViewPool)
└── 全局共享的ViewHolder存储(不同类型独立缓存)
根据position
判断是否命中Cache
,根据viewType
判断是否命中RecyclerViewPool
,会执行onBindViewHolder
在 RecyclerView 的回收复用机制中,changedScrap
和 attachedScrap
是两个关键临时缓存,而 Stable IDs 会改变 ViewHolder 获取的方式。以下是详细解释:
1. changedScrap
的作用
- 用途:专门配合
notifyItemChanged()
或notifyDataSetChanged()
使用。 - 工作机制:
- 当调用
notifyItemChanged(position)
时,被标记更新的 item 会被临时移到changedScrap
中。 - 在布局阶段(如
onLayout
),这些 ViewHolder 会被重新绑定数据(调用onBindViewHolder()
),然后放回原位置。
- 当调用
- 目的:支持局部更新动画(如淡入淡出),避免直接回收导致视觉中断。
2. attachedScrap
的作用
- 用途:用于 快速复用可见或即将可见的 ViewHolder。
- 工作机制:
- 在布局过程中(如
LinearLayoutManager.fill()
),RecyclerView 会先将当前屏幕上的 ViewHolder 临时移除 到attachedScrap
。 - 遍历新布局时,直接从
attachedScrap
中按 position 匹配 取回 ViewHolder(无需创建或绑定)。
- 在布局过程中(如
- 目的:避免无效的创建/绑定,提升滚动性能(尤其在快速滑动时)。
3. Stable IDs 如何改变 ViewHolder 获取方式
当启用 Stable IDs(通过 setHasStableIds(true)
+ 重写 getItemId()
)时: