深入浅出安卓内存泄漏与优化

深入浅出安卓内存泄漏与优化

一、什么是内存泄漏?

内存泄漏就像**"借书不还"**:

  • 你向图书馆(系统)借了一本书(内存)
  • 用完后没有归还(释放)
  • 图书馆的书越来越少(可用内存减少)
  • 最后无书可借(OOM崩溃)

在安卓中表现为:对象不再使用,但被错误引用导致GC无法回收


二、内存泄漏的四大凶手

1. 静态变量持有了Activity

java 复制代码
public class AppUtils {
    static Activity leakedActivity; // 危险!
    
    static void init(Activity activity) {
        leakedActivity = activity; // Activity关闭后仍被引用
    }
}

修复方案

java 复制代码
// 使用弱引用
static WeakReference<Activity> weakActivity;

2. 非静态内部类(Handler/Runnable)

java 复制代码
public class MainActivity extends Activity {
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // 隐式持有Activity
        }
    };
}

修复方案

java 复制代码
// 方案1:静态内部类+弱引用
private static class SafeHandler extends Handler {
    private WeakReference<Activity> weakActivity;
    
    SafeHandler(Activity activity) {
        this.weakActivity = new WeakReference<>(activity);
    }
    
    @Override
    public void handleMessage(Message msg) {
        Activity activity = weakActivity.get();
        if (activity != null) {
            // 安全使用activity
        }
    }
}

// 方案2:Android Studio推荐做法
private final Handler handler = new Handler(Looper.getMainLooper());

3. 未注销的监听器/广播

java 复制代码
@Override
protected void onCreate(Bundle savedInstanceState) {
    SensorManager manager = (SensorManager) getSystemService(SENSOR_SERVICE);
    manager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
    // 忘记unregisterListener()
}

修复方案

java 复制代码
@Override
protected void onDestroy() {
    sensorManager.unregisterListener(this);
    super.onDestroy();
}

4. 资源未关闭(Cursor/File/Stream)

java 复制代码
InputStream is = getAssets().open("bigfile.txt");
// 读取后忘记is.close()

修复方案

java 复制代码
// 使用try-with-resources(Java 7+)
try (InputStream is = getAssets().open("bigfile.txt")) {
    // 自动关闭
}

三、内存优化五大实战技巧

1. 图片优化三连

java 复制代码
// 1. 使用合适的图片库
Glide.with(this).load(url).into(imageView);

// 2. 大图采样压缩
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2; // 长宽各缩小1/2
Bitmap bitmap = BitmapFactory.decodeFile(path, options);

// 3. 及时回收
if (!bitmap.isRecycled()) {
    bitmap.recycle();
}

2. 数据结构优化

java 复制代码
// 场景:存储大量键值对,key为int类型
HashMap<Integer, Object> map; // 浪费内存
SparseArray<Object> sparseArray; // 更优选择

// 其他优化容器:
ArrayMap, LongSparseArray

3. 避免内存抖动

java 复制代码
// 错误示范:在onDraw()中频繁创建对象
@Override
protected void onDraw(Canvas canvas) {
    Paint paint = new Paint(); // 每次绘制都新建
    canvas.drawText("Hi", 10, 10, paint);
}

// 正确做法:对象复用
private Paint paint = new Paint();
@Override
protected void onDraw(Canvas canvas) {
    canvas.drawText("Hi", 10, 10, paint);
}

4. 使用内存缓存

java 复制代码
// 构建LRU缓存(最大内存的1/8)
int cacheSize = (int) (Runtime.getRuntime().maxMemory() / 1024 / 8);
LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        return bitmap.getByteCount() / 1024;
    }
};

5. 监控工具使用

bash 复制代码
# 查看内存占用
adb shell dumpsys meminfo <package_name>

# 生成HPROF文件分析
adb shell am dumpheap <package_name> /data/local/tmp/heap.hprof
adb pull /data/local/tmp/heap.hprof .

四、LeakCanary原理揭秘

自动检测内存泄漏的神器工作流程:

  1. 监控对象:Activity/Fragment销毁时创建弱引用
  2. 触发GC:尝试回收对象
  3. 检测残留:如果弱引用还能获取到对象
  4. 分析引用链:用HAHA库找出谁在持有该对象
  5. 通知开发者:显示泄漏路径

接入方法

gradle 复制代码
dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}

五、进阶优化方案

1. 使用ViewModel代替静态变量

java 复制代码
// 错误做法
public class UserManager {
    static User currentUser;
}

// 推荐做法
public class UserViewModel extends ViewModel {
    private MutableLiveData<User> currentUser;
    
    public LiveData<User> getUser() {
        if (currentUser == null) {
            currentUser = new MutableLiveData<>();
        }
        return currentUser;
    }
}

2. 优化多进程内存

xml 复制代码
<!-- 在AndroidManifest中声明 -->
<application android:process=":remote">
    <service android:name=".RemoteService"/>
</application>

注意:多进程会额外增加内存开销

3. 监控Native内存

Android 8.0+可通过Debug.getNativeHeapSize()监控Native内存泄漏


六、内存问题排查路线图

  1. 初步定位
    • 使用Android Profiler观察内存曲线
    • 检查Logcat中的GC日志
  2. 确认泄漏
    • LeakCanary自动检测
    • 手动dump堆分析
  3. 修复验证
    • 复现问题场景
    • 对比修复前后内存数据

总结

  • 内存泄漏本质:该回收的对象被错误引用
  • 四大高危场景:静态变量、Handler、监听器、资源未关闭
  • 优化三板斧:图片处理、数据结构、对象复用
  • 工具链:LeakCanary + Android Profiler + MAT

掌握这些技巧,你的App将:

  • 减少崩溃率
  • 提升流畅度
  • 降低功耗
  • 通过严苛的代码审查 🚀
相关推荐
Double Point3 分钟前
(四十三)Dart 中的空安全与 `required` 关键字
android·安全
奔跑吧 android15 分钟前
【android bluetooth 协议分析 01】【HCI 层介绍 1】【hci_packets.pdl 介绍】
android·bluetooth·bt·gabeldorsche·gd·aosp13·bluedroid
冰糖葫芦三剑客2 小时前
安卓 手机拨打电话录音保存地址适配
android
匹马夕阳3 小时前
(十五)安卓开发中不同类型的view之间继承关系详解
android
Jomurphys5 小时前
Android Studio - 解决 Please Select Android SDK
android·android studio
stevenzqzq6 小时前
kotlin扩展函数
android·开发语言·kotlin
V少年6 小时前
深入浅出Java内存模型(JMM)
android
行墨6 小时前
插件资源隔离冲突‌解决方案
android
Hello姜先森6 小时前
Kotlin日常使用函数记录
android·开发语言·kotlin
zhangphil6 小时前
Android Coil 3 Fetcher大批量Bitmap拼接成1张扁平宽图,Kotlin
android·kotlin