我一直在阅读一些关于Android中内存泄漏的文章,并观看了来自Google I/Oon the subject的这段有趣的视频.

不过,我并不完全理解这个概念,尤其是当它对用户inner classes inside an Activity安全或危险时.

这就是我的理解:

A memory leak will occur if an instance of an inner class survives longer than its outer class (an Activity). -> In which situations can this happen?

在本例中,我认为没有泄漏的风险,因为扩展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();
    }
};

我怀疑,理解这个主题与理解活动被破坏和重新创建时所保留的细节有关.

它是?

假设我刚刚更改了设备的方向(这是最常见的泄漏原因).当在我的onCreate()中调用super.onCreate(savedInstanceState)时,这是否会恢复字段的值(方向更改前的值)?这也会恢复内部类的状态吗?

我知道我的问题不是很精确,但我真的很感激任何能让事情更清楚的解释.

推荐答案

你要问的是一个很难回答的问题.虽然你可能认为这只是一个问题,但实际上你同时提出了几个问题.我会尽我所能去报道它,希望其他人也能参与报道我可能错过的内容.

Nested Classes: Introduction

由于我不确定您对Java中的OOP有多满意,这将触及一些基本问题.嵌套类是指一个类定义包含在另一个类中.基本上有两种类型:静态嵌套类和内部类.它们之间的真正区别是:

  • 静态嵌套类:
  • 内部嵌套类:

Garbage Collection and Inner Classes

垃圾收集是自动的,但会根据是否认为正在使用对象来try 删除对象.垃圾收集器相当智能,但并非完美无缺.它只能通过是否存在对该对象的活动引用来确定是否正在使用某个对象.

这里真正的问题是内部类的存活时间比它的容器长的时候.这是因为对包含类的隐式引用.发生这种情况的唯一方法是,如果包含类外部的对象保留对内部对象的引用,而不考虑包含对象.

这可能会导致内部对象处于活动状态(通过引用),但对包含对象的引用已从所有其他对象中删除.因此,内部对象将保持包含对象的活动状态,因为它将有一个对它的引用.问题是,除非对其进行编程,否则无法返回包含该对象的对象以判断其是否还活着.

实现这一点最重要的一点是,无论它是在活动中还是在可绘制中,都没有区别.在使用内部类时,必须有条不紊,并确保它们永远不会超过容器中的对象.幸运的是,如果它不是代码的核心对象,那么相比之下,泄漏可能很小.不幸的是,这些是最难发现的泄漏,因为在许多泄漏之前,它们很可能会被忽视.

Solutions: Inner Classes

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

Activities and Views: Introduction

活动包含大量信息,可以运行和显示.活动由其必须具有视图的特性来定义.它们还具有某些自动处理程序.无论是否指定,该活动都会对其包含的视图进行隐式引用.

为了创建一个视图,它必须知道在哪里创建它,以及它是否有子视图,以便显示.这意味着每个视图都有对活动的引用(通过getContext()).此外,每个视图都保留对其子视图(即getChildAt())的引用.最后,每个视图都会保留对表示其显示的渲染位图的引用.

每当您有一个对活动(或活动上下文)的引用时,这意味着您可以沿着布局层次结构的整个链.这就是为什么关于活动或视图的内存泄漏是如此巨大的问题.它可能是一次泄漏ton个内存.

Activities, Views and Inner Classes

根据上面关于内部类的信息,这些是最常见的内存泄漏,但也是最常避免的.虽然让内部类可以直接访问Activity类成员是可取的,但许多人愿意将其设为静电,以避免潜在的问题.活动和观点的问题远不止于此.

Leaked Activities, Views and Activity Contexts

这一切都可以归结为上下文和生命周期.某些事件(如定向)会扼杀活动上下文.因为有如此多的类和方法需要上下文,所以开发人员有时会试图通过获取上下文的引用并保留它来节省一些代码.碰巧的是,我们为运行我们的活动而必须创建的许多对象必须存在于活动生命周期之外,以便允许该活动执行它需要做的事情.如果您的任何对象在销毁时碰巧具有对活动、其上下文或其任何视图的引用,则您刚刚泄露了该活动及其整个视图树.

