关于android:Dialog投掷“无法添加窗口 – 令牌null不适用于应用程序”,其中getApplication()作为上下文

Dialog throwing "Unable to add window — token null is not for an application” with getApplication() as context

我的活动正在尝试创建一个需要上下文作为参数的AlertDialog。如果我使用:

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

但是,我对将"this"用作上下文持谨慎态度,因为当活动被破坏并重新创建时,即使在屏幕旋转之类的简单过程中,也可能发生内存泄漏。从Android开发者博客的相关帖子中:

There are two easy ways to avoid context-related memory leaks. The most obvious one is to avoid escaping the context outside of its own scope. The example above showed the case of a static reference but inner classes and their implicit reference to the outer class can be equally dangerous. The second solution is to use the Application context. This context will live as long as your application is alive and does not depend on the activities life cycle. If you plan on keeping long-lived objects that need a context, remember the application object. You can obtain it easily by calling Context.getApplicationContext() or Activity.getApplication().

但是对于AlertDialog()来说,getApplicationContext()getApplication()都不能作为上下文来接受,因为它抛出了例外:

"Unable to add window — token null is not for an application"

参考文献:1、2、3等。

所以,这真的应该被视为一个"bug",因为我们被正式建议使用Activity.getApplication(),但它并没有像广告中那样工作?

吉姆


ActivityName.this代替getApplicationContext()


使用this对我来说不起作用,但MyActivityName.this的确起作用。希望这能帮助那些不能让this工作的人。


您可以继续使用getApplicationContext(),但是在使用之前,您应该添加这个标志:dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT),错误不会显示。

将以下权限添加到清单:

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


您的对话框不应该是"需要上下文的长寿对象"。文档令人困惑。基本上,如果你做如下的事情:

1
static Dialog sDialog;

(注意静态)

然后在某个活动中

1
 sDialog = new Dialog(this);

您可能会在旋转期间泄漏原始活动,或者类似的情况会破坏活动。(除非在OnDestroy中清理,但在这种情况下,您可能不会使对话对象静态化)

对于某些数据结构来说,将它们设置为静态的并基于应用程序的上下文是有意义的,但通常不适用于与UI相关的事情,如对话框。所以像这样:

1
2
3
4
5
Dialog mDialog;

...

mDialog = new Dialog(this);

很好,不应该泄漏活动,因为MDialog将随活动一起释放,因为它不是静态的。


当你说"…对于alertDialog(),getApplicationContext()或getApplication()都不能作为上下文接受,因为它引发了异常:"无法添加窗口-标记NULL不适用于应用程序"。

要创建对话框,需要活动上下文或服务上下文,而不是应用程序上下文(getApplicationContext()和getApplication()都返回应用程序上下文)。

以下是获取活动上下文的方法:

(1)在活动或服务中:

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

(2)在片段中:AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

内存泄漏不是"this"引用固有的问题,它是对象对自身的引用(即,对存储对象数据的实际分配内存的引用)。在分配的内存超过其使用寿命后,垃圾收集器(GC)无法释放的任何已分配内存都会发生这种情况。

大多数情况下,当变量超出范围时,GC将回收内存。但是,当对变量(比如"x")持有的对象的引用持续存在时,甚至在对象超过其使用寿命之后,也可能发生内存泄漏。因此,只要"x"保持对它的引用,分配的内存就会丢失,因为只要该内存仍然被引用,GC就不会释放内存。有时,由于对分配的内存的一系列引用,内存泄漏并不明显。在这种情况下,在删除对该内存的所有引用之前,GC不会释放内存。

为了防止内存泄漏,请检查代码中是否存在逻辑错误,这些错误会导致分配的内存被"this"(或其他引用)无限期引用。记住还要检查链引用。以下是一些工具,您可以使用它们来帮助您分析内存使用情况并找到那些令人讨厌的内存泄漏:

  • Jrockit任务控制

  • J探测器

  • 你的工具包

  • AD4J


我必须通过一个在片段中显示的自定义适配器上的构造函数发送上下文,而getApplicationContext()存在这个问题。我用以下方法解决了它:

片段的onCreate回调中的this.getActivity().getWindow().getContext()


在活动中使用:

1
MyActivity.this

片段中:

1
getActivity();


Activity中,单击显示对话框的按钮

1
Dialog dialog = new Dialog(MyActivity.this);

为我工作。


小黑客:你可以防止GC破坏你的活动(你不应该这样做,但在某些情况下它可以帮助你)。当不再需要时,不要忘记将contextForDialog设置为null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
    }
    ...
}


如果使用片段并使用alertDialog/toast消息,则在上下文参数中使用getActivity()。

这样地

1
2
3
4
5
ProgressDialog pdialog;
pdialog = new ProgressDialog(getActivity());
pdialog.setCancelable(true);
pdialog.setMessage("Loading ....");
pdialog.show();

*****Kotlin版本*****

你应该通过this@YourActivity,而不是applicationContextbaseContext


我在片段中使用了ProgressDialog,在传递getActivity().getApplicationContext()作为构造函数参数时得到了这个错误。把它改成getActivity().getBaseContext()也没用。

对我有效的解决办法是通过getActivity();即

progressDialog = new ProgressDialog(getActivity());


添加

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

舱单中的"android.permission.SYSTEM_ALERT_WINDOW"/>

现在对我有效。在关闭并打开应用程序之后,给出了当时的错误。


使用MyDialog md = new MyDialog(MyActivity.this.getParent());


如果您不在活动范围内,则需要在函数"nameofmyactivity.this"中使用作为活动活动,例如:

1
2
3
4
5
6
7
8
9
10
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);

如果使用片段并使用AlertDialog / Toast消息,请在上下文参数中使用getActivity()

为我工作。

干杯!


尝试使用将在对话框下的活动的上下文。但是使用"this"关键字时要小心,因为它不会每次都起作用。

例如,如果您将TabActivity作为带有两个选项卡的主机,并且每个选项卡都是另一个活动,并且如果您尝试从其中一个选项卡(Activities)创建对话框,并且如果使用"this",则会出现异常,在这种情况下,对话框应该连接到承载所有可见内容的主机活动。(您可以说是最可见的父活动的上下文)

除了尝试,我没有从任何文档中找到这个信息。这是我的解决方案,没有很强的背景,如果有人更了解,请随时发表评论。


对于未来的读者,这将有助于:

1
2
3
4
5
6
7
8
public void show() {
    if(mContext instanceof Activity) {
        Activity activity = (Activity) mContext;
        if (!activity.isFinishing() && !activity.isDestroyed()) {
            dialog.show();
        }
    }
}

我认为,如果您试图显示一个不是主UI线程的线程的对话框,也可能发生这种情况。

在这种情况下使用runOnUiThread()


在新的AlertDialog.Builder(getParent());这样的背景的争论点上试试getParent(),希望它能起作用,对我也有作用。


在我的案例中:

1
this.getContext();

或者另一种可能性是创建如下对话框:

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

在查看API之后,如果您在一个片段中,可以通过Activity或GetActivity对话框,然后使用返回方法中的dialog.disclose()强制清理它以防止泄漏。

尽管我所知道的地方没有明确的说明,但似乎你只是为了这样做而被传回了Onclickhandlers中的对话。