上一篇讲了关于静态内部类的作用和分类,这一篇来讲一下关于非静态内部类的缺点:容易造成内存泄露,这一篇几乎照搬人家的博客啦,想去看原篇,可以直接点击文章最后的超链接啦。
非静态内部类: 成员内部类, 局部内部类、 匿名内部类。 会有对外部类的引用。这样内部类中耗时操作在用户频繁退出重启APP相关Activity时很容易导致内存泄漏。
一、匿名内部类:Runnable
1、泄漏版
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟耗时操作
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
连续多次退出重启后发现:
为什么?
上面代码在
activity
中创建了一个匿名类 Runnable
,匿名类和非静态内部类相同,会持有外部类对象,这里也就是 activity
,因此如果你在 Activity
里声明且实例化一个匿名的 Runnable
对象,则可能会发生内存泄漏,如果这个线程在Activity销毁后还一直在后台执行,那这个线程会继续持有这个 Activity
的引用从而不会被 GC
回收,直到线程执行完成。
2、优化版:
将 非静态内部类 改为 静态非匿名内部类
new Thread(new MyRunnable()).start();
private static class MyRunnable implements Runnable {
@Override
public void run() {
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
二、成员内部类:Handler
1、泄漏版:
private final static int MESSAGECODE = 1;
private 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_main);
new Thread(new Runnable() {
@Override
public void run() {
handler.sendEmptyMessage(MESSAGECODE);
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(MESSAGECODE);
}
}).start();
}
连续多次退出重启后发现:
为什么?
当
Android Application
启动以后,framework
会首先帮助我们完成UI线程的消息循环,也就是在 UI
线程中,Loop
、MessageQueue
、Message
等等这些实例已经由 framework
帮我们实现了。所有的 Application
主要事件,比如 Activity
的生命周期方法、Button
的点击事件都包含在这个 Message
里面,这些 Message
都会加入到 MessageQueue
中去,所以,UI
线程的消息循环贯穿于整个 Application
生命周期,所以当你在UI线程中生成 Handler
的实例,就会持有 Loop
以及 MessageQueue
的引用。并且在 Java
中非静态内部类和匿名内持有外部类的引用,而静态内部类则不会持有外部类的引用。
2、优化版:
使用静态内部类
使用弱引用
在onDestroy() 里面取消异步任务。(注意:单纯的取消还是会内存泄漏)
private final static int MESSAGECODE = 1;
private static Handler handler;//静态
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建Handler
handler = new MyHandler(this);
//创建线程并且启动线程
new Thread(new MyRunnable()).start();
}
//1、避免Handler引用activity造成的内存泄漏:使用静态内部类+ 使用弱引用
private static class MyHandler extends Handler {
WeakReference weakReference;
public MyHandler(HandlerActivity activity) {
weakReference = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (weakReference.get() != null) {
// update android ui
Log.d("mmmmmmmm", "handler " + msg.what);
}
}
}
//2、避免非静态Runnable内部类引用activity造成的内存泄漏
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();
//3、如果参数为null的话,会将所有的Callbacks和Messages全部清除掉。
handler.removeCallbacksAndMessages(null);
}
三、匿名内部类:TimerTask
1、泄漏版:
new Timer().schedule(new TimerTask() {
@Override
public void run() {
while (true) ;
}
}, 1000); // 1秒后启动一个任务
连续多次退出重启后发现:
为什么?
这里内存泄漏在于
Timer
和 TimerTask
没有进行 Cancel
,从而导致 Timer
和 TimerTask
一直引用外部类 Activity
。
2、优化版:
1、在适当的时机进行Cancel。
2、TimerTask用静态内部类
private TimerTask timerTask ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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() ;
}
}
四、匿名内部类:AsyncTask
1、泄露版:
new AsyncTask(){
@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 ) ;
}
}.execute();
连续多次退出重启后发现:
为什么?
上面代码在
activity
中创建了一个匿名类 AsyncTask
,匿名类和非静态内部类相同,会持有外部类对象,这里也就是 activity
,因此如果你在 Activity
里声明且实例化一个匿名的 AsyncTask
对象,则可能会发生内存泄漏,如果这个线程在 Activity
销毁后还一直在后台执行,那这个线程会继续持有这个 Activity
的引用从而不会被 GC
回收,直到线程执行完成。
2、优化版
1、自定义静态AsyncTask类
2、AsyncTask的周期和Activity周期应该保持一致。也就是在Activity生命周期结束时要将AsyncTask cancel掉。
private static MyTask myTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myTask = new MyTask();
myTask.execute();
}
//1、创建静态内部类
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();
//2、取消异步任务
if (myTask != null) {
myTask.cancel(true);
}
}
最后
你们要的原篇地址,参考链接:
android-内部类导致的内存泄漏实战解析