盒子
盒子
Posts List
  1. 一.前言
  2. 二.解决过程
    1. 1.集成LeakCanary
    2. 2.具体分析
      1. 内存泄露之 LinkedList
      2. 解决方法
      3. 内存泄露之 SqliteOpenHelper
      4. 修改方法
    3. 防止OOM之代码优化
    4. 低内存监听并释放内存缓存
    5. 特定应用(图片,视频)OOM可选处理方式
      1. 如何查看largeHeap后应用可用内存大小
    6. 低内存手机处理

使用leakCanary检测android app 内存泄露

一.前言

接手一个项目,Bugly统计的结果很不乐观,主要的异常包括OOM,nullPointerException。截图如下:

image

按下心情,逐个解决。

二.解决过程

OOM最多,首先看OOM问题,点开看详情

java.lang.OutOfMemoryError
Failed to allocate a 16396 byte allocation with 15133 free bytes and 14KB until OOM

内存用尽,对于普通的app,一般系统授予的进程空间是64M,正常使用是不会耗尽的,首先考虑的是有内存泄露。

1.集成LeakCanary

LeakCanary 用于追踪内存泄露十分方便,决定集成进来监控泄露情况。

集成LeakCanary非常简单,在依赖里面添加

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'

然后在Application的onCreate中添加启动

LeakCanary.install(this);

运行APP,会发现桌面上多了一个黄色叹号的启动图标,点开它能看到内存泄露的完整记录。

将app多个界面来回切换,很快收到LeakCanary的泄露,如下:

image

2.具体分析

内存泄露之 LinkedList

image

从图中能看到BigImageActivity的实例泄露掉了,启动BigImageActivity的代码并无特别之处:

Intent intent = new Intent();
intent.setClass(mContext, BigImageActivity.class);
startActivity(intent);

在BigImageActivity的onCreate中,我将它添加到了MyApplication的链表中,用于在退出的时候销毁

MyApplication.addActivity(this);

这样BigImageActivity finish的时候,MyApplication依旧持有BigImageActivity的引用,造成了泄露。

解决方法

将原来的强引用换成弱引用,弱引用会在每次垃圾回收的时候被释放掉

List<Activity> activitys = new LinkedList<Activity>();

修改为

WeakHashMap<String , Activity> activitys = new WeakHashMap<String , Activity>();

退出方法修改为

public static void exit() {
Set<String> keys = activitys.keySet();
for (String key : keys){
Activity activity = activitys.get(key);
activitys.remove(activity);
activity.finish();
}
}

并且在BaseActivity中重写finish和onCreate方法:

@Override
public void finish() {
MyApplication.removeActivity(this);
super.finish();
}
@Override
protected void onCreate(Bundle arg0) {
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(arg0);
MyApplication.addActivity(this);
}

这样任何新产生的activity自动被添加到WeakHashMap,并在finish的时候,自动从WeakHashMap中移除。重启app,再次测试,由于LinkedList引用导致的泄露消失。

内存泄露之 SqliteOpenHelper

image

这个泄露也比较清晰,在退出BigImageActivity的时候,SqliteOpenHelper 持有 mContext引用,也就是BigImageActivity的引用,导致BigImageActivity未被销毁

修改方法

SqliteOpenHelper 应该是全局性的存储辅助模块,与具体某个activity无关,抽象出来,传入application的context,让它的生命周期与application一致。

在application的onCreate中初始化DaoHelper ,它持有SqliteOpenHelper的引用

rowImageDao = new DaoHelper<RowImage>(this, RowImage.class);

然后在BaseActivity中获得DaoHelper的引用,这样所有的activity都可以使用与activity生命周期无关的DaoHelper

rowImageDao = ((MyApplication)getApplication()).getRowImageDao();

至此运行app,半小时没上报内存泄露问题。

防止OOM之代码优化

在activity中有这么一段代码,初始化volley的ImageLoader,这个ImageLoader实际上是全局性的,因此没必要放在单个activity中。

private void initData() {
mQueue = Volley.newRequestQueue(this);
imageLoader = new ImageLoader(mQueue, new BitmapCache());
listener = ImageLoader.getImageListener(mBigImage,
0, R.drawable.nopic);
}

低内存监听并释放内存缓存

在application中添加 onLowMemory 监听,释放内存缓存

@Override
public void onLowMemory() {
super.onLowMemory();
ImageLoader.getInstance().clearMemoryCache();
}

特定应用(图片,视频)OOM可选处理方式

在实际版本跟踪过程中,仍旧存在OOM问题,部分手机是在可用内存较为充裕的情况下发生的,由于是图片类应用,在androidManifest.xml的application标签中增加选项 android:largeHeap=”true” :

<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@drawable/app_icon"
android:label="@string/app_name"
android:largeHeap="true"
...
</application>

如何查看largeHeap后应用可用内存大小

android studio的terminal中,连上手机,输入

adb shell
cat /system/build.prop

输出如下,最大可使用内存就是headpSize的值:

dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=192m
dalvik.vm.heapsize=512m
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=512k
dalvik.vm.heapmaxfree=8m

低内存手机处理

将bitmap的存储方式由ARGB_8888改为RGB565,这样做会的影响:

  • 降低图片质量,实测从手机上看不出明显区别
  • 图片失去透明通道,透明底图片不适合这种方式,可以考虑Config.ARGB_4444
Config config = Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(w, h, config);
支持一下
扫一扫,支持牛头码农