Предупреждение: не размещайте классы контекста Android в статических полях; это утечка памяти (а также нарушает работу Instant Run)

84

Android Studio:

Не размещайте классы контекста Android в статических полях; это утечка памяти (а также нарушает работу Instant Run)

Итак, 2 вопроса:

# 1 Как вызвать a startServiceиз статического метода без статической переменной для контекста?
# 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?

ПРИМЕЧАНИЕ. Я думаю, что мой главный вопрос может заключаться в том, как передать контекст классу, из которого живет вызывающий метод.

Джон Смит
источник
Разве вы не можете передать контекст в качестве параметра метода?
Хуан Круз Солер
Я бы назвал эту процедуру там, где тоже не было бы контекста.
John Smith
# 1 передаем его как параметр # 2 то же самое.
njzk2 08
Затем вы также должны передать контекст вызывающему методу. Проблема в том, что статические поля не собираются сборщиком мусора, поэтому вы можете пропустить действие со всеми его представлениями
Хуан Круз Солер
1
@JohnSmith Каскадируйте его от инициирующего действия (через параметры конструктора или параметры метода) вплоть до нужной вам точки.
AndroidMechanic - Viral Patel

Ответы:

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

Обновление на основе комментариев к вопросу: каскадирование контекста из инициирующего действия (с помощью параметров конструктора или параметров метода) до момента, когда он вам нужен.

AndroidMechanic - вирусный Патель
источник
Вы можете привести пример конструктора?
John Smith
если имя вашего класса - MyClassдобавьте к нему общедоступный конструктор, точно так же, как метод public MyClass(Context ctx) { // put this ctx somewhere to use later }(это ваш конструктор). Теперь создайте новый экземпляр MyClassиспользования этого конструктора, напримерMyClass mc = new MyClass(ctx);
AndroidMechanic - Viral Patel
Я не думаю, что это так просто передать по требованию. Хотя есть очевидные преимущества, такие как отсутствие необходимости беспокоиться о устаревшем контексте или, как здесь, статическом. Скажем, вам нужен контекст [может быть, вы хотите написать в prefs] в обратном вызове ответа, который будет вызываться асинхронно. Поэтому иногда вы вынуждены помещать его в поле участника. А теперь надо подумать, как не сделать его статичным. stackoverflow.com/a/40235834/2695276, похоже, работает.
Раджат Шарма
1
Можно ли использовать ApplicationContext в качестве статического поля? В отличие от действий, объект приложения не уничтожается, верно?
NeoWang
но вопрос в том, почему вообще происходит утечка памяти?
juztcode
51

Просто убедитесь, что вы передаете 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)» Вы знаете, что такое классы контекста? Activity - одно из них, и вы не должны хранить Activity как статическое поле, как вы сами описали (иначе это приведет к утечке памяти). Однако вы можете сохранить Context (пока это контекст приложения) как статическое поле, поскольку оно переживает все. (И таким образом игнорируйте предупреждение). Я уверен, что мы можем согласиться с этим простым фактом, верно?
Маркус Грюно
в качестве ветеринара iOS, на моей первой неделе Android ... Подобные объяснения помогают мне понять эту чепуху контекста ... Итак, это предупреждение о ворсинах (о, как мне не нравятся любые предупреждения) будет висеть, но ваш ответ решает настоящую проблему .
eric
1
@Marcus, если ваш дочерний класс не знает, кто создает его в каком контексте, то хранить его как статический член - просто плохая практика. кроме того, контекст приложения живет как часть объекта Application вашего приложения, объект приложения не останется в памяти навсегда, он будет уничтожен. вопреки распространенному мнению, приложение не будет перезапущено с нуля. Android создаст новый объект Application и запустит действие там, где раньше был пользователь, чтобы создать иллюзию, что приложение никогда не было убито.
Raphael C
@RaphaelC у вас есть такая документация? Это кажется совершенно неправильным, потому что Android гарантирует только один контекст приложения за запуск каждого процесса.
HaydenKai
6

