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

Android Studio:

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

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

# 1 Как вы вызываете 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);
    }
}

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

Как правильно это сделать без использования mContext?

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


person John Smith    schedule 08.06.2016    source источник
comment
Разве вы не можете передать контекст в качестве параметра в методе?   -  person Juan Cruz Soler    schedule 08.06.2016
comment
Я бы назвал эту рутину в местах, где также не было бы контекста.   -  person John Smith    schedule 08.06.2016
comment
# 1 передаем его как параметр # 2 то же самое.   -  person njzk2    schedule 08.06.2016
comment
Затем вы также должны передать контекст вызывающему методу. Проблема в том, что статические поля не собираются сборщиком мусора, поэтому вы можете пропустить действие со всеми его представлениями.   -  person Juan Cruz Soler    schedule 08.06.2016
comment
Чего мне может не хватать, так это того, как передать контекст классу.   -  person John Smith    schedule 08.06.2016
comment
@JohnSmith Каскадируйте его от инициирующего действия (через параметры конструктора или параметры метода) вплоть до нужной вам точки.   -  person AndroidMechanic - Viral Patel    schedule 08.06.2016
comment
Когда вы используете jni с c и вызываете SharedPreferences в c, кажется невозможным не разместить классы контекста Android в статических полях. Я даже использую статический метод java для получения классов контекста Android. github.com/golang/mobile/blob/master/bind/ java / LoadJNI.java.   -  person bronze man    schedule 23.03.2017
comment
Согласно этому комментарию, который ссылается на этот отчет о проблеме, иногда это предупреждение может рассматриваться как ложное. .   -  person TT--    schedule 17.07.2019


Ответы (7)


Просто передайте его в качестве параметра вашему методу. Нет смысла создавать статический экземпляр 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);
    }
}

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

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

Просто убедитесь, что вы передаете 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.
person Marcus Gruneau    schedule 25.10.2016
comment
парню, избивающему парня, разбившего приведенный выше пример: суть этой ветки - предупреждение Lint, противоречащее собственному рекомендованному Google шаблону создания синглтона. - person Raphael C; 02.11.2016
comment
Читайте: не размещайте классы контекста Android в статических полях; это утечка памяти (а также нарушает работу Instant Run). Вы знаете, что такое классы контекста? Activity - одно из них, и вы не должны хранить Activity как статическое поле, как вы сами описали (иначе это приведет к утечке памяти). Однако вы можете сохранить Context (если это контекст приложения) как статическое поле, поскольку оно переживает все. (И таким образом игнорируйте предупреждение). Я уверен, что мы можем согласиться с этим простым фактом, верно? - person Marcus Gruneau; 02.11.2016
comment
как ветеринар iOS, на моей первой неделе Android ... Подобные объяснения помогают мне понять эту чепуху контекста ... Итак, это предупреждение о ворсинах (о, как мне не нравятся любые предупреждения) будет висеть, но ваш ответ решает настоящую проблему . - person eric; 05.11.2016
comment
@Marcus, если ваш дочерний класс не знает, кто создает его в каком контексте, то хранить его как статический член - просто плохая практика. кроме того, контекст приложения живет как часть объекта Application вашего приложения, объект приложения не останется в памяти навсегда, он будет уничтожен. вопреки распространенному мнению, приложение не будет перезапущено с нуля. Android создаст новый объект Application и запустит действие там, где раньше был пользователь, чтобы создать иллюзию того, что приложение никогда не было убито. - person Raphael C; 09.11.2016
comment
@RaphaelC есть ли у вас такая документация? Это кажется совершенно неправильным, потому что Android гарантирует только один контекст приложения за запуск каждого процесса. - person HaydenKai; 16.01.2018
comment
@HaydenKai В сообщении Lint говорится, что размещение любого контекста Android в статических полях вызывает утечку памяти (см. Исходную тему этой ветки). он ничего не говорит об одном контексте приложения для каждого процесса. Кстати, процесс приложения в Android может быть в любой момент остановлен ОС, чтобы потребовать память. процесс Android не обязательно гарантирует, что экземпляр контекста приложения всегда будет одним и тем же, поскольку ОС может в любой момент убить процесс приложения, чтобы потребовать память. - person Raphael C; 16.01.2018
comment
Я не понимаю противоречия, но это действительно решило проблему, отличный ответ - person juztcode; 25.11.2020

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

person Licat Julius    schedule 06.03.2017

Используйте 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);
        }
person Hitesh Sahu    schedule 23.06.2020

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

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

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

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

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

public static Context ctx;

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

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

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

public static Example example;

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

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

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

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

person Zoe    schedule 22.12.2017
comment
как мы можем сделать это, не создавая утечки памяти? - person isJulian00; 14.02.2019
comment
Вы не можете. Если вам абсолютно необходимо передавать контексты, вы можете заглянуть в шину событий - person Zoe; 14.02.2019
comment
хорошо, это была проблема, с которой я столкнулся, если вы могли бы взглянуть на нее, возможно, есть другой способ сделать это, кстати, метод должен быть статическим, потому что я вызываю его из кода C ++ stackoverflow.com/questions / 54683863 / - person isJulian00; 14.02.2019

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

@SuppressLint("StaticFieldLeak")
person Victor Choy    schedule 29.07.2019
comment
В любом случае я бы не рекомендовал это делать. Если вам нужен контекст, вы можете использовать метод requireContext (), если вы используете библиотеки AndroidX. Или вы можете передать Context непосредственно методу, который в нем нуждается. Или вы даже можете просто получить ссылку на класс приложения, но я бы рекомендовал не использовать такое предложение SuppressLint. - person Oleksandr Nos; 05.09.2019