Android内存优化案例——不合适和高性能的写法(一)

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);

这样做可以节省内存空间,因为 ArrayMapSparseArray 的内部实现是使用两个数组来存储键和值,没有额外的开销。另外,它们还可以避免 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老皮!!!欢迎大家来找我探讨交流👀

相关推荐
帅得不敢出门6 小时前
安卓设备adb执行AT指令控制电话卡
android·adb·sim卡·at指令·电话卡
我又来搬代码了7 小时前
【Android】使用productFlavors构建多个变体
android
德育处主任9 小时前
Mac和安卓手机互传文件(ADB)
android·macos
芦半山9 小时前
Android“引用们”的底层原理
android·java
迃-幵10 小时前
力扣:225 用队列实现栈
android·javascript·leetcode
大风起兮云飞扬丶10 小时前
Android——从相机/相册获取图片
android
Rverdoser10 小时前
Android Studio 多工程公用module引用
android·ide·android studio
aaajj10 小时前
[Android]从FLAG_SECURE禁止截屏看surface
android
@OuYang10 小时前
android10 蓝牙(二)配对源码解析
android
Liknana10 小时前
Android 网易游戏面经
android·面试