欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

什么时候使用(匿名)内部类确切泄漏?

程序员文章站 2022-07-14 18:26:12
...

本文翻译自:When exactly is it leak safe to use (anonymous) inner classes?

I have been reading some articles on memory leaks in Android and watched this interesting video from Google I/O on the subject . 我一直在阅读一些有关Android内存泄漏的文章,并观看了Google I / O 关于此主题的有趣视频。

Still, I don't fully understand the concept, and especially when it is safe or dangerous to user inner classes inside an Activity . 尽管如此,我还是不完全理解这个概念,特别是当在Activity中使用内部类是安全或危险的时候

This is what I understood: 这就是我的理解:

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? - > 在哪种情况下会发生这种情况?

In this example, I suppose there is no risk of leak, because there is no way the anonymous class extending OnClickListener will live longer than the activity, right? 在这个例子中,我认为没有泄漏的风险,因为扩展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();

Now, is this example dangerous, and why? 现在,这个例子是危险的,为什么?

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

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

I have a doubt regarding the fact that understanding this topic has to do with understanding in detail what is kept when an activity is destroyed and re-created. 我对于理解这个主题与详细了解活动被销毁和重新创建时所保留的内容有关。

Is it? 是吗?

Let say I just changed the orientation of the device (which is the most common cause of leaks). 假设我刚改变了设备的方向(这是泄漏的最常见原因)。 When super.onCreate(savedInstanceState) will be called in my onCreate() , will this restore the values of the fields (as they were before orientation change)? 当我的onCreate()调用super.onCreate(savedInstanceState)时,是否会恢复字段的值(就像在方向更改之前一样)? Will this also restore the states of inner classes? 这还会恢复内部阶级的状态吗?

I realize my question is not very precise, but I'd really appreciate any explanation that could make things clearer. 我意识到我的问题不是很精确,但我真的很感激任何可以使事情变得清晰的解释。


#1楼

参考:https://stackoom.com/question/jaRZ/什么时候使用-匿名-内部类确切泄漏


#2楼

What you are asking is a pretty tough question. 你问的是一个非常棘手的问题。 While you may think it is just one question, you are actually asking several questions at once. 虽然您可能认为这只是一个问题,但实际上您实际上是在问几个问题。 I'll do my best with the knowledge that I have to cover it and, hopefully, some others will join in to cover what I may miss. 我会尽我所能来覆盖它,并且希望其他一些人能够加入到我可能想念的范围内。

Nested Classes: Introduction 嵌套类:简介

As I'm not sure how comfortable you are with OOP in Java, this will hit a couple of basics. 由于我不确定你在Java中使用OOP有多舒服,这将有几个基础。 A nested class is when a class definition is contained within another class. 嵌套类是在另一个类中包含类定义的时候。 There are basically two types: Static Nested Classes and Inner Classes. 基本上有两种类型:静态嵌套类和内部类。 The real difference between these are: 这些之间的真正区别是:

  • 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. 终身应该不会超过容器的寿命。

Garbage Collection and Inner Classes 垃圾收集和内部类

Garbage Collection is automatic but tries to remove objects based on whether it thinks they are being used. 垃圾收集是自动的,但会尝试根据对象是否认为正在使用它们来删除它们。 The Garbage Collector is pretty smart, but not flawless. 垃圾收集器非常聪明,但并不完美。 It can only determine if something is being used by whether or not there is an active reference to the object. 它只能通过是否存在对象的活动引用来确定是否正在使用某些内容。

The real issue here is when a inner class has been kept alive longer than its container. 这里真正的问题是内部类比它的容器保持活着的时间长。 This is because of the implicit reference to the containing class. 这是因为对包含类的隐式引用。 The only way this can occur is if an object outside of the containing class keeps a reference to the inner object, without regard to the containing object. 发生这种情况的唯一方法是,如果包含类之外的对象保留对内部对象的引用,而不考虑包含对象。

This can lead to a situation where the inner object is alive (via reference) but the references to the containing object has already been removed from all other objects. 这可能导致内部对象处于活动状态(通过引用),但是已经从所有其他对象中删除了对包含对象的引用。 The inner object is, therefore, keeping the containing object alive because it will always have a reference to it. 因此,内部对象使包含对象保持活动状态,因为它始终具有对它的引用。 The problem with this is that unless it is programmed, there is no way to get back to the containing object to check if it is even alive. 这个问题是,除非它被编程,否则无法返回到包含对象以检查它是否还活着。

