У діалоговому вікні "Неможливо додати вікно - токен нуль не для програми" з getApplication () як контекст


665

Моя активність намагається створити AlertDialog, для якого потрібен контекст як параметр. Це працює, як очікувалося, якщо я використовую:

AlertDialog.Builder builder = new AlertDialog.Builder(this);

Однак я намагаюся використовувати "це" як контекст через потенціал витоку пам'яті, коли діяльність знищується та відтворюється навіть під час чогось простого, наприклад, обертання екрана. З пов’язаної публікації в блозі розробника Android :

Є два простих способи уникнути витоків пам'яті, що пов'язані з контекстом. Найбільш очевидний - уникати виходу з контексту поза його власним розмахом. Наведений вище приклад показав випадок статичного посилання, але внутрішні класи та їх неявне посилання на зовнішній клас можуть бути однаково небезпечними. Друге рішення - використовувати контекст програми. Цей контекст буде жити до тих пір, поки ваша програма жива і не залежить від життєвого циклу діяльності. Якщо ви плануєте зберігати довгоживучі об’єкти, яким потрібен контекст, запам’ятайте об’єкт програми. Ви можете легко отримати його, зателефонувавши Context.getApplicationContext () або Activity.getApplication ().

Але для " AlertDialog()ні", getApplicationContext()ні " getApplication()є прийнятним як контекст, оскільки він кидає виняток:

"Неможливо додати вікно - нулевий маркер не для програми"

за довідками: 1 , 2 , 3 тощо.

Отже, чи слід це насправді вважати «помилкою», оскільки нам офіційно рекомендується користуватися, Activity.getApplication()але він не працює як рекламований?

Джим


посилання на перший пункт, де R.Guy радить використовувати getApplication: android-developers.blogspot.com/2009/01/…
gymshoe

інші посилання: stackoverflow.com/questions/1561803 / ...
gymshoe

1
Інші посилання: stackoverflow.com/questions/2634991 / ...
gymshoe


Відповіді:


1354

Замість цього getApplicationContext()просто використовуйте ActivityName.this.


67
Чудово! Просто прокоментуйте це .. інколи вам може знадобитися зберігати "це" у всьому світі (наприклад), щоб отримати доступ до нього в реалізованому слухачем методі, у якого є власне "це". У цьому випадку слід визначити "контекст контексту" у всьому світі, а потім у onCreate встановити "контекст = це", а потім посилатися на "контекст". Сподіваюся, що теж стане в нагоді.
Стівен Л

8
Насправді, оскільки Listenerзаняття часто є анонімними-внутрішніми, я схильний просто робити final Context ctx = this;і я відсутній ;)
Alex

28
@StevenL Для того, щоб робити те, що ви говорите, вам слід використовувати ExternalClassName.this, щоб явно посилатися на "це" зовнішнього класу.
Артем Русаковський

11
Не вдалося б вимкнути це "це", якщо діалогове вікно буде використано для зворотного дзвінка, і ви покинете активність до виклику зворотного дзвінка? Принаймні, на це Android, схоже, скаржиться в logcat.
Артем Руссаковський

6
Я б не радив підхід @StevenLs, оскільки ви можете легко просочити пам'ять про цю діяльність, якщо ви не пам’ятаєте, щоб очистити статичну посилання в onDestroy - Артем правильний. Підхід StevenLs похований від нерозуміння того, як працює Java
Дорі

192

Використання thisне працювало для мене, але все- MyActivityName.thisтаки робило. Сподіваюся, що це допомагає всім, хто не зміг дістатися thisдо роботи.


63
Ось що відбувається, коли ви використовуєте thisзсередини внутрішнього класу. Якщо ви хочете посилатися на екземпляр зовнішнього класу, вам слід вказати так, як це робиться з OuterClass.this. Просто використовуючи thisзавжди посилання на екземпляр самого внутрішнього класу.
кака

60

Ви можете продовжувати використовувати getApplicationContext(), але перед використанням слід додати цей прапор:, dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)і помилка не з’явиться.

Додайте до свого маніфесту такий дозвіл:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

1
Я не можу додати вікно android.view.ViewRootImpl$W@426ce670 - дозвіл відхилено для цього типу вікна
Рам Г.

додати дозвіл: <використання-дозволу android: name = "android.permission.SYSTEM_ALERT_WINDOW" />
codezjx

3
Схоже, ви не можете ввімкнути цей дозвіл в API 23 code.google.com/p/android-developer-preview/issues/…
roy zhang

1
Ви можете використовувати його для API 23, однак потрібно сповістити користувача: startActivityForResult (новий намір (Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse ("пакет:" + getPackageName ())), OVERLAY_PERMISSION_REQ_CODE); однак чи варто вам це використовувати - інша справа ...
Бен Ніл

2
Це корисно, коли ви показуєте діалог прогрес у службі
Anand Savjani

37

Ви правильно визначили проблему, сказавши "... для AlertDialog () ні getApplicationContext (), ні getApplication () не є прийнятним як контекст, оскільки він викидає виняток:" Неможливо додати вікно - токен null не для додаток ""

