面试问题收集
一、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内存结构
- 垃圾回收机制基础
- 常见数据结构与基础算法