Android 如何有效的解决内存泄漏的问题
前言:最近在研究Handler的知识,其中涉及到一个问题,如何避免Handler带来的内存溢出问题。在网上找了很多资料,有很多都是互相抄的,没有实际的作用。
本文的内存泄漏检测工具是:LeakCanary github地址:https://github.com/square/leakcanary
什么是内存泄漏?
- 内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗。内存泄漏并不是指物理上的内存消失,这里的内存泄漏是值由程序分配的内存但是由于程序逻辑错误而导致程序失去了对该内存的控制,使得内存浪费。
怎样会导致内存泄漏?
- 资源对象没关闭造成的内存泄漏,如查询数据库后没有关闭游标cursor
- 构造Adapter时,没有使用 convertView 重用
- Bitmap对象不在使用时调用recycle()释放内存
- 对象被生命周期长的对象引用,如activity被静态集合引用导致activity不能释放
内存泄漏有什么危害?
内存泄漏对于app没有直接的危害,即使app有发生内存泄漏的情况,也不一定会引起app崩溃,但是会增加app内存的占用。内存得不到释放,慢慢的会造成app内存溢出。所以我们解决内存泄漏的目的就是防止app发生内存溢出。
1、新建线程引起的Activity内存泄漏
例子:
package rxnet.zyj.com.myapplication; import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View; public class Activity6 extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_6); findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
}); new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟耗时操作
Thread.sleep( 15000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start(); }
}
运行上面的代码后,点击finish按钮,过一会儿发生了内存泄漏的问题。
为什么Activity6会发生内存泄漏?
进入Activity6 界面,然后点击finish按钮,Activity6销毁,但是Activity6里面的线程还在运行,匿名内部类Runnable对象引用了Activity6的实例,导致Activity6所占用的内存不能被GC及时回收。
如何改进?
Runnable改为静态非匿名内部类即可。
package rxnet.zyj.com.myapplication; import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View; public class Activity6 extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_6); findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
}); new Thread( new MyRunnable()).start(); } private static class MyRunnable implements Runnable { @Override
public void run() {
try {
Thread.sleep( 15000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} }
2、Activity添加监听器造成Activity内存泄漏
package rxnet.zyj.com.myapplication; import android.app.Activity;
import android.os.Bundle; public class LeakActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NastyManager.getInstance().addListener(this);
}
}
这个是在开发中经常会犯的错误,NastyManager.getInstance() 是一个单例,当我们通过 addListener(this) 将 Activity 作为 Listener 和 NastyManager 绑定起来的时候,不好的事情就发生了。
如何改进?
想要修复这样的 Bug,其实相当简单,就是在你的 Acitivity 被销毁的时候,将他和 NastyManager 取消掉绑定就好了。
package rxnet.zyj.com.myapplication; import android.app.Activity;
import android.os.Bundle; public class LeakActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NastyManager.getInstance().addListener(this);
} @Override
protected void onDestroy() {
super.onDestroy();
NastyManager.getInstance().removeListener(this);
}
}
3、Handler 匿名内部类造成内存溢出?
先看着一段代码
package rxnet.zyj.com.myapplication; import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View; public class HandlerActivity extends AppCompatActivity { private final static int MESSAGECODE = 1 ; private final Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d("mmmmmmmm" , "handler " + msg.what ) ;
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler); findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
}); new Thread(new Runnable() {
@Override
public void run() {
handler.sendEmptyMessage( MESSAGECODE ) ;
try {
Thread.sleep( 8000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage( MESSAGECODE ) ;
}
}).start() ; }
}
这段代码运行起来后,立即点击 finish 按钮,通过检测,发现 HandlerActivity 出现了内存泄漏。当Activity finish后,延时消息会继续存在主线程消息队列中8秒钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。Handler 是个很常用也很有用的类,异步,线程安全等等。如果有下面这样的代码,会发生什么呢? handler.postDeslayed ,假设 delay 时间是几个小时… 这意味着什么?意味着只要 handler 的消息还没有被处理结束,它就一直存活着,包含它的 Activity 就跟着活着。我们来想办法修复它,修复的方案是 WeakReference ,也就是所谓的弱引用。垃圾回收器在回收的时候,是会忽视掉弱引用的,所以包含它的 Activity 会被正常清理掉。
如何避免
- 使用静态内部类
- 使用弱引用
修改后代码是这样的。
package rxnet.zyj.com.myapplication; import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View; import java.lang.ref.WeakReference; public class HandlerActivity extends AppCompatActivity { private final static int MESSAGECODE = 1 ;
private static Handler handler ; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler); findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
}); handler = new MyHandler( this ) ; new Thread(new Runnable() {
@Override
public void run() {
handler.sendEmptyMessage( MESSAGECODE ) ;
try {
Thread.sleep( 8000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage( MESSAGECODE ) ;
}
}).start() ; } private static class MyHandler extends Handler {
WeakReference<HandlerActivity> weakReference ; public MyHandler(HandlerActivity activity ){
weakReference = new WeakReference<HandlerActivity>( activity) ;
} @Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if ( weakReference.get() != null ){
// update android ui
Log.d("mmmmmmmm" , "handler " + msg.what ) ;
}
}
}
}
这个Handler已经使用了静态内部类,并且使用了弱引用。但是这个并没有完全解决 HandlerActivity 内存泄漏的问题,罪魁祸首是线程创建的方式出了问题,就像本文的第一个例子一样。改进的方式,是把Runnable类写成静态内部类。
最终完整的代码如下:
package rxnet.zyj.com.myapplication; import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View; import java.lang.ref.WeakReference; public class HandlerActivity extends AppCompatActivity { private final static int MESSAGECODE = 1 ;
private static Handler handler ; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler); findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
}); //创建Handler
handler = new MyHandler( this ) ; //创建线程并且启动线程
new Thread( new MyRunnable() ).start();
} private static class MyHandler extends Handler {
WeakReference<HandlerActivity> weakReference ; public MyHandler(HandlerActivity activity ){
weakReference = new WeakReference<HandlerActivity>( activity) ;
} @Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if ( weakReference.get() != null ){
// update android ui
Log.d("mmmmmmmm" , "handler " + msg.what ) ;
}
}
} private static class MyRunnable implements Runnable { @Override
public void run() {
handler.sendEmptyMessage( MESSAGECODE ) ;
try {
Thread.sleep( 8000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage( MESSAGECODE ) ;
}
}
}
等等,还没完呢?
上面这个代码已经有效的解决了Handler,Runnable 引用Activity实例从而导致内存泄漏的问题,但是这不够。因为内存泄漏的核心原因就是这个某个对象应该被系统回收内存的时候,却被其他对象引用,造成该内存无法回收。所以我们在写代码的时候,要始终绷着这个弦。再回到上面这个问题,当当前Activity调用finish销毁的时候,在这个Activity里面所有线程是不是应该在OnDestory()方法里,取消线程。当然是否取消异步任务,要看项目具体的需求,比如在Activity销毁的时候,启动一个线程,异步写log日志到本地磁盘,针对这个需求却需要在OnDestory()方法里开启线程。所以根据当前环境做出选择才是正解。
所以我们还可以修改代码为:在onDestroy() 里面移除所有的callback 和 Message 。
package rxnet.zyj.com.myapplication; import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View; import java.lang.ref.WeakReference; public class HandlerActivity extends AppCompatActivity { private final static int MESSAGECODE = 1 ;
private static Handler handler ; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler); findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
}); //创建Handler
handler = new MyHandler( this ) ; //创建线程并且启动线程
new Thread( new MyRunnable() ).start();
} private static class MyHandler extends Handler {
WeakReference<HandlerActivity> weakReference ; public MyHandler(HandlerActivity activity ){
weakReference = new WeakReference<HandlerActivity>( activity) ;
} @Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if ( weakReference.get() != null ){
// update android ui
Log.d("mmmmmmmm" , "handler " + msg.what ) ;
}
}
} private static class MyRunnable implements Runnable { @Override
public void run() {
handler.sendEmptyMessage( MESSAGECODE ) ;
try {
Thread.sleep( 8000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage( MESSAGECODE ) ;
}
} @Override
protected void onDestroy() {
super.onDestroy(); //如果参数为null的话,会将所有的Callbacks和Messages全部清除掉。
handler.removeCallbacksAndMessages( null );
}
}
4、AsyncTask造成内存泄漏
package rxnet.zyj.com.myapplication; import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View; public class Activity2 extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_2); findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
}); new AsyncTask<String,Integer,String>(){ @Override
protected String doInBackground(String... params) {
try {
Thread.sleep( 6000 );
} catch (InterruptedException e) {
}
return "ssss";
} @Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
Log.d( "mmmmmm activity2 " , "" + s ) ;
} }.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "" ) ; }
}
为什么?
上面代码在activity中创建了一个匿名类AsyncTask,匿名类和非静态内部类相同,会持有外部类对象,这里也就是activity,因此如果你在Activity里声明且实例化一个匿名的AsyncTask对象,则可能会发生内存泄漏,如果这个线程在Activity销毁后还一直在后台执行,那这个线程会继续持有这个Activity的引用从而不会被GC回收,直到线程执行完成。
怎么解决?
- 自定义静态AsyncTask类
- AsyncTask的周期和Activity周期保持一致。也就是在Activity生命周期结束时要将AsyncTask cancel掉。
package rxnet.zyj.com.myapplication; import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View; public class AsyncTaskActivity extends AppCompatActivity { private static MyTask myTask ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_asynctask); findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
}); myTask = new MyTask() ;
myTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "") ; } private static class MyTask extends AsyncTask{ @Override
protected Object doInBackground(Object[] params) {
try {
//模拟耗时操作
Thread.sleep( 15000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
return "";
}
} @Override
protected void onDestroy() {
super.onDestroy(); //取消异步任务
if ( myTask != null ){
myTask.cancel(true ) ;
}
}
}
5、Timer Tasks 造成内存泄漏
package rxnet.zyj.com.myapplication; import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View; import java.util.Timer;
import java.util.TimerTask; public class TimerActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_2); findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
}); //开始定时任务
timer();
} void timer(){
new Timer().schedule(new TimerTask() {
@Override
public void run() {
while(true);
}
},1000 ); // 1秒后启动一个任务
}
}
为什么?
这里内存泄漏在于Timer和TimerTask没有进行Cancel,从而导致Timer和TimerTask一直引用外部类Activity。
怎么解决?
- 在适当的时机进行Cancel。
- TimerTask用静态内部类
注意:在网上看到一些资料说,解决TimerTask内存泄漏可以使用在适当的时机进行Cancel。经过测试,证明单单使用在适当的时机进行Cancel , 还是有内存泄漏的问题。所以一定要用静态内部类配合使用。
package rxnet.zyj.com.myapplication; import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View; import java.util.Timer;
import java.util.TimerTask; public class TimerActivity extends AppCompatActivity { private TimerTask timerTask ; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_2); findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
}); //开始定时任务
timer();
} void timer(){
timerTask = new MyTimerTask() ;
new Timer().schedule( timerTask ,1000 ); // 1秒后启动一个任务
} private static class MyTimerTask extends TimerTask{ @Override
public void run() {
while(true){
Log.d( "ttttttttt" , "timerTask" ) ;
}
}
} @Override
protected void onDestroy() {
super.onDestroy(); //取消定时任务
if ( timerTask != null ){
timerTask.cancel() ;
}
}
}
参考资料
Android 如何有效的解决内存泄漏的问题的更多相关文章
- [转]Android 如何有效的解决内存泄漏的问题
Android 如何有效的解决内存泄漏的问题 前言:最近在研究Handler的知识,其中涉及到一个问题,如何避免Handler带来的内存溢出问题.在网上找了很多资料,有很多都是互相抄的,没有实际的 ...
- Android开发 |常见的内存泄漏问题及解决办法
在Android开发中,内存泄漏是比较常见的问题,有过一些Android编程经历的童鞋应该都遇到过,但为什么会出现内存泄漏呢?内存泄漏又有什么影响呢? 在Android程序开发中,当一个对象已经不需要 ...
- Android 性能优化之内存泄漏检测以及内存优化(中)
https://blog.csdn.net/self_study/article/details/66969064 上篇博客我们写到了 Java/Android 内存的分配以及相关 GC 的详细分析, ...
- 使用Xcode Instruments Leak解决内存泄漏问题
iOS 5.0之后apple引入了Xcode编译器特性ARC(Automatic Reference Counting,自动引用计数)来帮助开发者管理内存,但为了追求app的高性能与减少安装包大小,工 ...
- 老李分享:Android性能优化之内存泄漏1
老李分享:Android性能优化之内存泄漏 前言 对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我 ...
- ES6通过WeakMap解决内存泄漏问题
一.Map 1.定义 Map对象保存键值对,类似于数据结构字典:与传统上的对象只能用字符串当键不同,Map对象可以使用任意值当键. 2.语法 new Map([iterable]) 属性 size:返 ...
- Android中常见的内存泄漏
为什么会产生内存泄漏? 当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏. ...
- 使用 Android Studio 检测内存泄漏与解决内存泄漏问题
本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...
- android中常见的内存泄漏和解决的方法
android中的内存溢出预计大多数人在写代码的时候都出现过,事实上突然认为工作一年和工作三年的差别是什么呢.事实上干的工作或许都一样,产品汪看到的结果也都一样,那差别就是速度和质量了. 写在前面的一 ...
随机推荐
- 需要UWP Vendor一名
工作地点北京,海淀,微软大厦2号楼,小冰项目组.
- Angular2学习笔记——路由器模型(Router)
Angular2以组件化的视角来看待web应用,使用Angular2开发的web应用,就是一棵组件树.组件大致分为两类:一类是如list.table这种通放之四海而皆准的通用组件,一类是专为业务开发的 ...
- a different object with the same identifier value was already associated with the session:
hibernate操作: 实例化两个model类,更新时会提示 a different object with the same identifier value was already assoc ...
- VirtualBox 桥接上网方式的配置
最近在搞Redis所以装了个virtualbox的ubuntu的虚拟机, redis不是在ubuntu上. 因为需要使用本机客户端访问redis服务,所以需要配置虚拟机和本地机器的双向访问,所以就用到 ...
- (实例篇)PHP实现HTTP断点续传的方法
PHP实现HTTP断点续传的方法. <?php /** * PHP-HTTP断点续传实现 * @param string $path: 文件所在路径 * @param string $file: ...
- 百度API ; 很多有用的接口及公用 数据
百度API : http://apistore.baidu.com/ . 比如手机号码:
- 2.Kali安装VMware tools(详细+异常处理)
dnt@MT:~$ cd /media/cdrom0 进入光驱内 dnt@MT:/media/cdrom0$ ls 查看当前目录下有哪些内容manifest.txt run_upgrader.sh V ...
- STL标准模板库(简介)
标准模板库(STL,Standard Template Library)是C++标准库的重要组成部分,包含了诸多在计算机科学领域里所常见的基本数据结构和基本算法,为广大C++程序员提供了一个可扩展的应 ...
- Linux平台 Oracle 11gR2 RAC安装Part1:准备工作
一.实施前期准备工作 1.1 服务器安装操作系统 1.2 Oracle安装介质 1.3 共享存储规划 1.4 网络规范分配 二.安装前期准备工作 2.1 各节点系统时间校对 2.2 各节点关闭防火墙和 ...
- “NOSQL” 杂谈
引言: nosql 的兴起和革命,在我看来已经开始逐渐影响到了传统的sql的地位,但是仅仅是影响而已,取代是不太可能的. 正文: 两年前,一个偶然的机会开始接触到 nosql ( mongodb ). ...