Android内存优化是一个很重要的话题,有很多方面可以考虑,比如避免内存泄漏、减少内存抖动、优化图片加载、使用缓存和对象池等。下面我举一些代码案例,分别展示不合适的写法和高性能的写法。
1. 避免使用枚举类型。
枚举类型会占用更多的内存,因为它是一个类对象,而不是一个基本类型。如果需要定义一些常量,可以使用 static final int
或者 @IntDef
注解来代替。例如:
java
// 不合适的写法
public enum Color {
RED, GREEN, BLUE
}
// 高性能的写法
public static final int RED = 0;
public static final int GREEN = 1;
public static final int BLUE = 2;
@IntDef({RED, GREEN, BLUE})
@Retention(RetentionPolicy.SOURCE)
public @interface Color {}
这样做可以节省内存空间,因为枚举类型会占用至少4个字节,而 int
类型只占用2个字节。另外,使用注解可以保证类型安全和编译时检查。
2. 避免在循环中创建对象。
这会导致内存抖动和频繁的GC,影响性能和用户体验。如果需要在循环中使用对象,可以在循环外创建并复用,或者使用对象池来管理对象的生命周期。例如:
java
// 不合适的写法
for (int i = 0; i < 100; i++) {
String s = new String("Hello"); // 每次循环都会创建一个新的字符串对象
// do something with s
}
// 高性能的写法
String s = new String("Hello"); // 在循环外创建一个字符串对象
for (int i = 0; i < 100; i++) {
// do something with s
}
这样做可以减少内存分配和回收的次数,提高性能。如果对象的创建和销毁成本较高,可以考虑使用对象池来缓存和复用对象,例如 BitmapPool
3. 避免使用 String
连接符 +
来拼接字符串。
这会产生很多临时的字符串对象,占用内存空间,并触发GC。如果需要拼接字符串,可以使用 StringBuilder
或者 StringBuffer
来代替。例如:
java
// 不合适的写法
String s = "Hello" + "World" + "!" // 这会创建三个字符串对象
// 高性能的写法
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append("World");
sb.append("!");
String s = sb.toString(); // 这只会创建一个字符串对象
这样做可以避免不必要的字符串对象的创建,节省内存空间,并提高字符串拼接的效率。
4. 避免使用 System.gc()
来主动触发GC。
这会影响系统的自动内存管理机制,并可能导致应用卡顿或者OOM。如果需要释放内存,可以通过合理地设计数据结构和算法来减少内存占用,并及时释放不再使用的对象的引用。例如:
java
// 不合适的写法
System.gc(); // 强制调用GC
// 高性能的写法
list.clear(); // 清空列表中的元素,并释放引用
list = null; // 将列表对象置为null,让GC自动回收
这样做可以让系统根据内存情况自动调整GC策略,并避免不必要的GC开销。
5. 避免在 onDraw()
方法中创建对象。
这会导致每次绘制都会分配内存,造成内存抖动和GC。如果需要在 onDraw()
方法中使用对象,可以在构造方法或者 onSizeChanged()
方法中创建并复用,或者使用静态常量来代替。例如:
java
// 不合适的写法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint(); // 每次绘制都会创建一个画笔对象
paint.setColor(Color.RED);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 100, paint);
}
// 高性能的写法
private Paint paint; // 在类中声明一个画笔对象
public MyView(Context context) {
super(context);
paint = new Paint(); // 在构造方法中创建画笔对象,并设置颜色
paint.setColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, 100, paint); // 复用画笔对象
}
6. 避免使用 HashMap
来存储少量的键值对。
HashMap
的内部实现需要维护一个数组和一个链表,会占用较多的内存空间,并且可能导致内存碎片。如果只需要存储少量的键值对,可以使用 ArrayMap
或者 SparseArray
来代替。例如:
java
// 不合适的写法
HashMap<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
// 高性能的写法
ArrayMap<String, Integer> map = new ArrayMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
这样做可以节省内存空间,因为 ArrayMap
和 SparseArray
的内部实现是使用两个数组来存储键和值,没有额外的开销。另外,它们还可以避免 HashMap
的扩容和哈希冲突的问题。
7. 避免使用 setXxx()
方法来设置视图的属性。
这会导致视图的重新布局和重绘,消耗CPU和内存资源,并可能导致卡顿。如果需要动态改变视图的属性,可以使用属性动画来实现。例如:
java
// 不合适的写法
view.setAlpha(0.5f); // 设置视图的透明度,会触发视图的重绘
// 高性能的写法
ObjectAnimator.ofFloat(view, "alpha", 0.5f).start(); // 使用属性动画来设置视图的透明度,不会触发视图的重绘
这样做可以避免不必要的视图更新,提高动画效果和流畅度。
8. 避免在 onCreate()
方法中初始化不必要的对象。
这会导致应用启动时间变长,影响用户体验,并可能导致ANR。如果有些对象不需要在启动时就初始化,可以延迟到使用时再初始化,或者放到子线程中初始化。例如:
java
// 不合适的写法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
OkHttpClient client = new OkHttpClient(); // 在启动时就创建一个网络客户端对象,占用内存空间,并可能影响启动速度
}
// 高性能的写法
private OkHttpClient client; // 在类中声明一个网络客户端对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
private OkHttpClient getClient() {
if (client == null) {
client = new OkHttpClient(); // 在需要使用时才创建网络客户端对象,节省内存空间,并提高启动速度
9. 避免使用 findViewById()
方法来查找视图。
这会导致每次查找都会遍历视图树,消耗CPU和内存资源,并可能导致卡顿。如果需要使用视图,可以在 onCreate()
方法中使用 findViewById()
方法来获取并保存到变量中,或者使用 ViewBinding
或者 ButterKnife
等库来自动绑定视图。例如:
java
// 不合适的写法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onResume() {
super.onResume();
TextView textView = findViewById(R.id.text_view); // 每次调用都会查找视图树,影响性能
textView.setText("Hello World");
}
// 高性能的写法
private TextView textView; // 在类中声明一个视图变量
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text_view); // 在启动时就获取并保存视图对象,避免重复查找
}
@Override
protected void onResume() {
super.onResume();
textView.setText("Hello World"); // 复用视图对象
}
这样做可以避免不必要的视图查找,提高性能和流畅度。
10. 避免使用 VectorDrawable
来显示矢量图形。
VectorDrawable
的内部实现是使用 Path
来绘制矢量图形,这会消耗较多的CPU和内存资源,并可能导致卡顿。如果需要显示矢量图形,可以使用 SVG
或者 WebP
等格式来代替。例如:
java
// 不合适的写法
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM12,4c4.41,0 8,3.59 8,8s-3.59,8 -8,8 -8,-3.59 -8,-8 3.59,-8 8,-8zM6.5,9L10,12.5l-3.5,3.5L8,16l5,-5 -5,-5L6.5,9zM14,13h4v-2h-4v2z" />
</vector>
// 高性能的写法
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_arrow_forward_24px.webp" /> // 使用WebP格式的图片来显示矢量图形,节省CPU和内存资源,并提高绘制效率
这样做可以避免不必要的矢量图形绘制,提高性能和流畅度。
👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