背景介绍
之前一篇文章简单整理了Android 性能问题,这里的内存泄漏问题,是其中一部分,想来想去 还是单独整理这一部分,如果想了解Android 性能问题,请移步到 准备☞Android 性能优化
最近项目中一个内存泄露的问题,开始用MAT分析查看,结果太费劲了,还是使用简单粗暴的“武器”吧–LeakCanary (Square公司基于MAT开发的一款 开源的内存检测框架)
配置LeakCanary
1.配置build.gradle
最新的编译版本已经解决Android O (8.0)引入LeakCanary 产生异常问题“UnsupportedOerationException Could not find char array in java.lang.String”
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}
2.权限配置
<application
android:name=".DEVApplication">
<!--SDCard中创建与删除文件权限-->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<!--向SDCard写入数据权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
3.在代码中动态添加权限
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE" };
public void checkPermissions(Activity activity) {
try {
//检测是否有写的权限
int permission = ActivityCompat.checkSelfPermission(activity,
"android.permission.WRITE_EXTERNAL_STORAGE");
if (permission != PackageManager.PERMISSION_GRANTED) {
// 没有写的权限,去申请写的权限,会弹出对话框
Toast.makeText(this, "请开通权限,否则无法使用", Toast.LENGTH_LONG).show();
ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
} else {
Toast.makeText(this, "授权成功", Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
在Activity 的 onCreate()方法里调用 checkPermission(this);
4.添加TestApplication 继承Application类,如果默认有Application类就不用添加XXXApplication了,直在默认的Application添加代码即可。
public class TestApplication extends Application {
private RefWatcher watcher
@Override
public void onCreate() {
watcher = LeakCanary.install(this);
super.onCreate();
}
}
//getRefWatcher静态方法,返回全局监控RefWatcher
public static RefWatcher getRefWatcher(Context context) {
TestApplication testApplication = (TestApplication ) context.getApplicationContext();
return testApplication.refWatcher;
}
LeakCanary 默认监控所有Activity的内存泄露,我们也可以自定义监控对象。
在我们确定不再需要某个对象的时候,调用以下语句,就可以达到监视的目的。
watcher.watch(obj);
5.在MainActivity 添加监听泄露代码
@Override
protected void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = DEVApplication.getRefWatcher(this);
refWatcher.watch(this);
}
Demo演示
下面我们来演示一个简单的内存泄露的问题,主要是由于 静态变量引起的 内存泄露问题。
DEVApplication.java
public class DEVApplication extends Application {
private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
DEVApplication devApplication = (DEVApplication) context.getApplicationContext();
return devApplication.refWatcher;
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
static Context sContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sContext = this;
checkPermissions(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = DEVApplication.getRefWatcher(this);
refWatcher.watch(this);
}
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE" };
public void checkPermissions(Activity activity) {
try {
//检测是否有写的权限
int permission = ActivityCompat.checkSelfPermission(activity,
"android.permission.WRITE_EXTERNAL_STORAGE");
if (permission != PackageManager.PERMISSION_GRANTED) {
// 没有写的权限,去申请写的权限,会弹出对话框
Toast.makeText(this, "请开通权限,否则无法使用", Toast.LENGTH_LONG).show();
ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
} else {
Toast.makeText(this, "授权成功", Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
发生内存泄漏后悔弹出Notification,如下图
点击后查看
可以看出产生内存泄露的原因:Activity由于被静态变量sContext引用,无法正常销毁。静态变量随着类的加载而存在,随着类的消失而消失,不会被GC回收。
解决方法:不要设置成 静态的变量
原理补充
Java对象的四种引用类
强引用
通过new出来的对象, 即使发生OOM也不会被GC回收。
Object object = new Object();软引用
在内存充足的时候和强引用一样,内存不足的时候会被GC回收。
SoftReference softReference = new SoftReference(object);弱引用
无论内存是否充足,触发GC的时候都会被回收。
WeakReference weakReference = new WeakReference(object);虚引用
任何时候都可能被回收。
ReferenceQueue queue = new ReferenceQueue(); PhantomReference phantomReference = new
PhantomReference<>(object, queue);
ReferenceQueue – 引用队列
和软引用,弱引用和虚引用配合使用,当引用的对象将要被JVM回收时,会将其加入到引用队列中。ReferenceQueue – 引用队列
和软引用,弱引用和虚引用配合使用,当引用的对象将要被JVM回收时,会将其加入到引用队列中。Java垃圾回收机制 – 可达性算法
整个Java对象的引用关系可以认为是一颗树,根节点是GCRoot。
可达性算法指从GCRoot开始,能够遍历到的所有对象,认为是可达的,GC不会回收这些对象,没有被遍历到且存在于Java内存中的对象,就会被GC回收。
LeakCanary原理
利用弱引用被回收时会被加入引用队列的机制
- Install 方法在默认在Application注册一个Activity生命周期的回调方法,在Activity的onDestroy回调中进行watch监听。
- 创建一个监听对象的弱引用,放入set集合,手动触发GC。创建一个监听对象的弱引用,放入set集合,手动触发GC。
- 从引用队列中将被要被回收的对象取出来,然后将其从set里移除。从引用队列中将被要被回收的对象取出来,然后将其从set里移除。
- 如果移除后 set不为空,即说明set里的对象发生了内存泄露。如果移除后 set不为空,即说明set里的对象发生了内存泄露。