Попередження: Не розміщуйте контекстні класи Android у статичних полях; це витік пам'яті (а також порушує Миттєвий запуск)


84

Android Studio:

Не розміщуйте контекстні класи Android у статичних полях; це витік пам'яті (і також порушує миттєвий запуск)

Отже, 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?

ПРИМІТКА: Я думаю, моє головне питання може полягати в тому, як передати контекст класу, з якого живе метод виклику.


Ви не можете передати контекст як параметр у методі?
Хуан Крус Солер

Я б називав цю процедуру в місцях, де також не було б контексту.
Джон Сміт

# 1 передайте його як параметр # 2 однаково.
njzk2

Тоді вам також потрібно передати контекст методу, що викликає. Проблема полягає в тому, що статичні поля - це не зібране сміття, тому ви можете витікати з усіма його переглядами
Хуан Крус Солер

1
@JohnSmith Cascade це від ініціюючої діяльності (через параметри конструктора або параметри методу) аж до потрібної вам точки.
AndroidMechanic - Вірусний Патель

Відповіді:


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

Я не думаю, що це так просто передати на вимогу. Хоча є очевидні переваги, як-от не турбуватися про застарілий контекст або як тут, статичний. Скажімо, вам потрібен контекст [можливо, ви хочете написати у prefs] у зворотному виклику, який буде викликаний асинхронно. Тож часом ви змушені поміщати його в поле учасника. А тепер треба подумати, як не зробити його статичним. stackoverflow.com/a/40235834/2695276, здається, працює.
Раджат Шарма

1
Чи нормально використовувати ApplicationContext як статичне поле? На відміну від діяльності, об’єкт програми не руйнується, правда?
NeoWang

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 шаблоном створення синглтона.
Рафаель С

7
Читайте: "Не розміщуйте контекстні класи Android у статичних полях; це витік пам'яті (а також порушує миттєвий запуск)" Чи знаєте ви, що таке контекстні класи? Activity - це один із них, і ви не повинні зберігати Activity як статичне поле, як ви самі описали (інакше це витік пам’яті). Однак ви можете зберегти контекст (якщо це контекст програми) як статичне поле, оскільки воно переживає все. (І таким чином ігноруйте попередження). Я впевнений, що ми можемо домовитись про цей простий факт, так?
Маркус Груно

як ветеринар iOS, на моєму першому тижні Android ... Пояснення, подібні до цього, допомагають мені зрозуміти цей дурний контекст .. Отже, це попередження про ворсинки (о, як мені не подобаються будь-які попередження) буде зависати, але ваша відповідь вирішує справжню проблему .
eric

@Marcus, якщо ваш дочірній клас не знає, хто створює його за допомогою якого контексту, тоді просто зберігати його як статичний член - це просто погана практика. крім того, контекст програми живе як частина об'єкта Application вашої програми, об'єкт програми не назавжди залишиться в пам'яті, він загине. всупереч поширеній думці, додаток не буде перезавантажено з нуля. Android створить новий об'єкт програми та розпочне діяльність, де раніше знаходився користувач, щоб створити ілюзію, що програма ніколи не була вбита.
Рафаель С

@RaphaelC у вас є така документація? Здається, це абсолютно неправильно, оскільки Android забезпечує лише один контекст програми на кожен запуск кожного процесу.
HaydenKai

6

Це лише попередження. Не хвилюйся. Якщо ви хочете використовувати контекст програми, ви можете зберегти його в класі "singleton", який використовується для збереження всього класу singleton у вашому проекті.


2

У вашому випадку немає сенсу мати його як статичне поле, але я не думаю, що це погано у всіх випадках. Якщо ви зараз робите, що ви робите, ви можете мати статичне поле, яке має контекст, і обнулити його пізніше. Я створюю статичний екземпляр для мого основного класу моделі, який має контекст всередині, його контекст програми не контекст діяльності, а також у мене є поле статичного екземпляра класу, що містить Activity, яке я нулюю при знищенні. Я не бачу, що у мене витік пам'яті. Тож якщо якийсь розумний хлопець вважає, що я помиляюся, сміливо коментуйте ...

Також миттєвий запуск працює тут чудово ...


Я не думаю, що ви помиляєтеся в принципі, але вам слід бути надто обережними, щоб діяльність, про яку ви говорите, містила максимум один окремий екземпляр у будь-який момент часу, перш ніж вона зможе використовувати статичні поля. Якщо ваш додаток закінчується кількома задніми стеками, оскільки його можна запустити з різних місць (сповіщення, глибокі посилання, ...), все піде не так, якщо ви не використаєте в маніфесті якийсь прапор, як singleInstance. Тому завжди легше уникати статичних полів із Activity.
BladeCoder

android: launchMode = "singleTask" повинно бути достатньо, тому я переходжу до цього, я використовував singleTop, але не знав його недостатньо, оскільки я хочу завжди лише окремі екземпляри моїх основних дій, саме так розробляються мої програми.
Ренетик

2
"singleTask" гарантує лише один екземпляр для кожного завдання. Якщо у вашому додатку є кілька точок входу, таких як глибокі зв’язки або запуск із сповіщення, у вас може з’явитися кілька завдань.
BladeCoder

1

Як правило, уникайте, щоб поля контексту були визначені як статичні. Саме попередження пояснює, чому: це витік пам'яті. Порушення миттєвого пробігу, можливо, не є найбільшою проблемою на планеті.

Зараз є два сценарії, коли ви отримаєте це попередження. Для прикладу (найбільш очевидного):

public static Context ctx;

А ще є трохи хитріший, де контекст загортається в клас:

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

І цей клас десь визначається як статичний:

public static Example example;

І ви отримаєте попередження.

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

І рішення попередження просте: не розміщуйте поле статично. У вашому випадку передайте контекст як екземпляр методу. Для класів, де здійснюється кілька викликів контексту, використовуйте конструктор, щоб передати контекст (або Activity з цього приводу) класу.

Зверніть увагу, що це попередження, а не помилка. Якщо вам з якоїсь причини потрібен статичний контекст, ви можете це зробити. Хоча ви створюєте витік пам'яті, коли ви це робите.


як ми можемо це зробити, не створюючи витоку пам'яті?
isJulian00

1
Ви не можете. Якщо вам конче потрібно передати контекст, ви можете заглянути в автобус подій
Зої

добре, це була проблема, яка у мене була, якщо б ви могли, будь ласка, поглянути на це, можливо, це інший спосіб зробити це, до речі, метод повинен бути статичним, тому що я викликаю його з коду c ++ stackoverflow.com/questions/54683863/…
isJulian00

0

Якщо ви переконаєтесь, що це Контекст програми. Це має значення. Додайте це

@SuppressLint("StaticFieldLeak")

1
Я б не рекомендував робити це в будь-якому випадку. Якщо вам потрібен контекст, ви можете використовувати метод requireContext (), якщо ви використовуєте бібліотеки AndroidX. Або ви можете передати контекст безпосередньо тому методу, який йому потрібен. Або ви навіть можете просто отримати посилання на клас програми, але я б краще не рекомендував використовувати таку пропозицію SuppressLint.
Олександр Нос

0

Використовуйте 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;
}

Тепер ви можете отримати доступ до Context like

  if (context.get() instanceof MainActivity) {
            ((MainActivity) context.get()).startActivityForResult(pickIntent, CODE_REQUEST_PICK_APPWIDGET);
        }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.