当它真的泄漏安全使用(匿名)内部类?

问题:

我一直在阅读关于Android内存泄漏的一些文章,并从Google I / O on the subject观看了这个有趣的视频
不过,我不完全理解这个概念,特别是对于用户活动内部的内部类是安全的或危险的,
这是我的理解:
如果内部类的实例比其外部类(Activity)长得多,则会发生内存泄漏。
– > 在哪种情况下会发生这种情况?
在这个例子中,我认为没有漏洞的风险,因为延续OnClickListener的匿名类没有办法比活动活得更长,对吧?

    final Dialog dialog = new Dialog(this);
    dialog.setContentView(R.layout.dialog_generic);
    Button okButton = (Button) dialog.findViewById(R.id.dialog_button_ok);
    TextView titleTv = (TextView) dialog.findViewById(R.id.dialog_generic_title);

    // *** Handle button click
    okButton.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            dialog.dismiss();
        }
    });

    titleTv.setText("dialog title");
    dialog.show();

现在这个例子是危险的,为什么?

// We are still inside an Activity
_handlerToDelayDroidMove = new Handler();
_handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 10000);

private Runnable _droidPlayRunnable = new Runnable() { 
    public void run() {
        _someFieldOfTheActivity.performLongCalculation();
    }
};

我有一个疑问,即理解这个话题与理解一个活动被破坏和重新创建时所保留的内容有关。
是吗?
假设我刚刚改变了设备的方向(这是最常见的泄漏原因)。当super.onCreate(savedInstanceState)将在onCreate()中被调用时,是否会恢复字段的值(就像定向更改之前一样)?这是否也会恢复内在阶级的状态?
我意识到我的问题不是很精确,但我真的很感激任何可以使事情更清楚的解释。

回答:

塞巴斯蒂安
你问的是一个非常棘手的问题。虽然你可能认为这只是一个问题,但你实际上是一次问几个问题。我将尽全力了解我必须覆盖的知识,希望其他一些人能加入,以弥补我可能会想念的内容。
 内部类:介绍
由于我不确定在Java中使用OOP有多么舒适,这将会触及几个基础。内部类是当类定义包含在另一个类中时。基本上有两种类型:静态和非静态。这些之间的真正区别是:

  • 静态内部类:
     

    • 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.
  • 非静态内部类:
     

    • 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.

 垃圾收集和非静态内部类
垃圾收集是自动的,但是会尝试根据它们是否被使用来删除对象。垃圾收集器很聪明,但不完美。它只能确定是否使用某个东西是否存在对该对象的活动引用。
这里的真正问题是当非静态内部类别比其容器保持活动时间更长。这是因为对包含类的隐含引用。这可能发生的唯一方法是如果包含类之外的对象保持对内部对象的引用,而不考虑包含对象。
这可能导致内部对象存活(通过引用)但是对包含对象的引用已经从所有其他对象中删除。因此,内部对象是保持包含对象存活,因为它将always引用它。这个问题是,除非它被编程,否则没有办法回到包含的对象来检查它是否还活着。
这个实现的最重要的方面是,它是不是在一个活动或是一个可绘制。使用非静态内部类时,您将必须是有条理的,并确保它们永远不会超过容器的对象。幸运的是,如果它不是你的代码的核心对象,那么泄漏可能比较小。不幸的是,这些是一些最难找到的漏洞,因为它们很可能被忽视,直到许多泄漏。
 解决方案:非静态内部类

  • 从包含对象获取临时引用。
  • 允许包含对象作为唯一一个保持对内部对象的长期引用的对象。
  • 使用已建立的模式,如工厂。
  • 如果内部类不需要访问包含的类成员,请考虑将其转换为静态类。
  • 谨慎使用,无论是否在活动中。

 活动与观点:介绍
活动包含大量信息以便能够运行和显示。活动由他们必须具有视图的特征来定义。他们也有一些自动处理程序。无论您是否指定,“活动”都将对其包含的“视图”进行隐式引用。
为了创建视图,它必须知道在哪里创建它,以及它是否有任何孩子,以便它可以显示。这意味着每个视图都有对Activity的引用(通过getContext())。此外,每个视图都保留对其子节点的引用(即getChildAt())。最后,每个视图保留对表示其显示的渲染Bitmap的引用。
每当你有一个活动(或活动上下文)的引用,这意味着你可以沿着布局层次结构遵循ENTIRE链。这就是为什么关于活动或意见的内存泄漏是如此巨大的事情。它可能是一次的内存泄漏。
 活动,意见和非静态内部类
给出上面关于内部类的信息,这些是最常见的内存泄漏,也是最常见的避免。虽然内部类可以直接访问Activities类成员,但是很多人愿意将它们静态化以避免潜在的问题。活动和意见的问题比这更深刻。
 泄露的活动,观点和活动背景
