Warning: Do not place Android context classes in static fields; this is a memory leak (and also breaks Instant Run)

Android Studio:

Do not place Android context classes in static fields; this is a memory leak (and also breaks Instant Run)

So 2 questions:

#1 How do you call a startService from a static method without a static variable for context?
#2 How do you send a localBroadcast from a static method (same)?

Examples:

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

or

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

What would be the correct way to do this without using mContext?

NOTE: I think my main question might be how to pass context to a class from which the calling method lives.

75991 次浏览

Simply pass it as a parameter to your method. There is no sense in creating a static instance of Context solely for the purpose of starting an Intent.

This is how your method should look:

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

Update from comments on question: Cascade the context from the initiating activity (via constructor parameters or method parameters) right up to the point you need it.

In your case it does not make much sense to have it as static field but I don't think it's bad in all cases. If you now what are you doing you can have static field that has context and null it later. I am creating static instance for my main model class that has context inside, its application context not activity context and also i have static instance field of class containing Activity that I null in on destroy. I don't see that I have memory leak. So if some clever guy think i am wrong feel free to comment...

Also Instant Run works here fine...

Just make sure you pass context.getApplicationContext() or call getApplicationContext() on any context that is passed in via methods/constructor to your singleton if you decide to store it in any member field.

idiot proof example (even if someone would pass in an activity it will grab the app context and use that to instantiate the singleton):

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

getApplicationContext() according to the docs: "Return the context of the single, global Application object of the current process."

It means that the context returned by "getApplicationContext()" will live through the entire process and thus it does not matter if you store a static reference to it anywhere since it will always be there during the runtime of your app (and outlive any objects/singletons instantiated by it).

Compare that to the context inside of views/activities holding large amounts of data, if you leak a context held by an activity, the system won't be able to free that resource which obviously is not good.

A reference to an activity by its context should live the same life cycle as the activity itself, otherwise it will hold the context hostage causing a memory leak (which is the reason behind the lint warning).

EDIT: To the guy bashing the example from the docs above, there's even a comment section in the code about what I just wrote about:

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

It's just a warning. Don't worry. If you wanna use an application context, you can save it in a "singleton" class, which is used to save all of singleton class in your project.

Generally, avoid having context fields defined as static. The warning itself explains why: It's a memory leak. Breaking instant run may not be the biggest problem on the planet though.

Now, there are two scenarios where you'd get this warning. For an instance (the most obvious one):

public static Context ctx;

And then there's the bit more tricky one, where the context is wrapped in a class:

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

And that class is defined as static somewhere:

public static Example example;

And you'll get the warning.

The solution itself is fairly simple: Do not place context fields in static instances, whether that of a wrapping class or declaring it static directly.

And the solution to the warning is simple: don't place the field statically. In your case, pass the context as an instance to the method. For classes where multiple Context calls are made, use a constructor to pass the context (or an Activity for that matter) to the class.

Note that it is a warning, not an error. Should you for whatever reason need a static context, you can do it. Though you create a memory leak when you do so.

If you make sure it's an Application Context. It doest matter. Add this

@SuppressLint("StaticFieldLeak")

Use WeakReference to store the Context in Singleton classes & the warning will be gone

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

Now you can access Context like

  if (context.get() instanceof MainActivity) {
((MainActivity) context.get()).startActivityForResult(pickIntent, CODE_REQUEST_PICK_APPWIDGET);
}

You can set App instance as static property. For example:

public class App extends MultiDexApplication {
private static App context;


public void onCreate() {
super.onCreate();
App.context = this;
}


public static Context getAppContext() {
return App.context;
}
}

Warning will be gone. But I don't know that is this best way.