何时调用Activity上下文或应用程序上下文?

280

目前有很多关于这两个上下文的帖子.. 但我仍然不是很理解

据我目前所了解的: 每个都是其类的实例,这意味着一些程序员建议您尽可能使用 this.getApplicationContext() ,以便不会"泄漏"任何内存。这是因为另一个 this(获取活动实例上下文)指向正在被销毁的Activity,每次用户旋转手机或离开应用程序等。显然,垃圾收集器(GC)无法捕获它,因此会使用太多内存。

但请问有没有人能提供一些非常好的编码示例,其中使用this(获取当前Activity实例的上下文)将是正确的选择,而应用程序上下文将是无用/错误的?

7个回答

424

getApplicationContext() 几乎总是错误的。Ms. Hackborn(和其他人)非常明确地指出,只有在你知道为什么要使用 getApplicationContext() 并且只有在需要使用 getApplicationContext() 时才使用它。

坦率地说,“一些程序员”使用 getApplicationContext()(或者较少用的 getBaseContext()),因为他们的 Java 经验有限。他们实现了一个内部类(例如在 Activity 中的 ButtonOnClickListener),并且需要一个 Context。他们使用 getApplicationContext()getBaseContext() 来获取 Context 对象,而不是使用 MyActivity.this 来获取外部类的 this

只有当你知道需要一个 Context 用于某些可能比你拥有的其他 Context 存活得更长的情况时,才会使用 getApplicationContext()。这种情况包括:

  • 如果您需要与具有全局作用域的Context绑定某些内容,可以使用getApplicationContext()。例如,在WakefulIntentService中,我使用getApplicationContext()来创建静态WakeLock服务。由于该WakeLock是静态的,并且我需要一个Context来获取PowerManager以创建它,因此最安全的方法是使用getApplicationContext().

  • 如果你想通过onRetainNonConfigurationInstance()从一个Activity实例传递handle到binding,那么绑定Activity时请使用getApplicationContext()。Android会通过这些ServiceConnections追踪绑定并持有创建绑定的Contexts的引用。如果您从Activity进行绑定,则新的Activity实例将引用旧的Activity实例拥有的ServiceConnection,而旧的Activity无法被垃圾回收。

一些开发者使用自定义的Application子类来获取他们自己的全局数据,这些数据可以通过getApplicationContext()方法获取。这当然是可行的。我更喜欢使用静态数据成员,因为你只能拥有一个自定义Application对象。我曾经构建了一个应用程序,使用自定义Application对象,但发现它很痛苦。Hackborn女士也同意这个观点
以下是不要在任何地方都使用getApplicationContext()的原因:
  • 它不是完整的Context,不支持Activity所支持的所有功能。您将尝试使用此Context执行的各种操作将失败,大多与GUI相关

  • 如果getApplicationContext()返回的Context保留了由您在其上调用的某些内容而未清理,则可能会创建内存泄漏。对于Activity,如果它保留了某些内容,一旦Activity被垃圾回收,其他所有内容也将被清除。Application对象将在进程的生命周期内保留。


27
@Norfeldt:FYI,你评论中的链接指向了这个答案。 - CommonsWare
2
谢谢!这是链接:https://dev59.com/Rm025IYBdhLWcg3wyZIn 它描述了我担心使用这个会出现的内存泄漏问题。 - Norfeldt
6
你引用的后半部分几乎是正确的,更好的措辞是“不要将Activity上下文传递给比Activity生命周期更长的东西,比如静态数据成员”。但是,在特定情况下只有在你确切知道为什么需要时,才应该使用getApplicationContext()。如果要填充一个布局,请使用Activity。如果要绑定到服务,需要该绑定在配置更改后仍然存在,请使用getApplicationContext(),以便该绑定与Activity实例无关。 - CommonsWare
2
@CommonsWare:为什么getApplicationContext()几乎总是错误的?我怎样才能看到在http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html中,为了避免与上下文相关的内存泄漏,我们应该使用context-application而不是context-activity。 - Sever
7
@Sever: 我在我的回答中提到了这一点。Dave Smith也有一篇很好的博客文章涵盖上下文:http://www.doubleencore.com/2013/06/context/ 他的总结段落如下:“在大多数情况下,使用直接可用于所在组件的上下文。只要该引用不超出组件生命周期,就可以安全地持有对它的引用。一旦你需要保存一个从你的 Activity 或 Service 存在之外的对象中获取的上下文引用,即使是暂时的,就切换到应用程序上下文保存该引用。” - CommonsWare
显示剩余10条评论

51

我认为SDK网站上有许多文档不够详细,这就是其中之一。我要提出的观点是,似乎最好默认使用应用程序上下文,只有在确实需要时才使用活动上下文。我曾经看到过唯一需要活动上下文的地方是进度对话框。SBERG412声称你必须使用活动上下文来显示toast消息,然而Android文档清楚地展示了使用应用程序上下文。由于这个Google的例子,我一直使用应用程序上下文来显示toast。如果这样做是错误的话,那么Google在这里犯了错误。

以下是更多需要考虑和评审的信息:

对于toast消息,Google Dev Guide使用应用程序上下文,并明确指出要使用它: Toast Notifications

在Dev guide的对话框部分中,您可以看到AlertDialog.Builder使用应用程序上下文,然后进度条使用活动上下文。这并没有被Google解释清楚。 Dialogs

当您想要处理配置更改(例如方向更改)并且希望保留需要上下文(如视图)的对象时,使用应用程序上下文似乎是一个不错的理由。如果您在这里看: Run Time Changes

