在Android中获取“Context”的静态方法是什么?

1102

有没有一种方法可以在静态方法中获取当前的Context实例?

我正在寻找这种方法,因为我讨厌每次更改时都要保存“Context”实例。


64
不保存上下文不仅因为不方便而是一个好主意,更重要的是它可能导致巨大的内存泄漏! - Vikram Bodicherla
13
@VikramBodicherla 是的,但是下面的答案假定我们正在讨论应用程序上下文。因此,内存泄漏不是问题,但用户应该只在正确的上下文中使用这些解决方案。 - Tom
3
Android文档建议将上下文传递给单例的getter方法。http://developer.android.com/reference/android/app/Application.html - Marco Luglio
关于更喜欢使用单例和通过getInstance()传递上下文而不是静态上下文,请看一下,我在这里尝试解释我的理由,并支持工作代码:https://dev59.com/EHI-5IYBdhLWcg3wI0t9#38967293 - Alessio
我想知道SO是否应该成为搜索讨论或寻找问题解决方案的地方,因为要找到这个问题的解决方案,你必须阅读21个答案。 - David
显示剩余4条评论
21个回答

1412

按照以下步骤进行操作:

在Android清单文件中,声明如下内容。

<application android:name="com.xyz.MyApplication">

</application>

然后编写类:

public class MyApplication extends Application {

    private static Context context;

    public void onCreate() {
        super.onCreate();
        MyApplication.context = getApplicationContext();
    }

    public static Context getAppContext() {
        return MyApplication.context;
    }
}

现在无论何时都可以静态地调用MyApplication.getAppContext()来获取应用程序上下文。


94
这种方法有什么缺点吗?这似乎是作弊。(一个hack?) - jjnguy
9
或许我们应该将这个“静态上下文”变量声明为“易失性的(volatile)”? - Volodymyr Sorokin
15
@Tom 这不是静态数据成员最初是静态的情况。在给定的代码中,静态成员在onCreate()中被非静态地初始化。即使是静态初始化数据,在这种情况下也不够好,因为没有任何保证在某个其他类的静态初始化期间访问之前,给定类的静态初始化将发生。 - Melinda Green
17
根据 Application 的文档,onCreate() 在任何活动、服务或接收器被创建之前被调用(不包括内容提供者)。因此,只要您不尝试从内容提供者访问getAppContext(),这个解决方案就是安全的,@MelindaGreen。 - Magnus
9
在现代的“应该易如反掌”的精神中,确实应该有一个Context.getApplicationContext()或Application.getApplicationContext()的静态方法。这个对象是单例的,应该可以直接访问而无需复杂操作。在这个问题得到解决之前,在静态初始化程序或内容提供程序之外(即我的99%代码中),本答案提供了一个合理的解决方案。感谢评论者指出了这种技术不安全的特定情况。 - Ian Lovejoy
显示剩余21条评论

117

大多数希望方便地获取应用程序上下文的应用程序会创建自己的类,该类继承 android.app.Application

指南

您可以通过首先在项目中创建以下类来实现此目的:

import android.app.Application;
import android.content.Context;

public class App extends Application {

    private static Application sApplication;

    public static Application getApplication() {
        return sApplication;
    }

    public static Context getContext() {
        return getApplication().getApplicationContext();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sApplication = this;
    }
}

然后,在 AndroidManifest 文件中的 标签中指定你的类的名称:

<application 
    ...
    android:name="com.example.App" >
    ...
</application>

你可以使用以下代码在任何静态方法中检索应用程序上下文:

public static void someMethod() {
    Context context = App.getContext();
}

警告

在将类似上述内容添加到您的项目之前,您应该考虑文档中的建议:

通常不需要子类化 Application。 在大多数情况下,静态单例可以以更模块化的方式提供相同的功能。 如果您的单例需要全局上下文(例如注册广播接收器),则检索它的函数可以给出一个 Context,这个 Context 在首次构造单例时内部使用 Context.getApplicationContext()。


反射

还有另一种使用反射获取应用程序上下文的方法。在Android中,反射经常被轻视,我个人认为应该不要在生产中使用。

要检索应用程序上下文,我们必须调用一个隐藏类 (ActivityThread) 上的方法,该方法自 API 1 可用:

public static Application getApplicationUsingReflection() throws Exception {
    return (Application) Class.forName("android.app.ActivityThread")
            .getMethod("currentApplication").invoke(null, (Object[]) null);
}

还有一个隐藏的类(AppGlobals),它提供了一种静态的方式来获取应用程序上下文。它使用 ActivityThread 来获取上下文,因此以下方法与上面发布的方法实际上没有区别:

public static Application getApplicationUsingReflection() throws Exception {
    return (Application) Class.forName("android.app.AppGlobals")
            .getMethod("getInitialApplication").invoke(null, (Object[]) null);
} 

开心编码!



是的!喜欢最后一种方法!特别是因为我在Android Studio上有内部/隐藏的API,所以我甚至不需要使用反射,这似乎更安全(如果方法消失了,Android Studio会发出警告)。不知道为什么这不在SDK上。我认为这会让生活更轻松。 - Edw590
我刚发现上一个方法有问题...似乎并不总是返回上下文。getApplicationContext()和getBaseContext()可以工作,但当我调用ActivityThread.currentApplication()时,它返回null。我在一个声明为常量的Service内部的线程中调用了所有3个方法。这可能不是一种可靠的获取Context实例的方式。虽然我认为这种情况并没有发生很多次,但我认为这是唯一的一次。它发生在Android 4.0.3模拟器上,但在Lollipop 5.1的OnePlus X和Oreo 8.1的BV9500上不会发生。 - Edw590

68
假设我们正在讨论如何获取应用程序上下文,我按照@Rohit Ghatol的建议实现了它,扩展了Application。然后发生的情况是,并不能保证以这种方式检索的上下文始终为非null。当你需要它的时候,通常是因为你想初始化一个帮助程序或获取一个资源,你不能延迟时间;处理null情况将无法帮助你。所以我明白了,基本上我是在与Android架构作斗争,正如docs所述。

注意:通常不需要子类化Application。在大多数情况下,静态单例可以以更模块化的方式提供相同的功能。如果您的单例需要全局上下文(例如注册广播接收器),请在调用单例的getInstance()方法时包含Context.getApplicationContext()作为上下文参数。

并由Dianne Hackborn解释。
唯一的原因是在1.0版本之前的开发过程中,我们的一个应用程序开发人员一直在烦我需要有一个顶级应用程序对象,他们可以从中派生出更符合他们正常应用模型的东西,最终我屈服了。我将永远为自己的妥协感到遗憾。 :)
她还提出了解决这个问题的方案:
如果你想要一些可以在应用程序的不同部分共享的全局状态,请使用单例。[...]这更自然地引导您如何管理这些东西——按需初始化它们。
所以我摒弃了扩展Application的方法,并直接将上下文传递给单例帮助程序的getInstance(),同时在私有构造函数中保存对应用程序上下文的引用。
private static MyHelper instance;
private final Context mContext;    

private MyHelper(@NonNull Context context) {
    mContext = context.getApplicationContext();
}

public static MyHelper getInstance(@NonNull Context context) {
    synchronized(MyHelper.class) {
        if (instance == null) {
            instance = new MyHelper(context);
        }
        return instance;
    }
}

调用者将会向帮助程序传递本地上下文。
Helper.getInstance(myCtx).doSomething();

因此,为了正确回答这个问题:有一些静态访问应用程序上下文的方法,但它们都应该被避免,您应该优先将本地上下文传递给单例的getInstance()函数。

如果有人感兴趣,可以在fwd博客阅读更详细的版本。


1
@Alessio 这种方法会导致内存泄漏吗? - Phillip Kigenyi
3
@codephillip,我不明白你在说什么。单例引用从传递的活动中检索到的应用程序上下文,而不是宿主活动。这是合法的,也不会导致任何内存泄漏。这就是我撰写博客的主要观点。如果你真的认为你是正确的,请给我发送一个示例代码,让我能够重现你所说的内存泄漏,因为情况并非如此。 - Alessio
1
@MarkMcKenna,正如你所说,“它有一个类型为Context的私有字段mContext,它引用应用程序上下文”,因此对你来说很清楚mContext是引用应用程序上下文而不是任何上下文。在getApplicationContext()文档中,你可以看到:“一个上下文,其生命周期与当前上下文分开,与进程的生命周期而不是当前组件相关联”。这怎么会导致内存泄漏呢?只有当进程退出时,应用程序上下文才会被GC回收。 - Alessio
1
@Alessio 如果您认为对应用程序上下文的引用不算资源泄漏,那么您可以通过在 Application.onCreate() 中发布对 this 的静态引用来简化此过程,从而使接受的答案更好。 - Mark McKenna
2
对于任何怀疑使用getInstance单例模式+getApplicationContext不会导致内存泄漏的人,请检查LocalBroadcastManager源代码,它正是这样做的。 - Tim Autin
显示剩余19条评论