The most important aspect to this realization is that it makes no difference whether it is in an Activity or is a drawable. 这种认识最重要的方面是,无论是在活动中还是在绘制中都没有区别。 You will always have to be methodical when using inner classes and make sure that they never outlive objects of the container. 在使用内部类时,您将始终必须是有条理的,并确保它们永远不会超过容器的对象。 Luckily, if it isn't a core object of your code, the leaks may be small in comparison. 幸运的是,如果它不是代码的核心对象,相比之下泄漏可能很小。 Unfortunately, these are some of the hardest leaks to find, because they are likely to go unnoticed until many of them have leaked. 不幸的是,这些是最难发现的漏洞,因为它们可能会被忽视,直到它们中的许多漏洞。

Solutions: Inner Classes 解决方案:内部课程

  • Gain temporary references from the containing object. 从包含对象获取临时引用。
  • Allow the containing object to be the only one to keep long-lived references to the inner objects. 允许包含对象是唯一一个保持对内部对象的长期引用的对象。
  • Use established patterns such as the Factory. 使用已建立的模式,例如Factory。
  • If the inner class does not require access to the containing class members, consider turning it into a static class. 如果内部类不需要访问包含的类成员,请考虑将其转换为静态类。
  • Use with caution, regardless of whether it is in an Activity or not. 无论是否在活动中,都要谨慎使用。

Activities and Views: Introduction 活动和观点:简介

Activities contain a lot of information to be able to run and display. 活动包含许多能够运行和显示的信息。 Activities are defined by the characteristic that they must have a View. 活动由他们必须拥有View的特征定义。 They also have certain automatic handlers. 他们还有一些自动处理程序。 Whether you specify it or not, the Activity has an implicit reference to the View it contains. 无论您是否指定它,Activity都会隐式引用它包含的View。

In order for a View to be created, it must know where to create it and whether it has any children so that it can display. 为了创建视图,它必须知道创建它的位置以及它是否有任何子节点以便它可以显示。 This means that every View has an reference to the Activity (via getContext() ). 这意味着每个View都有一个对Activity的引用(通过getContext() )。 Moreover, every View keeps references to its children (ie getChildAt() ). 此外,每个View都会保留对其子项的引用(即getChildAt() )。 Finally, each View keeps a reference to the rendered Bitmap that represents its display. 最后,每个View都会保留对表示其显示的渲染位图的引用。

Whenever you have a reference to an Activity (or Activity Context), this means that you can follow the ENTIRE chain down the layout hierarchy. 每当您引用Activity(或Activity Context)时,这意味着您可以沿着布局层次结构沿着整个链。 This is why memory leaks regarding Activities or Views are such a huge deal. 这就是为什么有关活动或视图的内存泄漏是如此巨大的原因。 It can be a ton of memory being leaked all at once. 一下子就可以泄露大量的内存。

Activities, Views and Inner Classes 活动,观点和内在阶级

Given the information above about Inner Classes, these are the most common memory leaks, but also the most commonly avoided. 鉴于上面有关内部类的信息,这些是最常见的内存泄漏,但也是最常见的内存泄漏。 While it is desirable to have an inner class have direct access to an Activities class members, many are willing to just make them static to avoid potential issues. 虽然希望内部类可以直接访问Activities类成员,但是许多人愿意将它们设置为静态以避免潜在的问题。 The problem with Activities and Views goes much deeper than that. 活动和观点的问题比这更深刻。

Leaked Activities, Views and Activity Contexts 泄露的活动,观点和活动背景

It all comes down to the Context and the LifeCycle. 这一切都归结为Context和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. 由于许多类和方法需要Context,因此开发人员有时会尝试通过获取对Context的引用并保留它来保存一些代码。 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. 事实上,我们必须创建的许多用于运行Activity的对象必须存在于Activity LifeCycle之外,以便允许Activity执行它需要做的事情。 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. 如果你的任何对象碰巧有一个对Activity,它的Context或它的任何Views被销毁的引用,你刚刚泄露了该Activity及其整个View树。

