一、内存泄漏的常见场景与诊断方法

内存泄漏就像家里漏水的水龙头,虽然每次只漏一滴,但时间久了就会水漫金山。在Android开发中,最常见的场景就是Activity被静态对象持有引用。比如我们经常写的工具类:

// 技术栈:Android Java
public class ImageUtils {
    private static Context sContext;  // 静态变量持有Activity引用
    
    public static void init(Context context) {
        sContext = context;  // 错误示范:静态变量持有Activity
    }
    
    public static void load(String url) {
        // 使用sContext加载图片...
    }
}

这段代码的问题在于:当Activity销毁时,由于ImageUtils类持有它的引用,导致GC无法回收该Activity。正确的做法应该是使用ApplicationContext:

public class ImageUtils {
    private static Context sAppContext;  // 使用Application上下文
    
    public static void init(Context context) {
        sAppContext = context.getApplicationContext();  // 正确做法
    }
}

诊断内存泄漏可以使用Android Studio自带的Profiler工具。具体操作是:

  1. 打开Profiler -> Memory
  2. 执行可能泄漏的操作
  3. 触发GC多次
  4. 检查内存是否回落

二、四大内存优化实战技巧

2.1 使用弱引用处理回调

Handler是另一个常见的泄漏源:

// 危险代码示例
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        // 更新UI...
    }
};

改进方案是使用静态Handler+弱引用:

// 优化后的Handler
private static class SafeHandler extends Handler {
    private final WeakReference<Activity> mActivityRef;
    
    public SafeHandler(Activity activity) {
        mActivityRef = new WeakReference<>(activity);
    }
    
    @Override
    public void handleMessage(Message msg) {
        Activity activity = mActivityRef.get();
        if (activity != null && !activity.isFinishing()) {
            // 安全更新UI
        }
    }
}

2.2 优化图片加载

图片处理不当会直接导致OOM。推荐使用Glide时注意这些配置:

Glide.with(context)
    .load(imageUrl)
    .apply(new RequestOptions()
        .override(targetWidth, targetHeight)  // 按需加载
        .format(DecodeFormat.PREFER_RGB_565)  // 减少内存占用
        .diskCacheStrategy(DiskCacheStrategy.ALL)  // 启用缓存
    )
    .into(imageView);

2.3 对象池技术

对于频繁创建销毁的对象,可以使用对象池:

// 创建对象池
public class BitmapPool {
    private static final int MAX_SIZE = 10;
    private static Queue<Bitmap> pool = new LinkedList<>();
    
    public static Bitmap getBitmap(int width, int height) {
        Bitmap bitmap = pool.poll();
        if (bitmap == null || bitmap.isRecycled()) {
            return Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
        }
        return bitmap;
    }
    
    public static void recycle(Bitmap bitmap) {
        if (pool.size() < MAX_SIZE) {
            pool.offer(bitmap);
        } else {
            bitmap.recycle();
        }
    }
}

2.4 监控大对象分配

在Application中注册回调监控大内存分配:

// 监控大内存分配
public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Debug.setAllocationLimit(1024 * 1024); // 1MB
        Debug.setAllocationListener(new AllocationListener() {
            @Override
            public void onAllocation(int size) {
                Log.w("Memory", "大内存分配:" + size + " bytes");
            }
        });
    }
}

三、性能优化的进阶策略

3.1 启动速度优化

冷启动耗时主要消耗在:

  1. 加载主题样式
  2. 初始化第三方库
  3. 主线程耗时操作

优化方案示例:

// 延迟初始化非关键库
public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        // 主线程只初始化必要组件
        initEssentialComponents();
        
        // 非关键库放到后台线程
        new Handler().postDelayed(() -> {
            initNonCriticalLibs();
        }, 3000);
    }
}

3.2 列表滑动优化

RecyclerView优化要点:

// RecyclerView优化配置
recyclerView.setHasFixedSize(true);  // 固定尺寸提升性能
recyclerView.setItemViewCacheSize(20);  // 增加缓存数量
recyclerView.setDrawingCacheEnabled(true);  // 启用绘图缓存
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);

// ViewHolder优化示例
public class OptimizedViewHolder extends RecyclerView.ViewHolder {
    // 将findViewById结果缓存起来
    private SparseArray<View> mCachedViews = new SparseArray<>();
    
    public View getView(int id) {
        View view = mCachedViews.get(id);
        if (view == null) {
            view = itemView.findViewById(id);
            mCachedViews.put(id, view);
        }
        return view;
    }
}

3.3 数据库查询优化

Room数据库的优化技巧:

// 使用@Transaction减少事务开销
@Dao
public interface UserDao {
    @Transaction
    @Query("SELECT * FROM users WHERE age > :minAge")
    List<User> getUsersOlderThan(int minAge);
}

// 添加索引提升查询速度
@Database(entities = {User.class}, version = 1)
@TypeConverters({DateConverter.class})
public abstract class AppDatabase extends RoomDatabase {
    @Override
    public void init(@NonNull SupportSQLiteDatabase db) {
        db.execSQL("CREATE INDEX idx_user_age ON users(age)");
    }
}

四、工具链与最佳实践

4.1 使用LeakCanary自动检测

在build.gradle中添加:

dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}

LeakCanary会自动检测内存泄漏并生成报告,比手动检测效率高10倍以上。

4.2 使用StrictMode严苛模式

在Application中启用:

public void onCreate() {
    super.onCreate();
    if (BuildConfig.DEBUG) {
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
            .detectDiskReads()
            .detectDiskWrites()
            .detectNetwork()
            .penaltyLog()
            .build());
    }
}

4.3 内存优化检查清单

上线前必做的10项检查:

  1. 所有Activity是否都有泄漏检测
  2. 大图是否都做了压缩处理
  3. 静态集合是否及时清理
  4. 广播是否正确注销
  5. 线程池是否合理管理
  6. 数据库游标是否关闭
  7. 文件流是否关闭
  8. 缓存大小是否合理
  9. 第三方库是否按需初始化
  10. 内存监控是否到位

五、总结与建议

内存优化是个持续的过程,建议建立以下机制:

  1. 新功能开发时进行内存影响评估
  2. Code Review时加入内存检查项
  3. 定期使用工具进行全量扫描
  4. 建立性能基线持续监控

记住优化原则:预防胜于治疗,监控优于修复。良好的编码习惯比事后优化更重要,比如:

  • 及时释放不再使用的资源
  • 避免在循环中创建对象
  • 使用合适的数据结构
  • 注意第三方库的内存开销

最后提醒,不要过度优化。优化前先测量,找到真正的瓶颈再动手。有时候用户感知不到的微秒级优化,可能带来代码可读性的显著下降,得不偿失。