这一切都归结于上下文和生命周期。有某些事件(如方向)会杀死一个Activity Context。由于这么多类和方法需要一个Context,所以开发人员有时会通过抓取对Context的引用来保存一些代码。只要发生这样的事情,我们必须创建的许多对象才能运行我们的Activity,它们必须存在于Activity LifeCycle之外,才能使Activity执行所需要做的。如果您的任何对象恰好有一个对Activity,其上下文或其任何视图被破坏的引用,那么您刚刚泄漏了该Activity及其整个View树。
 解决方案:活动和观点

  • 不惜一切代价,对View或Activity进行静态引用。
  • 所有对活动上下文的引用应该是短暂的(功能的持续时间)
  • 如果您需要长期使用的Context,请使用Application Context(getBaseContext()getApplicationContext())。这些不会隐含地引用引用。
  • 或者,您可以通过覆盖配置更改来限制活动的销毁。但是,这不会阻止其他潜在的事件破坏活动。当您can执行此操作时,您可能仍然希望参考上述做法。

 Runnables:介绍
Runnables实际上并不是那么糟糕。我的意思是他们could是,但是我们已经打了绝大多数危险区域。 Runnable是一个异步操作,它从创建的线程执行独立的任务。大多数runnable都是从UI线程中实例化的。实质上,使用Runnable创建另一个线程,只需稍微管理一次。如果您像一个标准类一样运行Runnable,并遵循上述指导原则,您应该遇到几个问题。现实是,许多开发者不这样做。
许多开发人员使用匿名内部类来简化,可读性和逻辑程序流程,以定义其Runnables,例如上面创建的示例。这将产生一个像上面输入的例子。匿名内部类基本上是一个离散的非静态内部类。你只需要创建一个全新的定义,只需覆盖适当的方法即可。在所有其他方面,它是一个非静态内部类,这意味着它保留对其容器的隐式引用。
 运行和活动/视图
好极了!本节可以简短!由于Runnables运行在当前线程之外,因此这些危险来自长时间运行的异步操作。如果在“活动”或“视图”中将runnable定义为匿名内部类或非静态内部类,则会有一些非常严重的危险。这是因为,如前所述,它知道其容器是谁。输入方向更改(或系统终止)。现在回到前面的部分来了解刚刚发生的事情。是的,你的例子是相当危险的。
 解决方案:Runnables

  • 尝试并扩展Runnable,如果它不会破坏你的代码的逻辑。
  • 尽可能使扩展Runnables静态,如果它们必须是内部类。
  • 如果您必须使用匿名运行符,请避免在对象中创建它们,该对象具有对正在使用的Activity或View的长期引用。
  • 许多Runnables可以很容易地被AsyncTasks。考虑使用AsyncTask,因为默认情况下是VM Managed。

 回答最后的问题
现在回答这篇文章的其他部分不是directly解决的问题。你问“内部阶级的对象什么时候可以比外部类生活得更久?”在我们达成之前,让我重新强调一下:尽管你在活动中担心这一点,但它可能会在任何地方造成泄漏。我将提供一个简单的例子(不使用Activity)来演示。
以下是一个基本工厂的常见示例(缺少代码)。

public class LeakFactory
{//Just so that we have some data to leak
    int myID = 0;
// Necessary because our Leak class is non-static
    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... } } [/code] 现在我们有泄漏,但没有工厂。即使我们发布了工厂,它将保留在内存中,因为每一个Leak都有参考。外部类没有数据也不重要。这发生得比人们想象的更频繁。我们不需要创作者,只是它的创作。所以我们暂时创建一个,但是无限期地使用创作。 想象一下,当我们稍稍改变构造函数时会发生什么。 [code lang="python"] 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(); } } } [/code] 现在,这些新的LeakFactories中的每一个都刚刚泄露出来。你觉得怎么样?这些是内部类可以超越任何类型的外部类的两个非常常见的示例。如果那个外部阶层是一个活动,想象一下这将会更糟糕。  结论
这些列出了不适当地使用这些对象的主要已知危险。一般来说,这篇文章应该涵盖了大部分的问题,但是我明白这是一个很糟糕的帖子,所以如果你需要澄清,只是让我知道。只要你遵循上述做法,你就不用担心泄漏了。
希望这可以帮助,
FuzzicalLogic

 
 
Code问答: http://codewenda.com/topics/python/
Stackoverflow: When exactly is it leak safe to use (anonymous) inner classes?

*转载请注明本文链接以及stackoverflow的英文链接

发表评论

电子邮件地址不会被公开。 必填项已用*标注

8 + 1 =