简体   繁体   English

为什么对 Context 的引用会导致内存泄漏?

[英]Why is the reference to the Context a memory leak?

According to Romain Guy this kind of code is prone to memory leak due to the fact that根据Romain Guy 的说法,这种代码很容易发生内存泄漏,因为

.... views have a reference to the entire activity and therefore to anything your activity is holding onto; .... 视图引用了整个活动,因此引用了您的活动所持有的任何内容; usually the entire View hierarchy and all its resources.通常是整个 View 层次结构及其所有资源。

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);

  TextView label = new TextView(this);
  label.setText("Leaks are bad");

  setContentView(label);
}  

I am not clear about this.我不清楚这一点。
Assuming an application with 1 activity, this is the longest lived object and can be recreated as needed.假设应用程序有 1 个活动,这是生命周期最长的对象,可以根据需要重新创建。 Which means that all of its instance fields (which can and usually are View s) can be null at any time.这意味着它的所有实例字段(可以并且通常是View )在任何时候都可以为空。
And any static instance field will live for the same duration as the activity itself.并且任何静态实例字段都将与活动本身的持续时间相同。
So how can we get a memory leak with code like the above or the following:那么我们如何使用上面或下面的代码来获得内存泄漏:

private static Drawable sBackground;

@Override
protected void onCreate(Bundle state) {
  super.onCreate(state);

  TextView label = new TextView(this);
  label.setText("Leaks are bad");

  if (sBackground == null) {
    sBackground = getDrawable(R.drawable.large_bitmap);
  }
  label.setBackgroundDrawable(sBackground);

  setContentView(label);
}

Assuming an application with 1 activity, this is the longest lived object假设一个应用程序有 1 个活动,这是生命周期最长的对象

No, it is not.不它不是。 There are other objects in your process (eg, Application , content providers) that will outlive an activity instance.您的流程中还有其他对象(例如, Application 、内容提供者)将比活动实例存活时间更长。

In particular, note that activities get destroyed and recreated by default on a configuration change (eg, screen rotation).特别要注意的是,默认情况下,Activity 在配置更改(例如,屏幕旋转)时会被销毁和重新创建。

And any static instance field will live for the same duration as the activity itself.并且任何静态实例字段都将与活动本身的持续时间相同。

No. Static fields are around as long as the process is around.不。只要过程存在,静态字段就会存在。 Your activity instances can be shorter-lived than that.您的活动实例可以比这更短。

So how can we get a memory leak with code like the above or the following:那么我们如何使用上面或下面的代码来获得内存泄漏:

There is no static field in your first example.您的第一个示例中没有静态字段。

Romain Guy explains the second scenario in the blog post that you linked to : Romain Guy 解释了您链接到的博客文章中的第二种情况:

This code is very fast and also very wrong;这段代码非常快,也非常错误; it leaks the first activity created upon the first screen orientation change.它会泄漏在第一次屏幕方向更改时创建的第一个活动。 When a Drawable is attached to a view, the view is set as a callback on the drawable.当 Drawable 附加到视图时,视图被设置为可绘制对象的回调。 In the code snippet above, this means the drawable has a reference to the TextView which itself has a reference to the activity (the Context) which in turns has references to pretty much anything (depending on your code.)在上面的代码片段中,这意味着可绘制对象有一个对 TextView 的引用,它本身有一个对活动(上下文)的引用,而后者又引用了几乎任何东西(取决于您的代码)。

And, if you added LeakCanary to your project, you would see the leak.而且,如果您将LeakCanary添加到您的项目中,您将看到泄漏。

So, let's walk through this:那么,让我们来看看这个:

  • User taps on the home screen launcher icon for your app, which is tied to this activity用户点击您的应用程序的主屏幕启动器图标,该图标与此活动相关联
  • Your process is started您的流程已启动
  • Your activity instance is created, and is then called with onCreate()您的活动实例已创建,然后使用onCreate()调用
  • sBackground is null , and so you assign it the getDrawable() result sBackgroundnull ,因此您将getDrawable()结果分配给它
  • Your activity UI appears on the screen您的活动界面出现在屏幕上
  • The user sneezes and accidentally rotates the screen of the device as part of reacting to the sneeze用户打喷嚏并意外旋转设备的屏幕作为对打喷嚏做出反应的一部分
  • Your old activity instance is destroyed您的旧活动实例已销毁
  • A new activity instance is created, and is then called with onCreate()创建一个新的活动实例,然后使用onCreate()调用
  • sBackground is not null , and so you leave sBackground alone sBackground不为null ,所以你sBackground

And you have your leak.你有你的泄漏。 As Romain explained, sBackground has a not-very-obvious reference back to your original activity instance.正如 Romain 所解释的那样, sBackground有一个不太明显的对原始活动实例的引用。 So, now you have two outstanding instances of this activity: the leaked original, plus the new one created due to the configuration change.因此,现在您有此活动的两个未完成实例:泄露的原始实例,以及由于配置更改而创建的新实例。

TLDR This is no longer a memory leak, since 2010. Also, I would have said this was a bug (memory leak) in Android (since this reference was an implementation detail), not in your app. TLDR自 2010 年以来,这不再是内存泄漏。此外,我会说这是 Android 中的一个错误(内存泄漏)(因为此参考是一个实现细节),而不是您的应用程序。 We didn't even know that setBackgroundDrawable (deprecated in favor of setBackground ) calls setCallback which sets a strong reference to the view .我们甚至不知道setBackgroundDrawable (不赞成使用setBackground )调用setCallback ,它设置了对view的强引用。 Here I explain why Drawable has a reference to View anyway. 这里我解释一下为什么Drawable无论如何都引用了View


Romain Guy made a fix to the library in 2010 to prevent this from causing a memory leak by using a WeakReference , so this hasn't been a problem for many years: Romain Guy 在2010 年对该库进行了修复,以防止使用WeakReference导致内存泄漏,因此多年来这一直不是问题:

在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM