一个优秀程序员不可避免的问题:内存泄漏
前言
内存泄漏,一个说大不大说下不小的瑕疵。作为开发者,我们都很清楚内存泄漏是我们代码问题导致的。但是话说回来,泄漏后果会很严重嘛?这不好说,假如我们不泄漏Bitmap这种大内存的对象,那么修补内存泄漏就像鸡肋一样,“食之无味,弃之可惜”。 就比方说我们项目组,近2000w的DAU,只需不显著影响客户体验,一切以上需求为主…
但是这作为一个996福报码农,不能只挖坑,不填坑,毕竟技术债都是要还的。所以今天咱们来聊一聊Android中的内存泄漏。这篇文章总结翻译了外国友人的一篇文章:原文如下
techbeacon.com/app-dev-tes…
一、理论
先上一张图:

解释一下这张图,每个Android(或者Java)应用程序都有一个起点(GC Root),从这个点中实例化对象、调用方法。。少量对象直接引用GC Root,另少量对象又引用了这些对象。因而,形成了引用链,就像上图一样。因而垃圾收集器从GC Root开始并遍历直接或者间接链接到GC Root的对象。在此过程结束时,脱离GC Root的对象/对象链将被回收。
接下来咱们再想另一个问题:
什么是内存泄漏?
有了上图,了解内存泄漏的概念就很简单,说白了就是:长生命周期对象A持有了短生命周期的对象B,那么只需A不脱离GC Root的链,那么B对象永远没有可能被回收,因而B就泄漏了。
有什么危害?
危害的话,如开篇所说。假如泄漏的内存很小,几字节,几kb….对于现在的机器性能,就像星爵打灭霸…“伤害”基本无视。但是假如泄漏的足够多,普通的GC无法回收这些泄漏的内存,那么堆将持续添加,当堆足够大的时候,就会触发“stop-the-world” GC,直接在主线程进行耗时的GC。
主线程进行耗时操作,每一个android开发者都明白这意味着什么….
所以内存泄漏足够严重,其危害还是很严重的。
二、实践
对于我们日常开发来说,有比较多的场景稍不注意就会存在内存泄漏的风险。让我们一起留意一下:
2.1、内部类Inner classes
内部类存在内存泄漏的风险,是一个老生常谈的话题。说白了就是由于我们在new一个内部类时,编译器会在编译时让这个内部类的实例持有外部对象。
这也就是,为啥我们的内部类可以引用到外部类变量、方法的起因。
上段代码:
public class BadActivity extends Activity { private TextView mMessageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_bad_activity); mMessageView = (TextView) findViewById(R.id.messageView); new LongRunningTask().execute(); } private class LongRunningTask extends AsyncTask<Void, Void, String> { @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<TextView> 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); } } }}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<Movie>>() { @Override public void onResponse(Call<List<Movie>> call, Response<List<Movie>> response) { int numberOfMovies = response.body().size(); mNoOfMoviesThisWeek.setText("No of movies this week: " + String.valueOf(numberOfMovies)); } @Override public void onFailure(Call<List<Movie>> 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 IOC架构设计
加群 Android IOC架构设计领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起探讨交流处理问题。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 一个优秀程序员不可避免的问题:内存泄漏