前言:最近在研究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 如何有效的解决内存泄漏的问题的更多相关文章

  1. [转]Android 如何有效的解决内存泄漏的问题

    Android 如何有效的解决内存泄漏的问题   前言:最近在研究Handler的知识,其中涉及到一个问题,如何避免Handler带来的内存溢出问题.在网上找了很多资料,有很多都是互相抄的,没有实际的 ...

  2. Android开发 |常见的内存泄漏问题及解决办法

    在Android开发中,内存泄漏是比较常见的问题,有过一些Android编程经历的童鞋应该都遇到过,但为什么会出现内存泄漏呢?内存泄漏又有什么影响呢? 在Android程序开发中,当一个对象已经不需要 ...

  3. Android 性能优化之内存泄漏检测以及内存优化(中)

    https://blog.csdn.net/self_study/article/details/66969064 上篇博客我们写到了 Java/Android 内存的分配以及相关 GC 的详细分析, ...

  4. 使用Xcode Instruments Leak解决内存泄漏问题

    iOS 5.0之后apple引入了Xcode编译器特性ARC(Automatic Reference Counting,自动引用计数)来帮助开发者管理内存,但为了追求app的高性能与减少安装包大小,工 ...

  5. 老李分享:Android性能优化之内存泄漏1

    老李分享:Android性能优化之内存泄漏   前言 对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我 ...

  6. ES6通过WeakMap解决内存泄漏问题

    一.Map 1.定义 Map对象保存键值对,类似于数据结构字典:与传统上的对象只能用字符串当键不同,Map对象可以使用任意值当键. 2.语法 new Map([iterable]) 属性 size:返 ...

  7. Android中常见的内存泄漏

    为什么会产生内存泄漏? 当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏. ...

  8. 使用 Android Studio 检测内存泄漏与解决内存泄漏问题

    本文在腾讯技术推文上 修改 发布. http://wetest.qq.com/lab/view/63.html?from=ads_test2_qqtips&sessionUserType=BF ...

  9. android中常见的内存泄漏和解决的方法

    android中的内存溢出预计大多数人在写代码的时候都出现过,事实上突然认为工作一年和工作三年的差别是什么呢.事实上干的工作或许都一样,产品汪看到的结果也都一样,那差别就是速度和质量了. 写在前面的一 ...

随机推荐

  1. 用命令行工具创建 NuGet 程序包

    NuGet.exe 下载地址 本文翻译自: https://docs.nuget.org/Create/Creating-and-Publishing-a-Package https://docs.n ...

  2. Go build constraints

    Go语言有一个不(奇)错(葩)的设计,就是build constraints(构建约束).可以在源码中通过注释的方式指定编译选项,比如只允许在linux下,或者在386的平台上编译啊之类的:还可以通过 ...

  3. docker创建私有仓库

    由于网速和大中华局域网效果,使得我们在DockerHub下载镜像的速度很慢,甚至一些国内的镜像仓库,也感觉速度不是很好.所以,很有必要在本地或者一个我们访问很快速的地方(自己的云服务器)搭建一套镜像仓 ...

  4. 借助亚马逊S3和RapidMiner将机器学习应用到文本挖掘

    本挖掘典型地运用了机器学习技术,例如聚类,分类,关联规则,和预测建模.这些技术揭示潜在内容中的意义和关系.文本发掘应用于诸如竞争情报,生命科学,客户呼声,媒体和出版,法律和税收,法律实施,情感分析和趋 ...

  5. 自学MVC看这里——全网最全ASP.NET MVC 教程汇总

    MVC架构已深得人心,微软也不甘落后,推出了Asp.net MVC.小编特意整理博客园乃至整个网络最具价值的MVC技术原创文章,为想要学习ASP.NET MVC技术的学习者提供一个整合学习入口.本文从 ...

  6. Atitit.每月数据采集与备份 v4

    Atitit.每月数据采集与备份 v4 备份检查表 r12 00cate 00item im Inputmethod  ok ok Log Log ok cyar Cyar log  ... ok c ...

  7. CSS布局之div交叉排布与底部对齐--flex实现

    最近在用wordpress写页面时,设计师给出了一种网页排布图样,之前从未遇到过,其在电脑上(分辨率大于768px)的效果图如下: 而在手机(分辨率小于等于768px)上要求这样排列: 我想到了两种方 ...

  8. 【NLP】揭秘马尔可夫模型神秘面纱系列文章(三)

    向前算法解决隐马尔可夫模型似然度问题 作者:白宁超 2016年7月11日22:54:57 摘要:最早接触马尔可夫模型的定义源于吴军先生<数学之美>一书,起初觉得深奥难懂且无什么用场.直到学 ...

  9. MVC5学习系列--Razor视图(一)

    前言 嗷~小弟我又出现了~咳咳..嚎过头了, 先说一说为什么写这个吧,~首先肯定是我自己需要学(废话 - -,)//,之前也写过MVC4的项目,嗯..但是仅限于使用并没有很深入的每个模块去了解, 这段 ...

  10. JSTL标签 参考手册

    前言 ========================================================================= JSTL标签库,是日常开发经常使用的,也是众多 ...