Solutions: Activities and Views

  • 不惜一切代价避免对视图或活动进行静态引用.
  • 对活动上下文的所有引用都应该是短期的(函数的持续时间)
  • 如果需要长期上下文,请使用应用程序上下文(getBaseContext()getApplicationContext()).这些不会隐式保留引用.
  • 或者,您也可以通过覆盖配置更改来限制活动的销毁.但是,这并不能阻止其他潜在事件破坏活动.当您can这样做时,您可能仍然希望参考上面的实践.

Runnables: Introduction

run 其实没那么糟糕.我的意思是,他们可能是could人,但实际上我们已经到达了大多数危险区域.Runnable是一个异步操作,它独立于创建它的线程执行任务.大多数可运行程序都是从UI线程实例化的.本质上,使用Runnable是在创建另一个线程,只是稍微有点管理性.如果你像标准类一样将一个Runnable分类,并遵循上面的指导原则,你应该不会遇到什么问题.现实是,许多开发人员并没有这样做.

出于易用性、可读性和逻辑程序流的考虑,许多开发人员使用匿名内部类来定义他们的可运行项,比如上面创建的示例.这将产生一个类似于您在上面键入的示例.匿名内部类基本上是一个离散的内部类.您不必创建一个全新的定义,只需覆盖适当的方法.在所有其他方面,它都是一个内部类,这意味着它保留了对其容器的隐式引用.

Runnables and Activities/Views

耶!这个部分可能很短!由于可运行程序在当前线程之外运行,因此它们的危险在于长期运行的异步操作.如果runnable在活动或视图中定义为匿名内部类或嵌套内部类,则存在一些非常严重的危险.这是因为,如前所述,它需要知道它的容器是谁.输入方向更改(或系统终止).现在只需参考前面的章节就可以理解刚才发生了什么.是的,你的例子很危险.

Solutions: Runnables

  • 如果不破坏代码逻辑,请try 扩展Runnable.
  • 如果扩展的可运行文件必须是嵌套类,请尽量使其成为静态的.
  • 如果必须使用匿名可运行项,请避免在对正在使用的活动或视图具有长期引用的any对象中创建它们.
  • 许多Runnable也可以很容易地实现异步任务.考虑使用AssiCtWork作为默认管理的VM.

Answering the Final Question

下面是一个基本工厂(缺少代码)的常见示例.

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

现在,我们有泄漏,但没有工厂.即使我们发布了Factory,它也会留在内存中,因为每个泄漏都会引用它.即使外部类没有数据也无关紧要.这种情况的发生比人们想象的要频繁得多.我们不需要造物主,只需要它的创造物.因此,我们临时创建一个,但无限期地使用这些创作.

想象一下,当我们稍微更改构造函数时会发生什么.

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();
        }
    }
}

现在,每一家新的泄密工厂都被泄露了.你对此有何感想?这是两个非常常见的示例,说明内部类如何比任何类型的外部类更持久.如果外层课程是一项活动,想象一下会有多糟糕.

Conclusion

以下列出了不适当使用这些物品的主要已知危险.总的来说,这篇文章应该涵盖你的大部分问题,但我知道这是一篇龙文,所以如果你需要澄清,请告诉我.只要您遵循上述做法,您就不会担心泄漏.

Java相关问答推荐

TCP 发送缓冲区没有做任何事情

如何在不定义每个 Spring bean 的情况下创建多个相同类型的 Spring bean

无法在 JSP 和 Servlet 中解析方法 getException()

如何在使用 Stream API 的函数更改字符串变量时跟踪它?

0 到 1000 的随机数 Math.random()

Java 使用多个类型参数初始化数组错误 java.lang.ClassCastException: [Ljava.lang.Object;不能转换为

EntityManager.remove 和 EntityManager.persist 上的 JPA 重复条目错误

如何在不克隆的情况下使用 JGIT 将文件夹添加到 GitHub 存储库?

将 JGIT 库添加到项目中会导致资源重叠

在运行时更改 Spring bean 实现

我是否缺少序列化演示的简单内容

再次“过时的元素引用:元素未附加到页面文档”

如何在 Intellij 中关闭“将文件添加到 git”?

防止Java中的一个永无止境的while循环

Spark - 多次运行选择语句的问题

如何在我的 RecyclerView 中显示来自 firebase 的子元素数据的子元素?

查找有向图中的所有循环,包括后边

JMS setRollbackOnly - 不一致的行为

错误需要找不到类型为“XXX”的 bean

使用 Selenium 使用 sendKeys() 时,大写字母被重新排列/交换