@Override
protected String doInBackground(Void… params) {
return “Am finally done!”;
}
@Override
protected void onPostExecute(String result) {
mMessageView.setText(result);
}
}
}
大家应该都能看出这里的问题吧。作为非静态内部类的LongRunningTask
,会持有BadActivity
。并且LongRunningTask
是一个长时间任务,也就是说,在这个任务没有完成时,BadActivity
是不会被回收的,因此我们的BadActivity
就被泄漏了。那么怎么改呢?
解决原理
首先我不能让LongRunningTask
持有BadActivity
。那么我们需要使用静态内部类(static class)。这样的确不会持有BadActivity
,但是问题来了,我们LongRunningTask
不持有BadActivity
,也就意味着没办法引用到BadActivity
中的变量,那么我们的更新UI的操作就做不了,也就是说还是要显示的传一个BadActivity
中我们需要的变量进来…但是这样有造成了同样的泄漏问题。
因此,我们需要对传入的变量使用WeakReference
进行包一层。但发生GC的时候,告诉GC收集器“我”可以被回收。
上改造后的代码:
public class GoodActivity extends Activity {
private AsyncTask mLongRunningTask;
private TextView mMessageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_good_activity);
mMessageView = (TextView) findViewById(R.id.messageView);
mLongRunningTask = new LongRunningTask(mMessageView).execute();
}
@Override
protected void onDestroy() {
super.onDestroy();
mLongRunningTask.cancel(true);
}
private static class LongRunningTask extends AsyncTask<Void, Void, String> {
private final WeakReference messageViewReference;
public LongRunningTask(TextView messageView) {
this.messageViewReference = new WeakReference<>(messageView);
}
@Override
protected String doInBackground(Void… params) {
String message = null;
if (!isCancelled()) {
message = “I am finally done!”;
}
return message;
}
@Override
protected void onPostExecute(String result) {
TextView view = messageViewReference.get();
if (view != null) {
view.setText(result
《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享
);
}
}
}
}
2.2、匿名类 Anonymous classes
这一类和2.1很类似。本质都是持有外部对象的引用。
上一段很常见的代码:
public class MoviesActivity extends Activity {
private TextView mNoOfMoviesThisWeek;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_movies_activity);
mNoOfMoviesThisWeek = (TextView) findViewById(R.id.no_of_movies_text_view);
MoviesRepository repository = ((MoviesApp) getApplication()).getRepository();
repository.getMoviesThisWeek()
.enqueue(new Callback<List>() {
@Override
public void onResponse(Call<List> call,
Response<List> response) {
int numberOfMovies = response.body().size();
mNoOfMoviesThisWeek.setText("No of movies this week: " + String.valueOf(numberOfMovies));
}
@Override
public void onFailure(Call<List> call, Throwable t) {
// Oops.
}
});
}
}
2.3、注册Listener
SingleInstance.setMemoryLeakListener(new OnMemoryLeakListener(){
//……
})
这里写了段很常见的伪码,一个单例的对象,register了一个Listener,并且这个Listener被单例的一个成员变量引用。
OK,那么问题很明显了。单例作为静态变量,肯定是一直存在的。而其内部持有了Listener,而Listener作为一个匿名类,有持有了外部对象的引用。因此这条GC链上的所有对象都不会被释放。
解决也很简单,适当的时机,在单例中将Listener的引用置为null。这样,Listener和单例之间的引用关系断了,Listener链上的所有内容就可以被正常释放掉了。也就是咱们常做的在onDestory()
进行unRegisterListener的操作。
类似不注意的内容,还包括Lambda。不过有一点值得注意的,在Kotlin的Lambda中,如果我们没有使用外部对象的变量或者方法,那么Kotlin在编译时,这个Lambda是不会持有外部对象的引用的。也算是Kotlin的一些优化吧
2.4、Contexts
上下文的滥用,也是泄漏的大客户。不过大家针对这类问题应该比较熟悉。
比如:长时间存活的对象,不建议持有Activity的context,而是使用ApplicationContext。如果ApplicationContext没办法完成业务,那么就需要好好考虑一下:这个长时间存活的对象,为什么必须要持有Activity的context。它设计的是否合理,是否它应该是一个长时间存活的对象(比如单例)。
尾声
关于内存泄漏,还是需要咱们平时多注意,对自己写的每一行代码都多思考。毕竟这东西“不是病,但疼起来真要命”。
最后
针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
- Android前沿技术大纲
- 全套体系化高级架构视频
以分享动态给身边好友一起学习!
- Android前沿技术大纲
[外链图片转存中…(img-O8XqbOky-1639744012340)]
- 全套体系化高级架构视频
[外链图片转存中…(img-7HwfLIgH-1639744012352)]