Для створення діалогового вікна вам потрібен контекст діяльності або контекст служби , а не контекст програми (і getApplicationContext (), і getApplication () повертають контекст програми).

Ось як ви отримуєте контекст діяльності :

(1) У діяльності чи службі:

AlertDialog.Builder builder = new AlertDialog.Builder(this);

(2) У фрагменті: AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

Витік пам’яті не є проблемою, яка є властивою посиланню "це", що є посиланням об'єкта на себе (тобто посилання на фактично виділену пам'ять для зберігання даних об'єкта). Це трапляється з будь-якою виділеною пам'яттю, для якої збирач сміття (GC) не в змозі звільнитись після того, як виділена пам'ять пережила свій корисний термін експлуатації.

Більшу частину часу, коли змінна не виходить за межі, GC буде відновлено GC. Однак витік пам'яті може статися тоді, коли посилання на об'єкт, який утримується змінною, скажімо, "х", зберігається навіть після того, як об'єкт пережив свою корисну тривалість життя. Отже, виділена пам'ять втрачається до тих пір, поки "x" несе посилання на неї, оскільки GC не буде звільняти пам'ять, поки ця пам'ять все ще посилається. Іноді витоки пам'яті не очевидні через ланцюг посилань на виділену пам'ять. У такому випадку GC не звільнить пам'ять, поки не будуть видалені всі посилання на цю пам'ять.

Щоб запобігти витоку пам’яті, перевірте свій код на предмет логічних помилок, які спричиняють посилання на виділену пам’ять нескінченно “цим” (або іншими посиланнями). Не забудьте перевірити також наявність посилань на ланцюги. Ось декілька інструментів, якими ви можете скористатися, щоб проаналізувати використання пам’яті та знайти ті неприємні витоки пам’яті:


Для діяльності ви також можете використовувати ActivityName.this, де ActivityName (очевидно) ім'я вашої діяльності (наприклад, MainActivity)
Луїс Кабрера Беніто

34

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

static Dialog sDialog;

(зверніть увагу на статичну )

Тоді в діяльності десь ти займався

 sDialog = new Dialog(this);

Ви, ймовірно, просочилися початкові дії під час обертання або подібне, що знищило б діяльність. (Якщо ви не очистите в onDestroy, але в цьому випадку ви, ймовірно, не зробили б об'єкт Dialog статичним)

Для деяких структур даних було б доцільно зробити їх статичними та виходячи з контексту програми, але, як правило, не для речей, що стосуються інтерфейсу, наприклад діалогів. Тож щось подібне:

Dialog mDialog;

...

mDialog = new Dialog(this);

Це добре і не повинно протікати активність, оскільки mDialog буде звільнений від активності, оскільки він не є статичним.


Я називаю це з асинтакта, це працювало для мене, Thx товариш
MemLeak

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

25

Мені довелося надіслати свій контекст через конструктор на користувальницький адаптер, відображений у фрагменті, і ця проблема була з getApplicationContext (). Я вирішив це за допомогою:

this.getActivity().getWindow().getContext()у onCreateвідкликанні фрагментів .


4
Це теж працювало для мене, я передав його конструктору зовнішнього AsyncTask, який я використовую (Він показує діалог прогресу).
Рохан Кандвал

3
це РЕАЛЬНА відповідь на більш складні завдання :)
teejay

1
Я згоден з @teejay
Ерді Ізгі

23

у діяльності просто використовувати:

MyActivity.this

у фрагменті:

getActivity();

Це зафіксувало це для мене в моїй діяльності. Спасибі
Вернер

20

В Activityна клацанням кнопки відображається діалогове вікно

Dialog dialog = new Dialog(MyActivity.this);

Працювали для мене.


19

***** версія котлін *****

Ви повинні пройти this@YourActivityзамість applicationContextабоbaseContext


18

Маленький хак: ви можете запобігти знищенню вашої діяльності з допомогою ГХ (ви не повинні робити це, але це може допомогти в деяких ситуаціях Не забудьте встановити. contextForDialogЩоб , nullколи він більше не потрібен):

public class PostActivity extends Activity  {
    ...
    private Context contextForDialog = null;
    ...
    public void onCreate(Bundle savedInstanceState) {
        ...
        contextForDialog = this;
    }
    ...
    private void showAnimatedDialog() {
        mSpinner = new Dialog(contextForDialog);
        mSpinner.setContentView(new MySpinner(contextForDialog));
        mSpinner.show();
    }
    ...
}

@MurtuzaKabul Це працює, тому що це == PostActivity, який успадковує від Activity->, який успадковує від контексту, тому, переходячи до діалогу ваш контекст, ви насправді передаєте активність
Elad Gelman

13

Якщо ви використовуєте фрагмент і використовуєте повідомлення AlertDialog / Toast, тоді використовуйте getActivity () у контекстному параметрі.

подобається це

ProgressDialog pdialog;
pdialog = new ProgressDialog(getActivity());
pdialog.setCancelable(true);
pdialog.setMessage("Loading ....");
pdialog.show();

