你问的是一个相当棘手的问题。虽然你可能认为这只是一个问题,但实际上你同时问了几个问题。我会尽我最大的努力去覆盖它,希望其他一些人能加入进来,覆盖我可能错过的东西。
嵌套类:简介
因为我不确定你对Java中的OOP有多熟悉,这涉及到一些基础知识。嵌套类是指类定义包含在另一个类中。基本上有两种类型:静态嵌套类和内部类。它们之间的真正区别是:
Static Nested Classes:
Are considered "top-level".
Do not require an instance of the containing class to be constructed.
May not reference the containing class members without an explicit reference.
Have their own lifetime.
Inner Nested Classes:
Always require an instance of the containing class to be constructed.
Automatically have an implicit reference to the containing instance.
May access the container's class members without the reference.
Lifetime is supposed to be no longer than that of the container.
垃圾收集和内部类
垃圾收集是自动的,但它会根据是否认为对象正在被使用来删除对象。垃圾收集器非常聪明,但并非完美无缺。它只能通过是否存在对对象的活动引用来确定是否正在使用某个对象。
这里真正的问题是当一个内部类的生存时间比它的容器还要长。这是因为对包含类的隐式引用。发生这种情况的唯一方法是,如果包含类之外的对象保留了对内部对象的引用,而不考虑包含对象。
这可能会导致内部对象是活的(通过引用),但对包含对象的引用已经从所有其他对象中删除了。因此,内部对象将使包含它的对象保持活动状态,因为它将始终有一个对它的引用。这样做的问题在于,除非对其进行了编程,否则无法返回包含它的对象来检查它是否活的。
这种实现最重要的方面是,它是在活动中还是在可绘制对象中都没有区别。在使用内部类时,您必须始终保持有条不紊,并确保它们永远不会比容器的对象更长寿。幸运的是,如果它不是代码的核心对象,泄漏可能相对较小。不幸的是,这些都是最难发现的漏洞,因为它们很可能会被忽视,直到其中许多漏洞泄露。
解决方案:内部类
从包含对象获取临时引用。
允许包含对象是唯一保持对内部对象的长期引用的对象。
使用已建立的模式,如工厂。
如果内部类不需要访问包含的类成员,请考虑将其转换为静态类。
请谨慎使用,无论它是否在活动中。
活动和观点:介绍
活动包含大量能够运行和显示的信息。活动是由它们必须具有视图这一特征定义的。它们也有某些自动处理程序。无论您是否指定,活动都有一个对它所包含的视图的隐式引用。
为了创建一个视图,它必须知道在哪里创建它,以及它是否有任何子视图,以便它可以显示。这意味着每个View都有一个对Activity的引用(通过getContext())。此外,每个View都保留对其子视图的引用(即getChildAt())。最后,每个视图都保留一个对表示其显示的已呈现位图的引用。
每当你有一个活动(或活动上下文)的引用,这意味着你可以沿着布局层次结构的整个链。这就是为什么有关活动或视图的内存泄漏是如此大的问题。它可能是大量的内存同时被泄露。
活动,视图和内部类
根据上面关于内部类的信息,这些是最常见的内存泄漏,但也是最常避免的。虽然让内部类直接访问Activities类成员是可取的,但许多人愿意将它们设置为静态以避免潜在的问题。活动和视图的问题远不止于此。
泄露的活动、视图和活动上下文
It all comes down to the Context and the LifeCycle. There are certain events (such as orientation) that will kill an Activity Context. Since so many classes and methods require a Context, developers will sometimes try to save some code by grabbing a reference to a Context and holding onto it. It just so happens that many of the objects we have to create to run our Activity have to exist outside of the Activity LifeCycle in order to allow the Activity to do what it needs to do. If any of your objects happen to have a reference to an Activity, its Context, or any of its Views when it is destroyed, you have just leaked that Activity and its entire View tree.
解决方案:活动和视图
Avoid, at all costs, making a Static reference to a View or Activity.
All references to Activity Contexts should be short lived (the duration of the function)
If you need a long-lived Context, use the Application Context (getBaseContext() or getApplicationContext()). These do not keep references implicitly.
Alternatively, you may limit the destruction of an Activity by overriding Configuration Changes. However, this does not stop other potential events from destroying the Activity. While you can do this, you may still want to refer to the above practices.
可运行:介绍
Runnables are actually not that bad. I mean, they could be, but really we've already hit most of the danger zones. A Runnable is an asynchronous operation that performs a task independant from the thread it was created on. Most runnables are instantiated from the UI thread. In essence, using a Runnable is creating another thread, just slightly more managed. If you class a Runnable like a standard class and follow the guidelines above, you should run into few problem. The reality is that many developers do not do this.
出于易用性、可读性和逻辑程序流的考虑,许多开发人员使用匿名内部类来定义他们的可运行对象,例如上面创建的示例。这将导致一个类似于上面键入的示例。匿名内部类基本上是一个离散的内部类。您不需要创建一个全新的定义,只需重写适当的方法即可。在所有其他方面,它都是一个内部类,这意味着它保持了对其容器的隐式引用。
可运行对象和活动/视图
Yay! This section can be short! Due to the fact that Runnables run outside of the current thread, the danger with these comes to long running asynchronous operations. If the runnable is defined in an Activity or View as an Anonymous Inner Class OR nested Inner Class, there are some very serious dangers. This is because, as previously stated, it has to know who its container is. Enter the orientation change (or system kill). Now just refer back to the previous sections to understand what just happened. Yes, your example is quite dangerous.
解决方案:Runnables
尝试扩展Runnable,如果它不会破坏代码的逻辑的话。
如果扩展的Runnables必须是嵌套类,请尽量使它们成为静态的。
如果必须使用匿名可运行对象,请避免在任何对正在使用的活动或视图有长期引用的对象中创建它们。
许多Runnables可以很容易地成为AsyncTasks。考虑使用AsyncTask,因为它们默认是VM Managed的。
回答最后一个问题
现在来回答这篇文章其他部分没有直接提到的问题。你问“内部类的对象什么时候能比外部类存活得更久?”在我们讨论这个问题之前,让我再次强调:尽管您在Activities中对此感到担忧是正确的,但它可能在任何地方导致泄漏。我将提供一个简单的示例(不使用Activity)来演示。
下面是一个基本工厂的常见示例(缺少代码)。
public class LeakFactory
{//Just so that we have some data to leak
int myID = 0;
// Necessary because our Leak class is an Inner class
public Leak createLeak()
{
return new Leak();
}
// Mass Manufactured Leak class
public class Leak
{//Again for a little data.
int size = 1;
}
}
这是一个不常见的例子,但很容易演示。这里的关键是构造函数…
public class SwissCheese
{//Can't have swiss cheese without some holes
public Leak[] myHoles;
public SwissCheese()
{//Gotta have a Factory to make my holes
LeakFactory _holeDriller = new LeakFactory()
// Now, let's get the holes and store them.
myHoles = new Leak[1000];
for (int i = 0; i++; i<1000)
{//Store them in the class member
myHoles[i] = _holeDriller.createLeak();
}
// Yay! We're done!
// Buh-bye LeakFactory. I don't need you anymore...
}
}
现在我们有泄密,但没有工厂。即使我们发布了工厂,它仍将保留在内存中,因为每个泄漏都有对它的引用。外层类没有数据也没关系。这种情况发生得比人们想象的要频繁得多。我们不需要创造者,只需要它的创造物。因此,我们临时创建一个,但无限期地使用创建的对象。
想象一下稍微改变构造函数会发生什么。
public class SwissCheese
{//Can't have swiss cheese without some holes
public Leak[] myHoles;
public SwissCheese()
{//Now, let's get the holes and store them.
myHoles = new Leak[1000];
for (int i = 0; i++; i<1000)
{//WOW! I don't even have to create a Factory...
// This is SOOOO much prettier....
myHoles[i] = new LeakFactory().createLeak();
}
}
}
现在,每一个新的LeakFactories都被泄露了。你觉得怎么样?这是内部类如何比任何类型的外部类更长寿的两个非常常见的例子。如果这个外部类是一个Activity,想象一下会有多糟糕。
结论
这些列出了不恰当使用这些对象的主要已知危险。总的来说,这篇文章应该涵盖了你的大部分问题,但我知道这是一篇很长的文章,所以如果你需要澄清,请告诉我。只要你遵循以上的做法,你就会有很少的担心泄漏。