使用getApplication()作为上下文时,对话框抛出“无法添加窗口-令牌为空不是应用程序”的错误。

695

我的Activity试图创建一个需要Context作为参数的AlertDialog。如果我使用以下代码,则可以正常工作:

AlertDialog.Builder builder = new AlertDialog.Builder(this);
然而,我对使用"this"作为上下文感到不安,因为即使在像屏幕旋转这样简单的事情中,Activity被销毁和重新创建时可能会出现内存泄漏的风险。来自Android开发者博客上一篇相关文章的说法如下:

有两种简单的方法可以避免上下文相关的内存泄漏。最明显的方法是避免将上下文从其自身的范围之外逃逸。上面的示例显示了静态引用的情况,但是内部类及其对外部类的隐式引用同样危险。第二个解决方案是使用Application上下文。此上下文将与应用程序一起存在,并且不依赖于活动生命周期。如果您计划保留需要上下文的长期对象,请记住应用程序对象。您可以通过调用Context.getApplicationContext()或Activity.getApplication()轻松获取它。

但是,对于AlertDialog(),无论是getApplicationContext()还是getApplication()都不可接受作为上下文,因为它会抛出异常:

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

相关参考:123等。
那么,既然我们被官方建议使用Activity.getApplication(),但实际上它没有按照广告所说的正常工作,那么这真的应该被认为是一个“bug”吗?
Jim

第一项建议使用getApplication的参考资料:http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html - gymshoe
其他参考资料:https://dev59.com/GHI_5IYBdhLWcg3wBuRs - gymshoe
1
请参考以下内容:https://dev59.com/aXE85IYBdhLWcg3wvGKm - gymshoe
http://groups.google.com/group/android-developers/browse_thread/thread/7a648edddccf6f7d - gymshoe
显示剩余2条评论
28个回答

1404

使用ActivityName.this代替getApplicationContext()


67
太好了!就这个问题来评论一下...有时候你可能需要将"this"全局存储,以便在侦听器实现的方法内访问它,而该方法拥有自己的'this'。在这种情况下,你需要在全局定义"Context context",然后在onCreate中设置"context = this",然后引用"context"。希望这也能派上用场。 - Steven L
8
实际上,由于Listener类通常是匿名内部类,我倾向于只是这样做:final Context ctx = this;,然后就可以开始了;) - Alex
28
为了做你说的那件事,你应该使用ExternalClassName.this来明确引用外部类的"this"。 - Artem Russakovskii
11
如果你在回调中使用“this”,并且在回调被调用之前离开了活动,那么这不会泄漏吗?至少这是Android在logcat中抱怨的。 - Artem Russakovskii
6
我建议不要采用@StevenLs的方法,因为除非你在onDestroy中清除静态引用,否则很容易泄漏该活动的内存 - Artem是正确的。StevenLs的方法源于对Java工作原理的不理解。 - Dori
显示剩余17条评论

201

使用this对我无效,但MyActivityName.this有效。


65
如果你在内部类中使用 this,就会发生这种情况。如果你想引用外部类的实例,你必须显式地指明,就像使用 OuterClass.this 一样。只使用 this 将始终引用最内部类的实例。 - kaka

64

你可以继续使用getApplicationContext(),但在使用之前,你应该加上这个标志:dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT),然后错误就不会出现了。

在你的清单文件中添加以下权限:

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

1
我收到了“无法添加窗口android.view.ViewRootImpl$W@426ce670 - 拒绝此窗口类型的权限”的错误信息。 - Ram G.
添加权限:<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> - codezjx
3
似乎在Android API 23及其之后的版本中无法启用此权限。https://code.google.com/p/android-developer-preview/issues/detail?id=3123 - roy zhang
1
您可以在API 23及以上版本中使用它,但是您需要提示用户:startActivityForResult(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName())), OVERLAY_PERMISSION_REQ_CODE); 然而,是否应该使用它是另一回事... - Ben Neill
2
当您在服务内显示进度对话框时,这非常有用。 - Anand Savjani
1
这也适用于Presentation,自从我将应用程序上下文作为上下文提供以来,我的辅助显示器不再闪烁。 - Emre A

