andorid 内存优化-内存抖动
在 Android 开发中,内存抖动(Memory Churn) 是指应用频繁创建和销毁小对象,导致内存频繁分配和回收,进而引发性能问题。以下是常见的问题场景及解决方案:
1. 循环中创建对象
问题表现:在
for
/while
循环或频繁调用的方法中持续创建新对象,导致短时间内产生大量临时对象。典型场景:
- 在
onDraw()
中创建Paint
、Rect
等对象。 - 在
Adapter
的getView()
/onBindViewHolder()
中频繁创建数据对象。
示例代码:
java
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 错误:每次绘制都创建新对象
Paint paint = new Paint();
Rect rect = new Rect(0, 0, 100, 100);
canvas.drawRect(rect, paint);
}
解决办法:
- 对象复用:在循环外创建对象并复用(如成员变量或静态变量)。
- 使用对象池:通过
SparseArray
或自定义池管理常用对象。
java
private Paint paint = new Paint(); // 成员变量复用
private Rect rect = new Rect();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
rect.set(0, 0, 100, 100);
canvas.drawRect(rect, paint);
}
2. 字符串拼接(String.concat 或 + 操作符)
问题表现:在循环或频繁调用的方法中使用
+
拼接字符串,会生成大量临时String
对象。示例代码:
java
// 错误:每次循环创建新String对象
String result = "";
for (int i = 0; i < 1000; i++) {
result += "item" + i; // 等价于 result = result.concat("item" + i);
}
解决办法:
- 使用
StringBuilder
:在循环中替代String
拼接。
java
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append("item").append(i); // 减少临时对象
}
String result = sb.toString();
3. 自动装箱(Autoboxing)
问题表现:基本数据类型(如
int
、long
)与包装类(如Integer
、Long
)之间的自动转换,会频繁创建包装对象。示例代码:
java
// 错误:每次运算创建新Integer对象
Integer sum = 0; // 初始化为包装类
for (int i = 0; i < 1000; i++) {
sum += i; // 等价于 sum = Integer.valueOf(sum.intValue() + i);
}
解决办法:
- 优先使用基本数据类型:避免不必要的装箱操作。
java
int sum = 0; // 使用基本类型
for (int i = 0; i < 1000; i++) {
sum += i; // 无装箱操作
}
4. 动画与帧刷新
问题表现:在
ValueAnimator
、ObjectAnimator
或自定义动画中,每帧都可能创建新对象。典型场景:
- 动画更新时频繁创建
PointF
、Path
等对象。 - 在
AnimatorUpdateListener
中进行复杂计算并创建临时对象。
示例代码:
java
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 错误:每帧创建新对象
PointF point = new PointF(100 * animation.getAnimatedFraction(), 200);
// 使用point更新UI
}
});
解决办法:
- 复用对象:在监听器外部创建对象并更新其属性。
java
private PointF point = new PointF(); // 成员变量复用
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
point.set(100 * animation.getAnimatedFraction(), 200); // 更新现有对象
// 使用point更新UI
}
});
5. 频繁调用创建新对象的方法
问题表现:某些方法内部会创建新对象,若被频繁调用(如
onTouchEvent()
、onScroll()
),会导致内存抖动。典型场景:
BitmapFactory.decodeResource()
每次调用都创建新Bitmap
。Arrays.asList()
返回新的ArrayList
对象。
解决办法:
- 缓存结果:避免重复调用相同方法创建对象。
java
// 错误:每次点击都解析Bitmap
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.image);
imageView.setImageBitmap(bmp);
}
});
// 正确:提前解析并缓存Bitmap
private Bitmap cachedBitmap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
cachedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
button.setOnClickListener(v -> imageView.setImageBitmap(cachedBitmap));
}
6. 内部类隐式持有外部类引用
问题表现:非静态内部类(如匿名类)隐式持有外部类的引用,若频繁创建会导致外部类无法被回收。
典型场景:
- 在
Handler
、Runnable
、AsyncTask
中使用匿名内部类。
示例代码:
java
// 错误:匿名Runnable持有Activity引用
new Thread(new Runnable() {
@Override
public void run() {
// 长时间运行的任务
}
}).start();
解决办法:
- 使用静态内部类 + 弱引用:避免外部类被意外持有。
java
private static class MyRunnable implements Runnable {
private final WeakReference<MainActivity> activityRef;
public MyRunnable(MainActivity activity) {
this.activityRef = new WeakReference<>(activity);
}
@Override
public void run() {
MainActivity activity = activityRef.get();
if (activity != null) {
// 使用activity
}
}
}
7. 集合频繁扩容
问题表现:
ArrayList
、HashMap
等集合在容量不足时会自动扩容并复制数据,导致内存抖动。示例代码:
java
// 错误:未指定初始容量,频繁扩容
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add("item" + i); // 多次触发扩容
}
解决办法:
- 预分配容量:根据数据量预估初始大小。
java
ArrayList<String> list = new ArrayList<>(1000); // 初始容量足够大
for (int i = 0; i < 1000; i++) {
list.add("item" + i); // 避免扩容
}
8. 资源未及时释放
问题表现:
Bitmap
、Cursor
、InputStream
等资源使用后未及时释放,导致内存占用过高,触发频繁 GC。解决办法:
- 使用
try-with-resources
:自动关闭资源。 - 在
onDestroy()
中释放资源:如回收Bitmap
。
java
// 正确:使用try-with-resources关闭流
try (InputStream is = getResources().openRawResource(R.raw.file)) {
// 读取数据
} catch (IOException e) {
e.printStackTrace();
}
9. 自定义 View 绘制优化不足
问题表现:在
onDraw()
中进行复杂计算或创建大量对象,导致每帧绘制时内存波动。解决办法:
- 减少
onDraw()
中的对象创建:复用Paint
、Path
等对象。 - 避免复杂计算:将不变的计算移至
onSizeChanged()
等方法。
java
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 一次性计算,避免在onDraw()中重复计算
calculatePath();
}
10. 第三方库使用不当
问题表现:某些库的 API 设计可能导致频繁创建对象(如网络请求库、图片加载库)。
典型场景:
- 每次图片加载都创建新的
RequestOptions
。 - 网络请求频繁解析 JSON 生成新对象。
解决办法:
- 复用配置对象:如 Glide 的
RequestOptions
可静态化。
java
private static final RequestOptions OPTIONS = new RequestOptions()
.centerCrop()
.placeholder(R.drawable.placeholder);
// 使用静态OPTIONS,避免重复创建
Glide.with(context)
.load(url)
.apply(OPTIONS)
.into(imageView);
检测与分析工具
- Android Profiler:实时监控内存分配,定位内存抖动热点。
- Memory Profiler:查看短时间内的内存分配峰值和 GC 频率。
- Allocation Tracking:记录特定时间段内的所有对象分配,找出频繁创建的对象。
- Lint:静态代码分析工具,可检测潜在的内存抖动问题(如字符串拼接)。
总结
内存抖动的核心问题是频繁创建和销毁小对象,导致 GC 压力增大和 UI 卡顿。解决思路包括:
- 对象复用:避免在循环、高频调用方法中创建新对象。
- 资源管理:及时释放不再使用的资源,减少内存占用。
- 预分配与缓存:合理设置集合初始容量,缓存常用对象。
- 优化算法:减少不必要的计算和对象创建。
通过结合代码审查和工具分析,可以有效识别并解决内存抖动问题,提升应用性能。
作者: 一点点征服
出处:http://www.cnblogs.com/ldq2016/
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利