Solutions: Activities and Views 解决方案:活动和观点

  • 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() ). 如果需要长期使用的Context,请使用Application Context( getBaseContext()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: Introduction Runnables:简介

Runnables are actually not that bad. Runnables实际上并没有那么糟糕。 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. Runnable是一个异步操作,它执行与创建它的线程无关的任务。 Most runnables are instantiated from the UI thread. 大多数runnable都是从UI线程实例化的。 In essence, using a Runnable is creating another thread, just slightly more managed. 从本质上讲,使用Runnable创建另一个线程,只是稍微管理一下。 If you class a Runnable like a standard class and follow the guidelines above, you should run into few problem. 如果您将Runnable归类为标准类并遵循上面的指导原则,那么您应该遇到一些问题。 The reality is that many developers do not do this. 现实是许多开发人员不这样做。

Out of ease, readability and logical program flow, many developers utilize Anonymous Inner Classes to define their Runnables, such as the example you create above. 由于易用性,可读性和逻辑程序流,许多开发人员使用匿名内部类来定义他们的Runnables,例如您在上面创建的示例。 This results in an example like the one you typed above. 这会产生一个类似上面输入的示例。 An Anonymous Inner Class is basically a discrete Inner Class. 匿名内部类基本上是一个离散的内部类。 You just don't have to create a whole new definition and simply override the appropriate methods. 您只需创建一个全新的定义并简单地覆盖适当的方法即可。 In all other respects it is a Inner Class, which means that it keeps an implicit reference to its container. 在所有其他方面,它是一个内部类,这意味着它保持对其容器的隐式引用。

Runnables and Activities/Views Runnables和活动/视图

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. 由于Runnables在当前线程之外运行,因此这些问题的危险在于长时间运行的异步操作。 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. 如果runnable在Activity或View中定义为匿名内部类或嵌套内部类,则存在一些非常严重的危险。 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. 是的,你的例子非常危险。

Solutions: Runnables 解决方案:Runnables

  • Try and extend Runnable, if it doesn't break the logic of your code. 如果它没有破坏代码的逻辑,请尝试并扩展Runnable。
  • Do your best to make extended Runnables static, if they must be nested classes. 如果必须是嵌套类,请尽量使扩展的Runnables变为静态。
  • If you must use Anonymous Runnables, avoid creating them in any object that has a long-lived reference to an Activity or View that is in use. 如果必须使用Anonymous Runnables,请避免在任何具有对正在使用的Activity或View的长期引用的对象中创建它们。
  • Many Runnables could just as easily have been AsyncTasks. 许多Runnables可以很容易地成为AsyncTasks。 Consider using AsyncTask as those are VM Managed by default. 考虑使用AsyncTask,因为默认情况下是VM Managed。

Answering the Final Question Now to answer the questions that were not directly addressed by the other sections of this post. 回答最终问题现在回答本文其他部分没有直接解决的问题。 You asked "When can an object of an inner class survive longer than its outer class?" 你问过“内部阶级的对象什么时候能够比外部阶级存活更长时间?” Before we get to this, let me reemphasize: though you are right to worry about this in Activities, it can cause a leak anywhere. 在我们谈到这一点之前,让我再强调一下:尽管你在“活动”中担心这一点是正确的,但它可能导致任何地方的泄漏。 I shall provide a simple example (without using an Activity) just to demonstrate. 我将提供一个简单的例子(不使用Activity)来演示。

Below is a common example of a basic factory (missing the code). 下面是基本工厂的常见示例(缺少代码)。

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

This is a not as common example, but simple enough to demonstrate. 这不是一个常见的例子,但很容易证明。 The key here is the constructor... 这里的关键是构造函数......

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

Now, we have Leaks, but no Factory. 现在,我们有泄漏,但没有工厂。 Even though we released the Factory, it will remain in memory because every single Leak has a reference to it. 即使我们发布了Factory,它也会保留在内存中,因为每个Leak都会引用它。 It doesn't even matter that the outer class has no data. 外层没有数据也没关系。 This happens far more often than one might think. 这种情况比人们想象的要频繁得多。 We don't need the creator, just its creations. 我们不需要创作者,只需要创作。 So we create one temporarily, but use the creations indefinitely. 所以我们暂时创建一个,但无限期地使用创作。

Imagine what happens when we change the constructor just slightly. 想象一下当我们稍微改变构造函数时会发生什么。

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

Now, every single one of those new LeakFactories has just been leaked. 现在,这些新的LeakFactories中的每一个都刚刚被泄露。 What do you think of that? 你对那个怎么想的? Those are two very common examples of how a inner class can outlive an outer class of any type. 这是两个非常常见的例子,说明内部类如何能够比任何类型的外部类寿命更长。 If that outer class had been an Activity, imagine how much worse it would have been. 如果那个外类是一个活动,想象它本来会有多糟糕。

Conclusion 结论

These list the primarily known dangers of using these objects inappropriately. 这些列出了不恰当地使用这些对象的主要已知危险。 In general, this post should have covered most of your questions, but I understand it was a loooong post, so if you need clarification, just let me know. 一般来说,这篇文章应该涵盖了你的大多数问题,但我知道这是一个很好的帖子,所以如果你需要澄清,请告诉我。 As long as you follow the above practices, you will have very little worry of leakage. 只要您遵循上述做法,您就不必担心泄漏。