使用活动上下文存在一些潜在的内存泄漏问题,这可以通过使用应用程序上下文并保留要保留的视图(至少是我理解的)来避免。在我编写的应用程序中,我打算使用应用程序上下文,因为我试图在方向更改时保存一些视图和其他内容,同时仍希望在方向更改时销毁并重建活动。因此,我必须使用应用程序上下文而不会导致内存泄漏(请参见避免内存泄漏)。对我来说,使用应用程序上下文而不是活动上下文有很多好处,而且在我看来,你几乎总是会使用它而不是活动上下文。这就是我阅读过的许多Android书籍和谷歌示例所做的。

谷歌文档确实让人感觉在大多数情况下使用应用程序上下文是完全可以的,并且在他们的示例中似乎比使用活动上下文更常见(至少是我看到的示例)。如果使用应用程序上下文真的存在如此严重的问题,那么谷歌真的需要更加强调这一点。他们需要明确说明,并重新编写一些示例。我不会完全归咎于经验不足的开发人员,因为权威(谷歌)似乎真的认为使用应用程序上下文是没有问题的。


5
我完全同意。CommonsWare的回答有点让我意外。我很高兴能找到这个问题,因为在Google文档中建议使用getApplicationContext会非常危险。 - SAS

47

我使用这张表格作为使用不同类型的上下文(例如应用程序上下文(即:getApplicationContext())和活动上下文,以及BroadcastReceiver 上下文)指南:

图片描述

所有功劳归原作者此处查看更多信息。


12

应该使用哪种上下文?

有两种类型的上下文:

  1. 应用程序上下文与应用程序相关,始终在应用程序的整个生命周期内保持相同--它不会改变。因此,如果您正在使用Toast,则可以使用应用程序上下文或活动上下文(两者都可以),因为Toast可以从应用程序中的任何位置显示,而不附加到特定窗口。但是有许多例外情况,其中一个例外情况是当您需要使用或传递活动上下文时。

  2. 活动上下文与活动相关联,如果活动被销毁,则其也会被销毁--可能会有多个活动(很可能)与单个应用程序相关联。有时您绝对需要活动上下文处理。例如,如果要启动新活动,则需要在其Intent中使用活动上下文,以便新启动的活动与当前活动在活动堆栈方面相连。但是,您也可以使用应用程序的上下文来启动新活动,但然后您需要在意图中设置标志Intent.FLAG_ACTIVITY_NEW_TASK将其视为新任务。

让我们考虑一些情况:

  • MainActivity.this 指的是扩展了 Activity 类的 MainActivity 上下文,但该基类(activity)也继承了 Context 类,因此可以用于提供活动上下文。

  • getBaseContext() 提供活动上下文。

  • getApplication() 提供应用程序上下文。

  • getApplicationContext() 也提供应用程序上下文。

更多信息请查看 link


如果需要在应用程序中显示AlertDialog,例如异步进程显示结果的情况怎么办?一个例子是:用户点击下载,这会触发downloadmanager的下载请求,当接收到完成信号时,它应该显示一个对话框,比如“您想要如何处理此下载?”我的(hack)解决方案是将最近的Activity保存在一个static Application类中,并在下载完成时请求当前的Activity。然而,我怀疑这不是正确的实现方式。简而言之,如何在应用程序中的任何位置显示AlertDialog? - CybeX
@KGCybeX 如果你想在应用程序下载完成后在任何地方显示任何内容,你应该手动在你的活动上注册一个广播接收器,监听你的下载服务将要广播的特定消息,并在接收到消息时执行你想要的操作,或者直接将你的活动附加到该服务上。 - ExiRouS

7
我在想,为什么不使用Application Context来支持每个操作呢?最终它降低了内存泄漏和getContext()或getActivity()的缺失null检查的概率(在使用注入的应用程序上下文或从应用程序获得的静态方法时)。像Ms. Hackborn建议只在需要时才使用Application Context这样的声明,在没有解释的情况下对我来说并不令人信服。但是似乎我已经找到了一个答案:

我发现在某些Android版本/设备组合上存在问题,不遵循这些规则。例如,如果我有一个BroadcastReceiver传递了一个Context,并且我将该Context转换为Application Context,然后尝试在Application Context上调用registerReceiver(),这种情况在许多实例中都可以正常工作,但也有许多实例因为ReceiverCallNotAllowedException而崩溃。这些崩溃发生在API 15到22的各种Android版本上。 https://possiblemobile.com/2013/06/context/#comment-2443283153

因为下表中Application Context支持的所有操作不能保证在所有Android设备上都能正常工作! 在此输入图片描述


4

应该使用Activity上下文而不是Application上下文的两个主要例子是:当显示Toast消息或内置对话框消息时,使用Application上下文会导致异常:

ProgressDialog.show(this, ....);

或者

Toast t = Toast.makeText(this,....);

这两个需要来自Activity上下文的信息,而应用程序上下文中没有提供。


6
嗯... 你测试的是哪个Android操作系统版本?我在4.4.4上进行了测试,效果很好。此外,正如@Andi Jay所提到的,官方的Android开发者文档在示例代码中使用了应用程序上下文。http://developer.android.com/guide/topics/ui/notifiers/toasts.html#Basics - 김준호
1
@中文名字,是的,它可能会工作,但在那个应用程序的未来某个时刻,它也会崩溃。我遇到过几次这种情况。 - Ojonugwa Jude Ochalifu
1
当我在Toast中使用Activity上下文时,会导致内存泄漏! - Jemshit Iskenderov

3

应用程序上下文仅在应用程序存活期间存在,不依赖于Activity的生命周期,但是上下文可以使对象长时间存在。如果您使用的对象是临时的,那么可以使用应用程序上下文,而Activity上下文则完全相反。


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