java 匿名内部类内存泄露_Android 常见内存泄露 & 解决方案

前言

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃 (OOM) 等严重后果。

那什么情况下不能被回收呢?

目前 java 垃圾回收主流算法是虚拟机采用 GC Roots Tracing 算法。算法的基本思路是:通过一系列的名为 GC Roots (GC 根节点)的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径,当一个对象到GC Roots没有任何引用链相连(图论说:从GC Roots 到这个对象不可达)时, 证明此对象是不可用的。

关于可达性的对象,便是能与 GC Roots 构成连通图的对象,如下图:

7b394ae01355b2ee13440678e7df6d62.png

根搜索算法的基本思路就是通过一系列名为 "GC Roots" 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链 ( Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。

从上图,reference1、reference2、reference3 都是 GC Roots,可以看出:

reference1-> 对象实例1;

reference2-> 对象实例2;

reference3-> 对象实例4;

reference3-> 对象实例4 -> 对象实例6;

可以得出对象实例1、2、4、6都具有 GC Roots 可达性,也就是存活对象,不能被 GC 回收的对象。

而对于对象实例3、5直接虽然连通,但并没有任何一个 GC Roots 与之相连,这便是 GC Roots 不可达的对象,这就是 GC 需要回收的垃圾对象。

在了解 GC 之后,开始去了解 Android 的内存泄露情况了。

Android 内存泄露场景

下面会详细介绍一些常见的内存泄露场景,以及对应的修复办法。

非静态内部类的静态实例

比如我们在 Activity 内部定义了一个内部类 InnerClass,同时定义了一个静态变量 inner,并给予赋值。假设你在 onDestory 的时候没有将 inner 置 null;那么就会引起内存泄露。原因是静态变量持有了内部类的实例,内部类会对外部类有个引用,从而导致 Activity 得不到释放。

private staticObject inner;voidcreateInnerClass() {classInnerClass {

}

inner= newInnerClass();

}

View icButton=findViewById(R.id.ic_button);

icButton.setOnClickListener(newView.OnClickListener() {

@Overridepublic voidonClick(View v) {

createInnerClass();

nextActivity();

}

});

记得在生命周期结束的时候,将不需要的静态变量置 null。

多线程相关的匿名内部类/非静态内部类

和非静态内部类一样,匿名内部类也会持有外部类实例的引用。多线程相关的类有 AsyncTask 类,Thread 类和 Runnable 接口的类等,它们的匿名内部类如果做耗时操作

就可能发生内存泄露,这里以 AsyncTask 的匿名内部类举例,如下所示:

voidstartAsyncTask() {new AsyncTask() {

@OverrideprotectedVoid doInBackground(Void... params) {while(true);

}

}.execute();

}super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

View aicButton=findViewById(R.id.at_button);

aicButton.setOnClickListener(newView.OnClickListener() {

@Overridepublic voidonClick(View v) {

startAsyncTask();

nextActivity();

}

});

当异步任务在后台执行耗时任务期间,Activity 不幸被销毁了(比如:用户退出,系统回收),这个被 AsyncTask 持有的 Activity 实例就不会被垃圾回收器回收,直到异步任务结束。

解决方法是继承 AsyncTask 新建一个静态内部类,用静态内部类创建实例就不会存在对外部实例的引用了。

Handler 内存泄露

同样道理,Handler 的 message 被传递到消息队列 MessageQueue中,在 Message消息没有被处理之前,handler 的实例也不无法被回收,如果 handler 实例不是静态的,就会导致引用它的 activity 或者 service 不能被回收,于是就会发生内存泄漏。

voidcreateHandler() {newHandler() {

@Overridepublic voidhandleMessage(Message message) {super.handleMessage(message);

}

}.sendMessageDelayed(Message.obtain(),60000);

}

View hButton=findViewById(R.id.h_button);

hButton.setOnClickListener(newView.OnClickListener() {

@Overridepublic voidonClick(View v) {

createHandler();

nextActivity();

}

});

对于上述问题,有两种解决办法,一种是使用一个静态的 handler 内部类,并且其持有的对象都改成弱引用形式进行引用。还有一种是在销毁 activity 的时候,将发送的消息进行移除。

myHandler.removeCallbackAndMessages(null);

这种有个问题就是 Handler 中的消息可能无法全部被处理完。

另外还有一个要注意的是,最好不要直接使用 View#post 来做一些操作。如果要用,确保要用的话,确保 view 已经被 attach 到了 window。

静态 Activity 或 View

在类中定义了静态

Activity变量,把当前运行的

Activity实例赋值于这个静态变量。

如果这个静态变量在

Activity生命周期结束后没有清空,就导致内存泄漏。因为 static 变量是贯穿这个应用的生命周期的,所以被泄漏的

Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。

staticActivity activity;voidsetStaticActivity() {

activity= this;

}

View saButton=findViewById(R.id.sa_button);

saButton.setOnClickListener(newView.OnClickListener() {

@Overridepublic voidonClick(View v) {

setStaticActivity();

nextActivity();

}

});

为了能够被回收,需要在不需要使用的时候进行置 null 操作。比如销毁当前 activity 的时候。

特殊情况:如果一个 View 初始化耗费大量资源,而且在一个 Activity生命周期内保持不变,那可以把它变成 static,加载到视图树上 (View Hierachy),像这样,当 Activity被销毁时,应当释放资源。

staticview;voidsetStaticView() {

view=findViewById(R.id.sv_button);

}

View svButton=findViewById(R.id.sv_button);

svButton.setOnClickListener(newView.OnClickListener() {

@Overridepublic voidonClick(View v) {

setStaticView();

nextActivity();

}

});

同样的,为了解决内存泄露的问题,在 Activity 销毁的时候把这个 static view 置 null 即可,但是还是不建议用这个 static view的方法。

Eventbus 等注册监听造成的内存泄露

相信很多同学都在项目里面会用到 Eventbus。对于一些没有经验的同学在使用的时候经常会出现一些问题。比如说在 onCreate 的时候进行注册,却忘了反注册,或者说,在onStop的时候进行反注册,这些都会导致 Eventbus 的内存泄露。

@Overridepublic voidonCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);

