Android内存优化---关于内存泄漏场景
内存泄漏的由来
Android系统对每个应用都分配了一定大小的内存,内存的大小取决于终端硬件的配置和系统内部的算法。这个内存是存在上限的,Android系统为了充分使用有效的内存,会在应用内存不够用时,及时使用垃圾回收算法,回收已经使用过的内存。在Android系统回收过程中,使用的算法成为标记算法,对于GC Root不可达的对象,标记为可回收对象。那么内存泄漏的本质就是该对象理论上已经无用了,但还是GC Root可达的,那么它就不能被清除,此时的对象就像无形的占用了一部分内存,使得可用物理内存变小。
内存泄漏的原因
- 程序编码存在bug或者瑕疵,对内存的使用没有很好理解,以及对一些组件过度滥用导致内存泄漏。
- 使用的第三方框架存在泄漏
- Android系统本身存在的内容泄漏(inputmethodmanager等)
内存泄漏的场景
非静态内部类的静态实例
非静态的内部类会持有外部类的实例,如果非静态的内部类的实例是静态的,那么它将长期持有外部类的引用,阻止系统被回收。
如下面的例子:
public class MemoryActivity extends AppCompatActivity {
private int[] array = new int[1024 * 1024 * 50];
private static InnerClass innerClass ;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
innerClass = new InnerClass();
}
private class InnerClass{}
}
静态的InnerClass对象innerClass的生命周期将于Application的生命周期一致,由于此时的innerClass引用了MemoryActivity,即使MemoryActivity调用了onDestory()方法,也将不会从内存被去除,上面的代码你多点击几次,将会出现下面的异常:
此时的内存泄漏是由于生命周期短的引用了生命周期长的对象所导致的。
而如果将InnerClass换成外部实现的OuterClass,
public class MemoryActivity extends AppCompatActivity {
private int[] array = new int[1024 * 1024 * 50];
private static OuterClass outerClass;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
outerClass = new OuterClass();
}
}
同上面的执行,将不会报OOM,这是因为InnerClass中有一个引用指向了MemoryActivity,而此时的InnerClass又设置为了静态的,那么我们可以理解为非静态的内部类的静态实例其实已经将该外部类设置为了静态实例,那么它除非到app被完全退出,即使它调用了onDestory方法,也将不会释放资源。
多线程中匿名内部类/非静态内部类
其实这种也和上面的非静态内部类的静态对象所属于同一种问题,就是生命周期短的对象引用了生命周期长的对象,最后导致对象都不能被回收利用。
public class MemoryActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Thread(){
@Override
public void run() {
try {
Thread.sleep(40000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
}
我们退出该Activity时,如果子线程还没有执行完成,那么Activity退出栈时将不能被回收,此时将会造成内存泄漏。
Handler的泄漏
也应该算得上最大众最平常的内存泄漏吧,很多人在都在这个上内存泄漏过。我们一般定义Handler时,AS系统就会有警告:
上面大体的意思就是,不推荐这么使用Handler,因为会有内存泄漏的几率。原因是Handler引用了Message,而Message会存储在MessageQueue中,一旦Message在MessageQueue中delay时间过长,将会导致Handler无法被回收,而handler此时关联着Activity或者Service,则会导致其不能被正常的回收,从而导致内存泄漏。对于Handler,大家可以翻阅一下Handler源码,理清楚Handler&Message&MessageQUeue&Looper的关系,那么问题就好解决了。
一般的解决方法有两种:
1.直接在onDestory()方法中使用removeCallbacksAndMessages移除所有的callback和Message。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
2.使用WeakReference构建一个自定义Handler,在需要销毁Handler时,使用WeakReference的特性,自动解除关联。代码如下:
public class MemoryActivity extends AppCompatActivity {
private MemoryHandler memoryHandler;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
memoryHandler = new MemoryHandler(this);
}
/**
* 使用Handler时
*/
public void whenUseHandler() {
memoryHandler.sendEmptyMessage(0);
memoryHandler.sendMessage(Message.obtain());
}
/**
* 处理Handler事件
*/
public void doSomeThing() {
}
private static class MemoryHandler extends Handler {
private WeakReference<MemoryActivity> activityWeakReference ;
private MemoryHandler(MemoryActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void dispatchMessage(Message msg) {
if(null != activityWeakReference && null != activityWeakReference.get()) {
activityWeakReference.get().doSomeThing();
}
}
}
}
未正确使用Context
对于不是必须使用Activity的Context情况(Dialog必须使用Activity,因为它需要mToken),我们可以考虑使用ApplicationContext代替Activity的Context,这样可以避免Context的泄漏。同样这也是生命周期长短引起的问题。
静态的View
在Activity中使用静态的View可以避免每次启动Activity时都需要解析,渲染View。但是静态的View将会引用所在的Activity,导致Activity将不会被回收。所有如果需要将View静态化,那么需要在OnDestory()方法中,将View的引用及时设置为null。
WebView
不同版本的Android系统,不同厂商定义的Rom,在对WebView的实现上或多或少存在一些差异,但是相同的是,他们都会存在内存泄漏。在应用中只要一次使用WebView,那么它们在内存中将不会被释放掉。为了统一解决这一问题,当前比较常见的做法就是将WebView置于一个独立的进程中,使用AIDL与主进程通信。至于何时销毁,则需要每个公司的业务去制定。
资源对象没有及时的close掉
资源对象如Cusor,File,Closeble对象,一般都是使用了缓冲技术,在使用完成之后,一般都需要及时关闭,以免造成内存泄漏。一般这种做法都有格式化操作,如下:
private void doSomeIo() {
FileInputStream inputStream = null;
FileOutputStream outputStream = null;
try {
inputStream = new FileInputStream(new File(""));
outputStream = new FileOutputStream(new File(""));
//do .....
}catch (IOException e) {
e.printStackTrace();
}finally {
if(null != inputStream) {
try {
inputStream.close();
inputStream = null ;
} catch (IOException e) {
e.printStackTrace();
}
}
if(null != outputStream) {
try {
outputStream.close();
outputStream = null ;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
代码很简单,但是比较繁琐,这种类型一般都是可以写成工具类,规范化操作。
集合中对象中没有及时清理
通常把一些对象的引用加入到了集合中,当不需要更改对象时,如果集合的引用没有被及时清空,集合就会越来越大,如果静态的,那么后果将会更严重。这是新公司的文档上明确规定的:使用完了零时的集合,需要及时清空置null。
Bitmap对象
临时创建的某个相对比较大的Bitmap对象,在经过变换得到新的Bitmap对象后,应该尽快回收原始的Bitmap,这样能尽快的使用原有的Bitmap占用的空间。避免静态变量持有比较大的Bitmap对象或者其他比加大的数据对象,如果已经持有,要尽快置空该静态变量。
监听器未关闭
Android系统级服务(TelephonyManager,SensorManager)需要register和unregister监听器,我们需要确保在合适的时候及时unregister那些监听器。要记住手动register&unregister配套使用。