警告:请勿将Android上下文类放在静态字段中;这是内存泄漏(并且还会中断即时运行)


84

Android Studio:

不要将Android上下文类放在静态字段中;这是内存泄漏(并且还会中断即时运行)

所以有两个问题:

#1您如何startService从没有静态变量的静态方法中调用a ?
#2您如何通过静态方法(相同)发送localBroadcast?

例子:

public static void log(int iLogLevel, String sRequest, String sData) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(mContext, LogService.class);
        intent.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        mContext.startService(intent);
    }
}

要么

        Intent intent = new Intent(MAIN_ACTIVITY_RECEIVER_INTENT);
        intent.putExtra(MAIN_ACTIVITY_REQUEST_FOR_UPDATE, sRequest));
        intent.putExtra(MAIN_ACTIVITY_DATA_FOR_VIEW, sData);
        intent.putExtra(MAIN_ACTIVITY_LOG_LEVEL, iLogLevel);
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);

不使用此方法的正确方法是什么mContext

注意:我认为我的主要问题可能是如何将上下文传递给调用方法所在的类。


您不能在方法中将Context作为参数传递吗?
Juan Cruz Soler

我会在没有上下文的地方调用此例程。
约翰·史密斯

#1与参数#2相同。
njzk2

然后,您还必须将上下文传递给调用方方法。问题在于静态字段不会被垃圾收集,因此您可能会泄漏其所有视图的活动
Juan Cruz Soler

1
@JohnSmith从启动活动(通过构造函数参数或方法参数)一直层叠到您需要的位置。
AndroidMechanic-Viral Patel

Answers:


56

只需将其作为参数传递给您的方法即可。Context仅出于启动的目的创建静态实例是没有意义的Intent

这是您的方法的外观:

public static void log(int iLogLevel, String sRequest, String sData, Context ctx) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(ctx, LogService.class);
        intent1.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        ctx.startService(intent);
    }
}

问题注释的更新:从启动活动(通过构造函数参数或方法参数)直至所需的级联上下文。


您可以提供一个构造函数示例吗?
约翰·史密斯

如果您的类名是MyClass像方法一样添加一个公共构造函数public MyClass(Context ctx) { // put this ctx somewhere to use later }(这是您的构造函数)现在创建MyClass使用该构造函数的新实例,例如MyClass mc = new MyClass(ctx);
AndroidMechanic-Viral Patel

我认为按需传递并不那么简单。尽管有明显的好处,例如不必担心陈旧的上下文,或者像这里一样,是静态的。假设您需要在响应回调中使用上下文[可能是要写给首选项],该回调将被异步调用。因此,有时您不得不将其放在成员字段中。现在,您必须考虑如何使其不静态。stackoverflow.com/a/40235834/2695276似乎可以工作。
Rajat Sharma

1
可以将ApplicationContext用作静态字段吗?与活动不同,应用程序对象不会被销毁,对吗?
NeoWang19年

50

如果您决定将其存储在任何成员字段中,只需确保您通过context.getApplicationContext()或在通过方法/构造函数传递给您的单例的任何上下文上调用getApplicationContext()即可。

白痴证明示例(即使有人通过活动,它也会获取应用上下文并使用该实例实例化单例):

public static synchronized RestClient getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new RestClient(context.getApplicationContext());
    }
    return mInstance;
}

根据文档的getApplicationContext():“返回当前进程的单个全局Application对象的上下文。”

这意味着“ getApplicationContext()”返回的上下文将贯穿整个过程,因此,在任何地方存储对它的静态引用都没有关系,因为在应用程序运行期间,它始终存在/实例化的实例)。

将其与包含大量数据的视图/活动内部的上下文进行比较,如果泄漏活动所拥有的上下文,则系统将无法释放明显不好的资源。

通过活动上下文对活动的引用应与活动本身具有相同的生命周期,否则它将使活动成为人质,从而导致内存泄漏(这是棉绒警告的原因)。

编辑:对于猛击上述文档中的示例的人,甚至在代码中还有关于我刚刚写过的内容的注释部分:

    // getApplicationContext() is key, it keeps you from leaking the
    // Activity or BroadcastReceiver if someone passes one in.

8
抨击上面例子的家伙:这个话题的重点是Lint警告,与Google自己推荐的创建单例模式相冲突。
Raphael C

7
阅读:“不要将Android上下文类放在静态字段中;这是内存泄漏(并且还会中断Instant Run)”。您知道什么是上下文类吗?活动是其中之一,您不应该像描述自己一样将活动存储为静态字段(否则会泄漏内存)。但是,您可以将Context(只要是应用程序上下文)存储为静态字段,因为它的寿命超过了所有内容。(因此忽略警告)。我敢肯定,我们可以就这个简单的事实达成共识,对吗?
Marcus Gruneau