Это просто предупреждение. Не волнуйся. Если вы хотите использовать контекст приложения, вы можете сохранить его в «одноэлементном» классе, который используется для сохранения всего одноэлементного класса в вашем проекте.

Ликат Юлиус
источник
2

В вашем случае нет смысла использовать его как статическое поле, но я не думаю, что это плохо во всех случаях. Если вы сейчас, что вы делаете, вы можете иметь статическое поле с контекстом и обнулить его позже. Я создаю статический экземпляр для своего основного класса модели, который имеет контекст внутри, его контекст приложения, а не контекст активности, а также у меня есть поле статического экземпляра класса, содержащего Activity, которое я обнуляю при уничтожении. Я не вижу, что у меня утечка памяти. Так что, если какой-то умный парень думает, что я неправ, не стесняйтесь комментировать ...

Также здесь отлично работает Instant Run ...

Ренетик
источник
Я не думаю, что вы ошибаетесь в принципе, но вам нужно быть особенно осторожным, чтобы Activity, о которой вы говорите, имела максимум один единственный экземпляр в любой момент времени, прежде чем он сможет использовать статические поля. Если в вашем приложении больше одного стека, потому что его можно запускать из разных мест (уведомление, глубокая ссылка, ...), все пойдет не так, если вы не используете какой-либо флаг, например singleInstance, в манифесте. Так что всегда проще избегать статических полей в Activity.
BladeCoder
android: launchMode = "singleTask" должно быть достаточно, поэтому я переключаюсь на это, я использовал singleTop, но не знал, что этого недостаточно, потому что мне всегда нужны только отдельные экземпляры моих основных действий, вот как разрабатываются мои приложения.
Renetik
2
«singleTask» гарантирует только один экземпляр для каждой задачи. Если ваше приложение имеет несколько точек входа, например, глубокую ссылку или запуск из уведомления, вы можете столкнуться с несколькими задачами.
BladeCoder
2

Используйте WeakReferenceдля хранения контекста в классах Singleton, и предупреждение исчезнет

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);
        }
Хитеш Саху
источник
1

Как правило, избегайте определения полей контекста как статические. Само предупреждение объясняет, почему: это утечка памяти. Однако прерывание мгновенного бега может быть не самой большой проблемой на планете.

Теперь есть два сценария, при которых вы получите это предупреждение. Например (самый очевидный):

public static Context ctx;

И есть еще более сложный вопрос, когда контекст заключен в класс:

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

И этот класс где-то определяется как статический:

public static Example example;

И вы получите предупреждение.

Само решение довольно простое: не помещайте поля контекста в статические экземпляры , будь то экземпляры класса-оболочки или напрямую объявляйте его статическим.

И решение предупреждения простое: не размещайте поле статически. В вашем случае передайте контекст как экземпляр методу. Для классов, в которых выполняется несколько вызовов Context, используйте конструктор для передачи контекста (или Activity, если на то пошло) классу.

Обратите внимание, что это предупреждение, а не ошибка. Если вам по какой-либо причине нужен статический контекст, вы можете это сделать. Хотя при этом вы создаете утечку памяти.

Зои
источник
как мы можем сделать это, не создавая утечки памяти?
isJulian00
1
Вы не можете. Если вам абсолютно необходимо передавать контексты, вы можете заглянуть в автобус событий
Зои,
хорошо, это была проблема, с которой я столкнулся, если вы могли бы взглянуть на нее, возможно, есть другой способ сделать это, кстати, метод должен быть статическим, потому что я вызываю его из кода C ++ stackoverflow.com/questions/54683863/…
isJulian00
0

Если вы убедитесь, что это Application Context. Это действительно важно. Добавь это

@SuppressLint("StaticFieldLeak")
Виктор Чой
источник
1
В любом случае я бы не рекомендовал это делать. Если вам нужен контекст, вы можете использовать метод requireContext (), если вы используете библиотеки AndroidX. Или вы можете передать Context непосредственно методу, который в нем нуждается. Или вы даже можете просто получить ссылку на класс приложения, но я бы предпочел не использовать такое предложение SuppressLint.
Александр Нос