Android内存泄漏专题分析
一、 资源对象未关闭引起的内存泄漏
Android的资源对象在不再使用时,及时关闭对象,这类资源对象有Receiver、Cursor、Bitmap、文件对象File以及InputStream、OutputStream衍生类对象未关闭。
举几个例子:
1、 registerReceiver(mReceiver, filter)后,在退出Activity后未调用unregisterReceiver(mReceiver)。
下面是用LeakCanary抓取的Receiver泄漏:
2、创建Bitmap,但是未在退出Activity时调用Bitmap的recycle()方法。
为什么Bitmap那么特殊,还需要调用recycle()来释放内存?简单地说是因为Bitmap通过BitmapFactory.decodeXXX接口解码后会在java和native层都存有内存,java层的Bitmap实际上是native层通过反射调用Bitmap的createBitmap方法得到的引用。调用recycle()主要是主动释放native层的内存。
有兴趣可以参考以下链接,过程分析的比较清晰:
https://blog.csdn.net/qq_26984087/article/details/89320850
如果不调用recycle(),通过LeakCanary等工具是无法检测出来的,因为内存泄漏在native层。
其他Cursor、File、InputStream、OutputStream等,就不详细举例了。
二、 Activity的Context引起的内存泄漏
Activity的Context生命周期和Activity是一致的,如果被其他对象持有,在Activity生命周期结束后未释放,就会引起内存泄漏。解决方法很简单,需要用到Context的地方使用getApplicationContext()代替。
三、 属性动画未取消引起的内存泄漏
从 Android3.0开始,Google提供了属性动画,属性动画中有一类无限循环的动画,如果在Activity中播放此类动画且没有在 onDestroy中去停止动画,那么动画会一直播放下去,尽管已经无法在界面上看见动画效果了,并且这个时候 Activity的 View会被动画持有,而View又持有了Activty, 最终Activity无法释放。源码分析可以参考:https://blog.csdn.net/qq_36356379/article/details/97125062。
下面的动画是无限动画,会泄露当前的Activity, 解决方法是在Activity的onDestroy中调用animator.cancel()来停止动画。
四、 WebView引起的内存泄漏
在android5.1及以上版本中,webview会引起内存泄漏。解决方法是在onDetachedFromWindow中先将父view中移除WebView,再调用destroy方法。例如:
public voidonDestroy() {
if (mWebView != null) {
ViewParent parent = mWebView.getParent();
if(parent != null) {
((ViewGroup)parent).remove(mWebView);
}
mWebView.stopLoading();
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
try{
mWebView.destroy();
mWebView= null;
} catch (Exception err) {
}
}
}
五、 单例引起的内存泄漏
单例因为声明周期是和整个应用相同的,即使某些对象(如Activity)退出了,但是如果这个单例持有这个Activity,例如常见的单例参数中传入Context,Activity退出后,这个Context就被单例持有,引起内存泄漏。举个例子,我们有时候在Activity里写个管理类用来做逻辑处理:
mAppTestMgr = AppTestManager.getInstance(this);
public class AppTestManager {
private static AppTestManager sInstance;
private AppTestManager(Context context) {
mContext = context;
}
public static AppTestManager getInstance(Context context) {
if (sInstance == null) {
synchronized (AppTestManager.class) {
if (sInstance == null) {
sInstance = new AppTestManager(context);
}
}
}
return sInstance;
}
}
这个AppTestManager就是个简单的单例实现,getInstance传入的参数就是Context类型,AppTestManager类持有Activity实例。Activity退出后,就会报内存泄漏:
正确的方法是初始化单例时,传入Application
Context,这个是和进程生命周期一致的:
AppTestManager.getInstance(getApplicationContext());
六、 Java非静态内部类引起的内存泄漏
Android开发中比较常见的内部类有Handler、AsyncTask、自定义的非静态内部类等等,如果类似以下采用以下方式,就会引起内存泄漏。
6.1 Handler的错误用法:
private void updateMemLeakTitle() {
if (mTitleTextView != null) {
mTitleTextView.setText(R.string.mem_leak_title_text);
}
if (mMainHandler != null) {
mMainHandler.sendEmptyMessageDelayed(MSG_UPDATE_TEXT, 2000);
}
}
private class MainHanlder extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MSG_UPDATE_TEXT:
updateMemLeakTitle();
break;
}
}
};
java语法指出在内部类Outer$Inner中, 存在一个名字为this$0 , 类型为Outer的成员变量, 并且这个变量是final的。 其实这个就是所谓的“在内部类对象中存在的指向外部类对象的引用。也就是说像以上MainHandler类有个隐含的引用指向了外部类Activity,在Activity退出后,内部类执行的内容尚未执行完,这个引用并未被释放,从而引起内存泄漏。
Handler的内存泄漏修改方法就是在退出Activity时,移除所有的消息并将handler置空,或者将Handler改为静态内部类。
方法一:在退出Activity时,移除所有的消息并将handler置空。
@Override
protected void onStop() {
super.onStop();
if (mMainHandler != null) {
mMainHandler.removeCallbacksAndMessages(null);
mMainHandler = null;
}
}
方法二:将Handler改为静态内部类,采用WeakReference引用外部接口。
private static class MainHanlder extends Handler {
private WeakReference
public MainHanlder(MainActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MSG_UPDATE_TEXT:
mActivity.get().updateMemLeakTitle();
break;
}
}
};
6.2 AsyncTask的错误用法
AsyncTask和Handler有些类似,也是非静态内部类引起的内存泄漏,修改方案也可以参考方法二。例如这么写就会出现内存泄漏问题:
private class UpdateTask extends AsyncTask {
@Override
protected String doInBackground(Void... voids) {
try {
Thread.sleep(5000);
} catch (Exception err) {
}
String text = mContext.getResources().getString(R.string.mem_leak_title_text);
return text;
}
@Override
protected void onPostExecute(String text) {
super.onPostExecute(text);
updateMemLeakTitle(text);
}
};
同样的把AsyncTask的内部类改造成静态内部类,并用WeakReference引用Activity的方法:
private static class UpdateTask extends AsyncTask {
private WeakReference
public UpdateTask(MainActivity activity) {
mActivityRef = new WeakReference<>(activity);
}
@Override
protected String doInBackground(Void... voids) {
Log.d(TAG, "
try {
Thread.sleep(5000);
} catch (Exception err) {
}
String text = mActivityRef.get().getResources().getString(R.string.mem_leak_title_text);
return text;
}
@Override
protected void onPostExecute(String text) {
super.onPostExecute(text);
mActivityRef.get().updateMemLeakTitle(text);
}
};
6.3 自定义的非静态内部类
类似Handler和AsyncTask的原因及解决方案,因为情况比较多,就不一一列举了。
七、 未释放过期的对象引用
先看个Effective Java中提到的例子:
publicclass Stack {
private Object[] elements;
private int size = 0;
private static final intDEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = newObject[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) {
throw newEmptyStackException();
}
return elements[--size];
}
private void ensureCapacity() {
if (elements.length == size){
elements =Arrays.copyOf(elements, 2 * size + 1);
}
}
}
能不能看出来,内存泄漏的问题在哪里?
如果一个栈显示增长,然后再收缩,那么从栈中弹出的对象将不会被当做垃圾回收,即使使用栈的程序不再引用这些对象。因为栈内部维护者对这些对象的过期引用(永远也不会再被解除的引用)。
修复方案很简单:一旦对象引用已经过期了,只需清空这些引用即可。
publicObject pop() {
if (size == 0) {
throw newEmptyStackException();
}
Object result = elements[--size];
elements[size] = null;
return result;
}
八、 Sensor Manager的使用
通过Context.getSystemService(int name)可以获取系统服务。这些服务工作在各自的进程中,帮助应用处理后台任务,处理硬件交互。如果需要使用这些服务,可以注册监听器,这会导致服务持有了Context的引用,如果在Activity销毁的时候没有注销这些监听器,会导致内存泄漏。例如:
private SensorManager mSensorManager;
private Sensor mSensor;
private void registerListener() {
mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ALL);
mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_FASTEST);
}
解决方法是在Activity退出时,在onDestroy方法中调用unregisterListener方法。
@Override
protected void onDestroy() {
super.onDestroy();
unregisterListener();
}
private void unregisterListener() {
if (mSensorManager != null) {
mSensorManager.unregisterListener(this, mSensor);
}
}
九、 内存泄漏的检测工具介绍
MAT工具、AndroidStudio、LeakCanary
LeakCanary的使用:
1、在工程app目录下的build.gradle中找到dependencies区域,增加如下代码:
1、在build.gradle中找到dependencies区域,增加如下代码:
debugCompile'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseCompile'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
重新编译工程,这样AndroidStudio会自动为App增加LeakCanary的库文件。
2、App文件的TvApplication.java找到onCreate方法,增加如下代码:
增加变量定义:
private RefWatcher refWatcher;//leak canary test
public void onCreate() {
sRefWatcher = initLeakCanary();
}
增加如下函数:
private RefWatcher initLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return RefWatcher.DISABLED;
}
return LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
return sRefWatcher;
}
3、在需要测试的java文件的onStop中增加如下代码:
@Override
protected void onStop() {
super.onStop();
RefWatcher refWatcher = MemLeakTestApplication.getRefWatcher(this);
refWatcher.watch(this);
}
然后就可以开始测试了,按照测试原理,只要退出应用,即调用到onStop即可抓取泄漏内容。
重新编译Launcher,这样AndroidStudio会自动为App增加LeakCanary的库文件。
参考文献:
Android内存泄漏的八种可能:
https://www.jianshu.com/p/ac00e370f83d?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
https://www.jianshu.com/p/c5ac51d804fa
https://www.cnblogs.com/zhaoyanjun/p/5981386.html
内存泄漏工具:
https://blog.csdn.net/yulianlin/article/details/50393872