作为iOS兽医,在我使用Android的第一周...这样的解释可以帮助我理解上下文的废话。 。
eric

@Marcus如果您的子类不知道是谁在哪个上下文中实例化它,那么将其存储为静态成员是一种不好的做法。此外,应用程序上下文作为应用程序的Application对象的一部分存在,该应用程序对象不会永远保留在内存中,而是会被杀死。与流行的看法相反,该应用不会从头开始重启。Android将创建一个新的Application对象,并在用户之前所在的位置开始活动,以给人以为该应用程序从未被杀死的幻想。
拉斐尔C

@RaphaelC您有此类文件吗?这似乎是完全错误的,因为Android确保每个进程每次运行仅一个Application上下文。
HaydenKai


2

在您的情况下,将其作为静态字段没有多大意义,但我认为在所有情况下都不是一件坏事。如果您现在正在做什么,则可以使用具有上下文的静态字段,并在以后将其无效。我正在为内部具有上下文的主模型类创建静态实例,其应用程序上下文不是活动上下文,并且我具有包含Activity的类的静态实例字段,在销毁时为null。我看不到内存泄漏。因此,如果某个聪明的人认为我做错了,请随时发表评论...

另外Instant Run在这里也可以正常工作...


我认为您在原则上没有错,但是您需要格外小心,要使您谈论的Activity在任何给定时间最多只能有一个实例,然后才能使用静态字段。如果您的应用程序由于可以从不同的地方启动而导致返回多个堆栈(通知,深层链接等),除非您在清单中使用诸如singleInstance之类的标志,否则事情将会出错。因此,避免Activity中的静态字段总是比较容易的。
BladeCoder

android:launchMode =“ singleTask”应该足够了,所以我切换到那个位置,我使用了singleTop但并不知道它是不够的,因为我一直只希望我的主要活动有一个实例,这就是我的应用程序的设计方式。
Renetik '17

2
“ singleTask”仅保证每个任务一个实例。如果您的应用程序具有多个入口点,例如深层链接或从通知中启动它,则您可能会完成多个任务。
BladeCoder

1

通常,避免将上下文字段定义为静态。该警告本身解释了原因:这是内存泄漏。但是,打破即时运行可能不是地球上最大的问题。

现在,在两种情况下您会收到此警告。对于一个实例(最明显的一个):

public static Context ctx;

然后是一个比较棘手的问题,其中上下文被包装在一个类中:

public class Example{
    public Context ctx;
    //Constructor omitted for brievety 
}

该类在某处定义为静态的:

public static Example example;

而且您会得到警告。

解决方案本身非常简单:不要将上下文字段放在静态实例中,无论是包装类的上下文字段,还是直接声明为静态的字段

警告的解决方案很简单:不要静态放置字段。在您的情况下,请将上下文作为实例传递给方法。对于进行多个Context调用的类,请使用构造函数将上下文(或与此相关的Activity)传递给该类。

请注意,这是警告,不是错误。如果您出于某种原因需要静态上下文,则可以这样做。尽管这样做会造成内存泄漏。


我们如何做到这一点而又不造成内存泄漏?
isJulian00 '19

1
你不能 如果您绝对需要传递上下文信息,则可以研究一下事件总线
Zoe

好的,这是我遇到的问题,如果您可以请看一下,也许还有另一种方法可以使用,但该方法必须是静态的,因为我是从c ++代码stackoverflow.com/questions/54683863/…
isJulian00 '19

0

如果您确定它是一个应用程序上下文。没关系。加上这个

@SuppressLint("StaticFieldLeak")

1
我不建议这样做。如果需要上下文,则可以使用requireContext()方法(如果使用AndroidX库)。或者,您可以将Context直接传递给需要它的方法。或者甚至可以只获取应用程序的类参考,但我建议不要使用此类SuppressLint建议。
Oleksandr,

0

使用WeakReference存储在辛格尔顿类和警告的背景下将会消失

private WeakReference<Context> context;

//Private contructor
private WidgetManager(Context context) {
    this.context = new WeakReference<>(context);
}

//Singleton
public static WidgetManager getInstance(Context context) {
    if (null == widgetManager) {
        widgetManager = new WidgetManager(context);
    }
    return widgetManager;
}

现在您可以像

  if (context.get() instanceof MainActivity) {
            ((MainActivity) context.get()).startActivityForResult(pickIntent, CODE_REQUEST_PICK_APPWIDGET);
        }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.