EventBus.getDefault().register(this);//注意在onCreate()方法中注册

}

@Overridepublic voidonDestroy() {

EventBus.getDefault().unregister(this);//注意在onDestory()方法中注册

super.onDestroy();

}

注册和反注册(取消注册)是对应的,必须要添加,否则会引起组件的内存泄漏。因为注册的时候组件是被 EventBus 内部的单例队列所持有引用的。

如果你是在 View 里面注册 Eventbus 的,记得是在 View 的生命周期 onAttachedToWindow 和 onDetachedFromWindow 的时候进行注册和反注册。

最近跟我的同事进行聊天的时候发现,他们为了解决 eventbus 导致的内存泄露问题(已经成对注册和反注册还是存在内存泄露问题),于是打算创建一个 object 的实例,用这个来进行注册与反注册,这样即使发生内存泄露也只会占用很小的内存空间。

单例引起的内存泄露

项目中,经常会存在很多单例。有时候需要我们将当前 Activity 实例传给单例,然后去做一些事情。如下面的代码:

public classSingleInstance {privateContext mContext;private staticSingleInstance instance;privateSingleInstance(Context context) {this.mContext =context;

}public staticSingleInstance getInstance(Context context) {if (instance == null) {

instance= newSingleInstance(context);

}returninstance;

}

}

上述单例中传入一个 context ,就会导致 context 的生命时长和应用的生命时长一样。就会造成内存泄露。

对于这种有三种解决办法:

1、采用弱引用的方式进行引用,确保能够被回收;

2、在对应的 context 要被销毁的时候,进行置 null;确保不会长于原本的生命时长;

3、看是否能够使用 APP context;这样就不会存在内存泄露的问题了。

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

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

解决办法:及时关闭资源

WebView

不同的Android 版本的 webView 会有差异,加上不同的厂商定制的 ROM 的 webView 差异,这就导致 webView 存在很大的兼容性问题。weView 都会存在内存泄露问题,在应用中只要使用一次,内存就不会被释放。通常的做法是为 webView 单独开一个进程,使用 AIDL 与应用的主进程进程通信。webView 进程可以根据业务的需求,在合适的时机进行销毁。

参考文献:

1、《Android进阶解密》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值