12

Просто використовуйте наступне:

ДЛЯ КОРИСТУВАЧІВ JAVA

Якщо ви використовуєте активність -> AlertDialog.Builder builder = new AlertDialog.Builder(this);

АБО

AlertDialog.Builder builder = new AlertDialog.Builder(your_activity.this);

Якщо ви використовуєте фрагмент -> AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

ДЛЯ КОРИСТУВАЧІВ КОТЛІНА

Якщо ви використовуєте активність -> val builder = AlertDialog.Builder(this)

АБО

val builder = AlertDialog.Builder(this@your_activity.this)

Якщо ви використовуєте фрагмент -> val builder = AlertDialog.Builder(activity!!)


9

додавання

dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);

і

"android.permission.SYSTEM_ALERT_WINDOW"/> в маніфесті

Це працює для мене зараз. Навіть закривши та відкривши додаток, мені на той час була помилка.


9

Я використовував ProgressDialogфрагмент і отримував цю помилку при переході getActivity().getApplicationContext()в якості параметра конструктора. Змінивши його наgetActivity().getBaseContext() також не спрацювала.

Рішення, яке працювало для мене, було прийняти getActivity(); тобто

progressDialog = new ProgressDialog(getActivity());


6

Використовуйте MyDialog md = new MyDialog(MyActivity.this.getParent());


6

Якщо ви перебуваєте за межами діяльності, вам потрібно використовувати у своїй функції "NameOfMyActivity.this" як активність, наприклад:

public static void showDialog(Activity activity) {
        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
        builder.setMessage("Your Message")
        .setPositiveButton("Yes", dialogClickListener)
        .setNegativeButton("No", dialogClickListener).show();
}


//Outside your Activity
showDialog(NameOfMyActivity.this);

5

Якщо ви використовуєте фрагмент і використовуєте AlertDialog / Toastповідомлення, використовуйте getActivity()в контексті параметр.

Працювали для мене.

Ура!


5

Спробуйте використовувати контекст діяльності, яка буде знаходитись під діалоговим вікном. Але будьте обережні, коли використовуєте ключове слово "це", оскільки воно не працюватиме кожен раз.

Forexample, якщо у вас є TabActivity як хост з двома вкладками, і кожна вкладка - це інша активність, і якщо ви спробуєте створити діалогове вікно з однієї з вкладок (діяльності), і якщо ви використовуєте "це", ви отримаєте виняток. Діалогове вікно справи повинно бути підключене до діяльності хоста, яка розміщує все та видиме. (ви можете сказати найбільш видимий контекст батьківської діяльності)

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


4

Для майбутніх читачів це має допомогти:

public void show() {
    if(mContext instanceof Activity) {
        Activity activity = (Activity) mContext;
        if (!activity.isFinishing() && !activity.isDestroyed()) {
            dialog.show();
        }
    }
}


2

Або інша можливість - створити діалог таким чином:

final Dialog dialog = new Dialog(new ContextThemeWrapper(
            this, R.style.MyThemeDialog));

2

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

Використовуйте runOnUiThread()в цьому випадку.


2

Спробуйте getParent()в місці аргументу контексту, як новий AlertDialog.Builder(getParent());Надію, він спрацює, він працював на мене.


1

Оглянувши API, ви можете передати діалогове вікно своєї активності або getActivity, якщо ви знаходитесь у фрагменті, а потім примусово очистити його методом return.dismiss (), щоб запобігти витоку.

Хоча це явно не вказано де-небудь, де я знаю, здається, вам передано діалогове вікно діалогу в OnClickHandlers саме для цього.


0

Якщо ваш діалог створюється на адаптері:

Передайте активність конструктору адаптера:

adapter = new MyAdapter(getActivity(),data);

Прийом на адаптер:

 public MyAdapter(Activity activity, List<Data> dataList){
       this.activity = activity;
    }

Тепер ви можете використовувати у своєму Builder

            AlertDialog.Builder alert = new AlertDialog.Builder(activity);

-1

Ось як я вирішив ту саму помилку для своєї програми:
Додаючи наступний рядок після створення діалогового вікна:

dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);  

Вам не потрібно буде набувати контексту. Це особливо корисно, якщо ви вискакуєте інше діалогове вікно над поточним спливаючим діалогом. Або коли не зручно отримати контекст.

Сподіваємось, це може допомогти вам у розробці додатків.

Девід


-1
android.support.v7.app.AlertDialog.Builder builder = new android.support.v7.app.AlertDialog.Builder(getWindow().getDecorView().getRootView().getContext());

builder.setTitle("Confirm");
builder.setMessage("Are you sure you want delete your old account?");

builder.setPositiveButton("YES", new DialogInterface.OnClickListener() {

    public void onClick(DialogInterface dialog, int which) {
        //Do nothing but close the dialog



        dialog.dismiss();

    }
});

builder.setNegativeButton("NO", new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int which) {

        //Do nothing
        dialog.dismiss();
    }
});

android.support.v7.app.AlertDialog alert = builder.create();
alert.show();
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.