您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
Android中常见的内存泄漏
 
作者:huachao1001 来源:博客 发布于:2016-8-26
  1036  次浏览      15
 

我们经常会在不经意间写出造成内存泄漏的代码,往往在代码上很难查出来。但是我们可以通过一些辅助工具来检测是否存在内存泄漏,比如通过AndroidStudio的monitors来查看内存的变化情况,或者是通过开源框架《LeakCanary》来检测。本文主要是从网络中搜索汇总一些常见的内存泄漏,一方面自己应对校招,另一方面以后自己写代码时也会注意这些问题。当然了,还有一方面就是方便大家~

1 Activity对象未被回收

本节是从《Eight Ways Your Android App Can Leak Memory》中学习并总结。

1.1 静态变量引用Activity对象

通过静态变量引用Activty对象时,会导致Activty对象所占内存内漏。主要是因为,静态变量是驻扎在JVM的方法区,因此,静态变量引用的对象是不会被GC回收的,因为它们所引用的对象本身就是GC ROOT(这块不清楚的请参考我的另一篇文章《JVM理解其实并不难! 》)。即最终导致Activity对象不被回收,从而也就造成内存泄漏。

看个简单例子,比如说,你应用启动Activty的场景很多,你希望定义一个工具类Util.java,在这个类中,定义一个启动Activty的方法startActivity(Class nextActivity);以此来简化启动Activty的代码。另外,加入你当前的Activty启动另一个Activty的代码使用率也特别高。为了使得参数尽可能的少,你提供setFirstActivty,保存当前的Activty。代码如下:

Util.java

/**
* Created by HuaChao on 2016/8/13.
*/
public class Util {

private static Activity sActivity;

public static void setActivity(Activity activity) {
sActivity = activity;
}

public static void startActivity(Class nextActivity) {
Intent intent = new Intent(sActivity, nextActivity);
sActivity.startActivity(intent);
}
}

在当前的Activty中,只需在onCreate中调用Util.setFirstActivity(this);,在需要启动另一个Activty处调用Util.startActivity(SecondActivity.class);。

在上面代码中,如果当前的Activty不再使用且Util中的sActivity对象没有更改,会导致当前Activty一直驻留在内存中。

1.2 静态View

有时,当一个Activity经常启动,但是对应的View读取非常耗时,我们可以通过静态View变量来保持对该Activity的rootView引用。这样就可以不用每次启动Activity都去读取并渲染View了。这确实是一个提高Activity启动速度的好方法!但是要注意,一旦View attach到我们的Window上,就会持有一个Context(即Activity)的引用。而我们的View有事一个静态变量,所以导致Activity不被回收。当然了,也不是说不能使用静态View,但是在使用静态View时,需要确保在资源回收时,将静态View detach掉。

1.3 内部类

我们知道,非静态内部类持有外部类的一个引用。因此,如果我们在一个外部类中定义一个静态变量,这个静态变量是引用内部类对象。将会导致内存泄漏!因为这相当于间接导致静态引用外部类。

1.4 匿名类

与内部类一样,匿名类也会持有外部类的引用。

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
//另一个线程中持有Activity的引用,并且不释放
while (true) ;
}
}.execute();

}

1.5 Handler

我们知道,主线程的Looper对象不断从消息队列中取出消息,然后再交给Handler处理。如果在Activity中定义Handler对象,那么Handler肯定是持有Activty的引用。而每个Message对象是持有Handler的引用的(Message对象的target属性持有Handler引用),从而导致Message间接引用到了Activity。如果在Activty destroy之后,消息队列中还有Message对象,Activty是不会被回收的。当然了,如果消息正在准备(处于延时入队期间)放入到消息队列中也是一样的。

private final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {

}
};


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


handler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ }
}, Integer.MAX_VALUE);
}

解决办法就是,将Handler放入单独的类或者将Handler放入到静态内部类中(静态内部类不会持有外部类的引用)。如果想要在handler内部去调用所在的外部类Activity,可以在handler内部使用弱引用的方式指向所在Activity,这样不会导致内存泄漏。

1.6 Threads和TimerTask

Threads和Timer导致内存泄漏的原因跟内部类一样。虽然在新的线程中创建匿名类,但是只要是匿名类/内部类,它都会持有外部类引用。

void spawnThread() {
new Thread() {
@Override public void run() {
while(true);
}
}.start();
}

void scheduleTimer() {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
}

1.7 监听器

当我们需要使用系统服务时,比如执行某些后台任务、为硬件访问提供接口等等系统服务。我们需要把自己注册到服务的监听器中。然而,这会让服务持有 activity 的引用,如果程序员忘记在 activity 销毁时取消注册,那就会导致 activity 泄漏了。

void registerListener() {
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}

2 集合对象造成的泄漏

当我们定义一个静态的集合类时,请注意,这可能会导致内存泄漏!前面我们提到过,静态变量所引用的对象是不会被回收掉的。而我的静态集合类中,包含有大量的对象,这些对象不会被回收。另外,如果集合中保存的对象又引用到了其他的大对象,如超长字符串、Bitmap、大数组等,很容易造成OOM。

3 资源对象没关闭造成内存泄漏

当我们打开资源时,一般都会使用缓存。比如读写文件资源、打开数据库资源、使用Bitmap资源等等。当我们不再使用时,应该关闭它们,使得缓存内存区域及时回收。虽然有些对象,如果我们不去关闭,它自己在finalize()函数中会自行关闭。但是这得等到GC回收时才关闭,这样会导致缓存驻留一段时间。如果我们频繁的打开资源,内存泄漏带来的影响就比较明显了。

4 使用对象池避免频繁创建对象

在我们需要频繁创建使用某个类时,或者是在for循环里面创建新的对象时,导致JVM不断创建同一个类。我们知道,在使用Message对象时,不是直接new出来的,而是通过obtain方法获取,以及recycle方法回收。这是典型的享元模式(不熟悉的同学参考《从Android代码中来记忆23种设计模式 》)。我们可以通过使用对象池来实现.

import android.support.v4.util.Pools;

/**
* Created by HuaChao on 2016/8/13.
*/
public class MyObject {
private static final Pools.SynchronizedPool<MyObject> MY_POOLS = new Pools.SynchronizedPool<>(10);

public static MyObject obtain() {
MyObject object = MY_POOLS.acquire();
if (object == null)
object = new MyObject();
return object;
}

public void recycle() {
MY_POOLS.release(this);
}
}

   
1036 次浏览       15
 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]

android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...