42
您已经正确地指出了问题,即“...对于AlertDialog(),既不接受getApplicationContext()也不接受getApplication()作为上下文,因为它会抛出异常:'无法添加窗口-令牌为空不是应用程序'”。
要创建对话框,您需要一个Activity ContextService Context,而不是Application Context(getApplicationContext()和getApplication()都返回Application Context)。
以下是获取Activity Context的方法:
(1)在Activity或Service中: AlertDialog.Builder builder = new AlertDialog.Builder(this); (2)在Fragment中: AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 内存泄漏不是与"this"引用本身相关的问题,"this"是对象对其自身的引用(即引用用于存储对象数据的实际分配内存)。它发生在任何分配的内存中,当垃圾收集器(GC)无法在分配的内存超过其有用寿命后释放它时。
大多数情况下,当变量超出作用域时,内存将被垃圾回收器回收。然而,如果一个变量持有一个对象的引用,比如说“x”,即使对象已经超出其有用寿命,这个引用仍然存在,就会发生内存泄漏。只要“x”仍然持有对该内存的引用,垃圾回收器就不会释放该内存。有时候,由于对分配的内存存在一条引用链,内存泄漏并不明显。在这种情况下,垃圾回收器直到所有对该内存的引用都被移除后才会释放该内存。
为了防止内存泄漏,请检查您的代码中是否存在逻辑错误,导致分配的内存被“this”(或其他引用)无限期地引用。记得检查引用链。以下是一些可用于帮助您分析内存使用情况并找到那些讨厌的内存泄漏的工具:

对于一个 Activity,您也可以使用 ActivityName.this,其中 ActivityName 是(显然)您的活动名称(例如 MainActivity)。 - Luis Cabrera Benito

34

你的对话框不应该是“需要上下文的长期存在的对象”。文档很令人困惑。基本上,如果你像这样做:

static Dialog sDialog;
(note the static)
然后在某个活动中,注意这里有 static
 sDialog = new Dialog(this);

在旋转或类似的操作中销毁Activity时,您很可能会泄漏原始Activity。(除非您在onDestroy中进行清理,但在这种情况下,您可能不会将Dialog对象设置为静态)

对于某些数据结构,将它们设置为基于应用程序上下文和静态的可能是有意义的,但通常不适用于与UI相关的事物,例如对话框。因此,像这样做:

Dialog mDialog;

...

mDialog = new Dialog(this);

这是正确的代码,并且不应该泄露活动(activity)的内存,因为mDialog会随着活动一起被释放,因为它不是静态的。


我正在从异步任务中调用它,这对我起作用了,谢谢伙计。 - MemLeak
我的对话框是静态的,一旦我去掉了静态声明,它就可以工作了。 - Ceddy Muhoza

28

Activity 中,只需使用:

MyActivity.this

Fragment:中:

getActivity();

这个在我的Activity中解决了问题。谢谢。 - Werner

26

我必须通过自定义适配器的构造函数将上下文发送到一个片段中,并且在使用getApplicationContext()时遇到了问题。 我用如下方式解决:

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


4
这对我也起作用了,我将其传递给我正在使用的外部AsyncTask的构造函数(它显示一个进度对话框)。 - Rohan Kandwal
3
这是更复杂任务的真正答案 :) - teejay
1
我同意 @teejay 的观点。 - Erdi İzgi

22

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

您应该传递 this@YourActivity 而不是 applicationContextbaseContext


20

Activity中,点击按钮后会显示一个对话框

Dialog dialog = new Dialog(MyActivity.this);

对我有用。


19

只需按照以下步骤操作:

对于Java用户

如果您正在使用Activity --> AlertDialog.Builder builder = new AlertDialog.Builder(this);

或者

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

如果您正在使用Fragment --> AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());

对于Kotlin用户

如果您正在使用Activity --> val builder = AlertDialog.Builder(this)

或者

val builder = AlertDialog.Builder(this@your_activity)

如果您正在使用Fragment --> val builder = AlertDialog.Builder(requireActivity())


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接