深入浅出安卓内存泄漏与优化
一、什么是内存泄漏?
内存泄漏就像**"借书不还"**:
- 你向图书馆(系统)借了一本书(内存)
- 用完后没有归还(释放)
- 图书馆的书越来越少(可用内存减少)
- 最后无书可借(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原理揭秘
自动检测内存泄漏的神器工作流程:
- 监控对象:Activity/Fragment销毁时创建弱引用
- 触发GC:尝试回收对象
- 检测残留:如果弱引用还能获取到对象
- 分析引用链:用HAHA库找出谁在持有该对象
- 通知开发者:显示泄漏路径
接入方法:
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内存泄漏
六、内存问题排查路线图
- 初步定位 :
- 使用Android Profiler观察内存曲线
- 检查Logcat中的GC日志
- 确认泄漏 :
- LeakCanary自动检测
- 手动dump堆分析
- 修复验证 :
- 复现问题场景
- 对比修复前后内存数据
总结
- 内存泄漏本质:该回收的对象被错误引用
- 四大高危场景:静态变量、Handler、监听器、资源未关闭
- 优化三板斧:图片处理、数据结构、对象复用
- 工具链:LeakCanary + Android Profiler + MAT
掌握这些技巧,你的App将:
- 减少崩溃率
- 提升流畅度
- 降低功耗
- 通过严苛的代码审查 🚀