译-Finally understanding how references work in Android and Java(彻底理解引用在 Android 和 Java中的工作原理)

原文链接:https://medium.com/google-developer-experts/finally-understanding-how-references-work-in-android-and-java-26a0d9c92f83#.3jla7ug1r

几周前我很荣幸地去波兰参加了Mobiconf,对于移动开发者来说是最好的会议之一。我的朋友兼同事 Jorge Barroso做了名为“最佳实践”的演讲,其中有这么一句话让我听后很有感触:

如果你是一个 Android 开发者但你不使用 WeakReferences ,那这是有问题的。

举个恰当的例子,几个月前我发布了我最后一本书,“Android High Performance”,Diego Grancini作为联席作者。其中最受欢迎的章节是讨论 Android 中的内存管理。在这个章节中,我们讲了在移动设备上内存是如何工作的,以及内存泄露是怎么发生的,为什么这很重要,和使用什么技术来让我们避免内存泄露的发生。自从我开发 Android ,我总是看到轻视甚至不管内存泄露和管理的这种倾向。如果需求已经满足了,为什么要自寻烦恼呢?我们总是急于开发新的功能,我们更倾向于在下一个 Sprint 演示中加入可见的东西而不关注那些人们不能第一眼就看见的东西。

这无疑是导致技术债务的一个例子。我觉得技术债务会在现实中产生一些影响,这是我们不能用单元测试衡量的:失望,开发者间的摩擦,低质量的软件和消极的情绪。这难以衡量的原因是因为他们常常会发生在将来某个时间点。这就有点像政客:如果我只执政8年,为什么我要去操心12年后会发生什么呢?除了在软件开发中,一切都发展得十分迅速。

编写在软件开发中要采取的合适的设计思想可能需要很大的篇幅,现在已经有很多书和文章你可以参考。然而简要地解释不同类型的内存引用,它们是什么,以及它们如何在 Android 中应用,这是个简短的任务,这就是我想要在这篇文章中说的。

首先:Java 中的引用是什么?

引用直接指向了一个对象,所以你可以通过引用访问对象。

Java 默认有四种的引用类型:强引用软引用弱引用虚引用
有些人说只有两种引用类型:强引用和弱引用,并且弱引用有两种程度的弱化。我们像植物学家一样将一切事物分类。不论你觉得哪种分类更好,首先你需要理解他们。然后你可以找出你自己的分类。

那各种引用都是着什么呢?

Strong reference : 强引用是 Java 中常见的引用类型。每当我们创建一个对象的时候,强引用也同时被创建了。举个例子,当我们这么写:

1
MyObject object = new MyObject();

一个新的对象 MyObject 被创建了,然后对这个对象的强引用被存放在 object 里。就这么简单,你还在看吗?好的,现在更多有意思的事情来了。这个 object 是可以强行到达的,这说明,这个可以通过一系列强引用找到。这会阻止垃圾回收器回收它,这通常是我们想要的。但现在,让我们来看个不会像我们想的那样运行的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new MyAsyncTask().execute();
}
private class MyAsyncTask extends AsyncTask {
@Override
protected Object doInBackground(Object[] params) {
return doSomeStuff();
}
private Object doSomeStuff() {
//do something to get result
return new MyObject();
}
}
}

思考几分钟尝试找出可能出现的问题。

不要担心,如果你找不到的话再思考几分钟。

现在呢?

AsyncTask 会在Activity 的 onCreate() 方法中被创建同时执行。但是这里就有问题了:内部类在它整个生命周期内会访问外部类。

当 Activity 被销毁的时候会发生什么呢? AsyncTask 会持有对 Activity 的引用,所以 Activity 不会被 GC 回收。这就是我们所说的内存泄露。

旁注:曾经我在面试合适的应聘者的时候,我问他们内存泄露是怎么发生的而不是问他们内存泄露是什么这种理论方面的东西。这总是更加有趣!

内存泄露实际上不仅仅发生在 Activity 被销毁的时候,也发生在 Activity 因为配置的更改或者系统需要更多内存的时候被强制销毁等情况。如果 AsyncTask 比较是复杂的(比如它持有 Activity 中 Views 的引用等)这可能会引起崩溃,因为 views 的引用是空指针。

所以如何防止这种问题的发生呢?我们接下来介绍其他几种类型的引用:

WeakReference : 弱引用是一种引用强度不足以将对象保存在内存中的引用。如果我们想要确定对象的引用强度,它恰好是弱引用的话,那这个对象就会被垃圾回收。为了便于理解,我们就抛开理论,展示一个实际的例子如何让我们在之前的代码中使用弱引用来避免内存泄露:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MyAsyncTask(this).execute();
}
private static class MyAsyncTask extends AsyncTask {
private WeakReference<MainActivity> mainActivity;
public MyAsyncTask(MainActivity mainActivity) {
this.mainActivity = new WeakReference<>(mainActivity);
}
@Override
protected Object doInBackground(Object[] params) {
return doSomeStuff();
}
private Object doSomeStuff() {
//do something to get result
return new Object();
}
@Override
protected void onPostExecute(Object object) {
super.onPostExecute(object);
if (mainActivity.get() != null){
//adapt contents
}
}
}
}

注意现在主要的区别:内部类引用 Activity 是通过下面这种方式了:

1
private WeakReference<MainActivity> mainActivity;

现在会发生什么呢?如果 Activity 停止退出了,因为它持有一个弱引用,所以可以被回收。因此内存泄露就不会再发生了。

旁注:如果现在你比之前对 WeakReferences 的有了更好的理解,你会发现 WeakHashMap 是很有用的。它就像 HashMap 一样,除了使用 WeakReferences 引用键(键,不是值)。这让它们在实现一些诸如缓存的实体时很有用。

我们提到过一组引用。让我们来看看他们用在什么地方,和他们给我们带来的益处:

SoftReference: 可以把软引用作为较强的弱引用。当弱引用被立即回收时,软引用会请求垃圾回收器让它暂存于内存中,除非没有其他的选项。垃圾回收算法是如此的有意思以致于你会沉醉在其中几个小时而不感到疲惫,不过大体上,垃圾回收器会说:“我总是会去回收弱引用,如果对象是一个软引用,我会根据具体条件来选择是否回收”。这就让软引用对实现一个缓存非常有用:只要内存还有剩余,我们不必担心手动地去回收对象。如果你想看一个实例,你可以查看这个用软引用实现的缓存的例子。

PhantomReference:呀,虚引用!我觉得在实际的产品开发中我只看到过它们5次。一个虚引用持有的对象可以在任何垃圾回收器想回收它们的时候就被回收。没有进一步的解释,没有“回调”。这让它很难以描述。为什么我们会使用这样一个东西?其他的问题还不够吗?为什么我们选择成为程序员?虚引用可以用于探测对象被回收的时机。说实话,我在工作中只使用过两次虚引用。所以如果现在比较难理解他们的话也不要有压力。

希望这篇文章能够消除点之前你对这些引用的疑问。作为学习的过程,你可能现在想做些练习和玩你的代码看怎么来改进它。第一步就是看你是否存在内存泄露,然后想你是否可以用这篇文章中学到的内容去避免讨厌的内存泄露。如果你喜欢这篇文章或者觉得它帮助了你,请随意分享或留下评论吧。这将会鼓励我这个业余的写手。

感谢我的同事Sebastian对这篇文章的投入。