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

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

一、什么是内存泄漏?

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

  • 你向图书馆(系统)借了一本书(内存)
  • 用完后没有归还(释放)
  • 图书馆的书越来越少(可用内存减少)
  • 最后无书可借(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将:

  • 减少崩溃率
  • 提升流畅度
  • 降低功耗
  • 通过严苛的代码审查 🚀
相关推荐
橙子199110162 小时前
在 Kotlin 中什么是委托属性,简要说说其使用场景和原理
android·开发语言·kotlin
androidwork2 小时前
Kotlin Android LeakCanary内存泄漏检测实战
android·开发语言·kotlin
笨鸭先游3 小时前
Android Studio的jks文件
android·ide·android studio
gys98953 小时前
android studio开发aar插件,并用uniapp开发APP使用这个aar
android·uni-app·android studio
H309193 小时前
vue3+dhtmlx-gantt实现甘特图展示
android·javascript·甘特图
像风一样自由3 小时前
【001】renPy android端启动流程分析
android·gitee
千里马学框架5 小时前
重学安卓14/15自由窗口freeform企业实战bug-学员作业
android·framework·bug·systrace·安卓framework开发·安卓窗口系统·自由窗口
xianrenli3810 小时前
android特许权限调试
android
*拯13 小时前
Uniapp Android/IOS 获取手机通讯录
android·ios·uni-app
天天打码15 小时前
Lynx-字节跳动跨平台框架多端兼容Android, iOS, Web 原生渲染
android·前端·javascript·ios