51

不,我认为没有。不幸的是,你只能从Activity或其他Context子类中调用getApplicationContext()。此外,这个问题有些相关。


8
文章的正确链接:http://android-developers.blogspot.co.il/2009/01/avoiding-memory-leaks.html - Tal Weiss

41

Kotlin之道:

清单文件:

<application android:name="MyApplication">

</application>

我的应用程序.kt

class MyApplication: Application() {

    override fun onCreate() {
        super.onCreate()
        instance = this
    }

    companion object {
        lateinit var instance: MyApplication
            private set
    }
}

您可以通过 MyApplication.instance 访问该属性。


40

以下是一种未记录在案的方式,可以在UI线程的任何地方获取一个Application(即一个Context)。它依赖于隐藏的静态方法ActivityThread.currentApplication()。它至少应该适用于Android 4.x。

try {
    final Class<?> activityThreadClass =
            Class.forName("android.app.ActivityThread");
    final Method method = activityThreadClass.getMethod("currentApplication");
    return (Application) method.invoke(null, (Object[]) null);
} catch (final ClassNotFoundException e) {
    // handle exception
} catch (final NoSuchMethodException e) {
    // handle exception
} catch (final IllegalArgumentException e) {
    // handle exception
} catch (final IllegalAccessException e) {
    // handle exception
} catch (final InvocationTargetException e) {
    // handle exception
}

请注意,调用此方法有可能返回null,例如当您在UI线程之外调用该方法或应用程序未绑定到该线程时。

如果您可以更改应用程序代码,仍然最好使用@RohitGhatol的解决方案。


1
我使用了KennyTM提供的方法,但有时该方法会返回null。是否有其他替代方法?例如,如果我们在这里得到了null,我们可以从其他地方检索上下文。在我的情况下,Application的onCreate()未被调用。但是上述方法在其之前被调用。请帮忙解决。 - AndroidGuy
这种情况并不总是适用,特别是当垃圾回收清理了所有与活动相关的内容时。 - AlexVPerl
那么getApplicationContext()或getBaseContext()如何返回Context实例呢?它们内部使用静态变量并直接返回,而不依赖于currentApplication()吗?从这两个函数获取Context的位置是静态的,这很酷。我以为currentApplication()是另外两个函数会去的地方,但似乎并不是这样。不知道它到底是什么。 - Edw590

33

这取决于您使用上下文的方式。我至少可以想到一种此方法的缺点:

如果您正在尝试使用 AlertDialog.Builder 创建一个 AlertDialog,那么 Application 上下文将无法正常工作。我认为您需要当前 Activity 的上下文...


6
没错。如果您使用应用程序上下文,可能会发现您的对话框被隐藏在前台活动下面。 - Nate
3
首先,+1是一种表示赞同或赞扬的表情。其后可能出现的错误提示是“无法启动活动ComponentInfo{com.samples/com.MyActivity}:android.view.WindowManager$BadTokenException:无法添加窗口——token为null不是应用程序”。 - Govind

16

Kotlin

open class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        mInstance = this
    }

    companion object {
        lateinit var mInstance: MyApp
        fun getContext(): Context? {
            return mInstance.applicationContext
        }
    }
}

并获取上下文,例如:

MyApp.mInstance
或者
MyApp.getContext()

12

如果你愿意使用RoboGuice,你可以将上下文注入到任何你想要的类中。以下是如何在RoboGuice 2.0 (beta 4 版本)中实现的示例:

import android.content.Context;
import android.os.Build;
import roboguice.inject.ContextSingleton;

import javax.inject.Inject;

@ContextSingleton
public class DataManager {
    @Inject
    public DataManager(Context context) {
            Properties properties = new Properties();
            properties.load(context.getResources().getAssets().open("data.properties"));
        } catch (IOException e) {
        }
    }
}

9

我曾经使用过这个:

ActivityThread at = ActivityThread.systemMain();
Context context = at.getSystemContext();

我曾经在获取系统服务时使用过这个有效的上下文,并且它很奏效。

但是,我只在framework/base的修改中使用过它,没有在Android应用程序中尝试过。

警告:您必须知道:在使用此上下文注册广播接收器时,它将无法正常工作,您将会看到以下错误消息:

java.lang.SecurityException:调用者包android未在ProcessRecord进程中运行


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