在任何地方都使用应用程序上下文?

498
在Android应用程序中,以下方法有什么问题吗:
public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}
并且将其传递到所有需要上下文的地方(例如SQLiteOpenHelper),而不会泄露上下文吗?

23
为了帮助其他人实现这个功能,您可以修改AndroidManifest.xml文件中的<application>节点,包括以下属性定义:android:name="MyApp"。 MyApp需要在与您的清单引用相同的包下。 - Matt Huggins
6
解决向SQLiteOpenHelper提供上下文的问题的绝妙方法!! 我已经实现了一个单例的“SQLiteManager”,但是卡在了“如何为单例获取上下文?” - Someone Somewhere
8
请注意,您正在通过其中一个超级接口返回应用程序,因此如果在MyApp中提供了额外的方法,则无法使用它们。您的getContext()应该返回MyApp类型,这样您就可以使用稍后添加的方法以及ContextWrapper和Context中的所有方法,但请勿更改原始含义。 - user4903
5
请参见http://goo.gl/uKcFn - 这是另一个与类似帖子相关的回复。最好在onCreate中设置静态变量而不是构造函数。 - AlikElzin-kilaka
1
@ChuongPham 如果框架已经导致你的应用崩溃,那么不会有任何访问空上下文的东西... - Kevin Krumwiede
显示剩余7条评论
10个回答

432

这种方法存在一些潜在的问题,但在许多情况下(如您的示例)它会很好地工作。

特别是当处理任何需要使用ContextGUI时要小心。例如,如果将应用程序上下文传递给LayoutInflater,则会收到异常。总的来说,您的方法非常好:在Activity中使用Activity的Context,在超出Activity范围的上下文中传递Application Context避免内存泄漏是一个好习惯。

此外,作为您模式的替代方案,您可以使用调用Context对象(例如Activity)上的getApplicationContext()的快捷方式来获取应用程序上下文。


22
谢谢您的启发性回答。我想我会将这种方法仅用于持久层(因为我不想使用内容提供程序)。想知道设计SQLiteOpenHelper时期望提供Context而不是从Application自身获取的动机是什么。 另外,我觉得您的书非常棒! - yanchenko
8
在使用 LayoutInflator 时,使用应用程序上下文对我很有效。可能在过去的三年中已经有所改变。 - Jacob Phillips
6
如果没有活动上下文使用LayoutInflator将会错过那个活动的样式。因此在某种意义上,它可以工作,但在另一方面就不行了。 - Mark
1
@MarkCarter 你的意思是使用Application Context会错过Activity的样式吗? - Jacob Phillips
1
@JacobPhillips 是的,应用程序上下文不能具有样式,因为每个活动可能以不同的方式进行样式设置。 - Mark
显示剩余2条评论

31

根据我的经验,这种方法通常不是必要的。如果你需要上下文信息,通常可以通过调用View.getContext()并使用获取到的Context来调用Context.getApplicationContext()来获取Application上下文。如果你正在尝试从Activity中获取Application上下文,你可以随时调用Activity.getApplication(),它应该能够作为所需的Context传递给SQLiteOpenHelper()

总的来说,在处理Context时,你的方法在这种情况下似乎没有问题,但请确保你没有像Google Android Developers博客中所描述的那样在任何地方泄漏内存。


14

有些人问过:单例模式如何返回空指针? 我正在回答这个问题。(我不能在评论中回答,因为我需要发布代码。)

它可能在两个事件之间返回 null:(1)加载类,和(2)创建这个类的对象。以下是一个例子:

class X {
    static X xinstance;
    static Y yinstance = Y.yinstance;
    X() {xinstance=this;}
}
class Y {
    static X xinstance = X.xinstance;
    static Y yinstance;
    Y() {yinstance=this;}
}

public class A {
    public static void main(String[] p) {
    X x = new X();
    Y y = new Y();
    System.out.println("x:"+X.xinstance+" y:"+Y.yinstance);
    System.out.println("x:"+Y.xinstance+" y:"+X.yinstance);
    }
}

让我们运行这段代码:

$ javac A.java 
$ java A
x:X@a63599 y:Y@9036e
x:null y:null

第二行显示Y.xinstanceX.yinstance都是null;它们是空的,因为在读取变量X.xinstanceY.yinstance时它们是空的。

这可以修复吗?可以,

class X {
    static Y y = Y.getInstance();
    static X theinstance;
    static X getInstance() {if(theinstance==null) {theinstance = new X();} return theinstance;}
}
class Y {
    static X x = X.getInstance();
    static Y theinstance;
    static Y getInstance() {if(theinstance==null) {theinstance = new Y();} return theinstance;}
}

public class A {
    public static void main(String[] p) {
    System.out.println("x:"+X.getInstance()+" y:"+Y.getInstance());
    System.out.println("x:"+Y.x+" y:"+X.y);
    }
}

这段代码没有异常:

$ javac A.java 
$ java A
x:X@1c059f6 y:Y@152506e
x:X@1c059f6 y:Y@152506e

但是对于Android Application对象来说,这不是一种选择:程序员无法控制其创建的时间。

再次强调:第一个示例和第二个示例之间的区别在于第二个示例会在静态指针为空时创建一个实例。但是程序员无法在系统决定创建它之前创建the Android应用程序对象。

更新

另一个令人困惑的示例,其中已初始化的静态字段恰好为null

Main.java:

enum MyEnum {
    FIRST,SECOND;
    private static String prefix="<", suffix=">";
    String myName;
    MyEnum() {
        myName = makeMyName();
    }
    String makeMyName() {
        return prefix + name() + suffix;
    }
    String getMyName() {
        return myName;
    }
}
public class Main {
    public static void main(String args[]) {
        System.out.println("first: "+MyEnum.FIRST+" second: "+MyEnum.SECOND);
        System.out.println("first: "+MyEnum.FIRST.makeMyName()+" second: "+MyEnum.SECOND.makeMyName());
        System.out.println("first: "+MyEnum.FIRST.getMyName()+" second: "+MyEnum.SECOND.getMyName());
    }
}

然后你会得到:

$ javac Main.java
$ java Main
first: FIRST second: SECOND
first: <FIRST> second: <SECOND>
first: nullFIRSTnull second: nullSECONDnull

请注意,您不能将静态变量声明提前一行,否则代码将无法编译。


3
有用的例子;知道这样的漏洞是很好的。我的理解是,在任何类的静态初始化期间,应避免引用此类静态变量。 - ToolmakerSteve

11

应用程序类:

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

public class MyApplication extends Application {

    private static Context mContext;

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

    public static Context getAppContext() {
        return mContext;
    }

}

在AndroidManifest中声明应用程序:

<application android:name=".MyApplication"
    ...
/>

使用方法:

MyApplication.getAppContext()

2
容易出现内存泄漏。你绝不能这样做。 - Dragas
2
@Dragas,它不容易泄漏。您可以随时这样做。 - Eugene Kartoyev
@Dragas getApplicationContext() 实际上是先前泄漏的,因此使用这个示例不会有问题。 - Mohamed Salah

9
您正在尝试创建一个包装器以获取应用程序上下文,有可能会返回“null”指针。
据我理解,更好的方法是调用以下任一方法之一:Context.getApplicationContext()或Activity.getApplication()。

14
什么情况下应该返回 null? - Stuck
29
据我所知,没有静态的Context.getApplicationContext()方法。 我有所遗漏吗? - dalcantara
我在我的应用程序中也实现了相同的方法,但是当调用SQLiteOpenHelper时,它返回空指针。对于这种情况有任何答案吗? - ashutosh
2
如果您在应用程序之前加载的内容提供程序中调用SQLiteOpenHelper,则可能会出现这种情况。 - Gunnar Bernstein

5

这是一个不错的方法,我自己也在使用。我只建议覆盖onCreate来设置单例而非使用构造函数。

既然你提到了SQLiteOpenHelper:在onCreate()中,你也可以打开数据库。

个人认为文档说通常不需要子类化Application是错误的。我认为相反的是真的:你应该总是子类化Application。


3

我会在构造函数中使用应用程序上下文来获取系统服务。这样做可以方便测试并且有利于组合。

public class MyActivity extends Activity {

    private final NotificationManager notificationManager;

    public MyActivity() {
       this(MyApp.getContext().getSystemService(NOTIFICATION_SERVICE));
    }

    public MyActivity(NotificationManager notificationManager) {
       this.notificationManager = notificationManager;
    }

    // onCreate etc

}

测试类将使用重载构造函数。

安卓将使用默认构造函数。


1

我喜欢它,但我建议使用单例模式:

package com.mobidrone;

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

public class ApplicationContext extends Application
{
    private static ApplicationContext instance = null;

    private ApplicationContext()
    {
        instance = this;
    }

    public static Context getInstance()
    {
        if (null == instance)
        {
            instance = new ApplicationContext();
        }

        return instance;
    }
}

32
扩展 android.app.Application 已经保证了单例模式,因此这是不必要的。 - Vincent
8
如果你想从非活动类中访问,该怎么办? - Maxrunner
9
除了在单元测试时可能会例外,你不应该自己使用new来创建应用程序。操作系统会执行此操作。你也不应该有构造函数,这就是onCreate方法的作用。 - Martin
@Vincent:你能在这里发布一些链接吗?最好是代码——我在这里问:http://stackoverflow.com/questions/19365797/androids-basic-components-class-loading-java-objects-lifecycle - Mr_and_Mrs_D
@radzio 为什么我们不应该在构造函数中这样做? - Miha_x64
显示剩余2条评论

0

我正在使用相同的方法,建议更好地编写单例:

public static MyApp getInstance() {

    if (instance == null) {
        synchronized (MyApp.class) {
            if (instance == null) {
                instance = new MyApp ();
            }
        }
    }

    return instance;
}

但我并不是到处都在使用,我只在可以使用的地方使用getContext()getApplicationContext()


因此,请写一条评论来解释您为什么对答案进行了反对,以便我能够理解。单例模式被广泛用于在活动或视图主体之外获取有效上下文... - Seraphim's
1
不需要,因为操作系统会确保应用程序仅被实例化一次。如果有必要,我建议在onCreate()中设置Singleton。 - Martin
1
一个很好的线程安全的方式来延迟初始化单例,但在这里并不是必要的。 - naXa stands with Ukraine
3
哇,就在我认为人们最终停止使用双重检查锁的时候...http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html - Søren Boisen

0

我知道原始问题是在13年前发布的,这是Kotlin版本的随处获取上下文。

class MyApplication : Application() {
    companion object {
        @JvmStatic
        private var instance: MyApplication? = null

        @JvmStatic
        public final fun getContext(): Context? {
            return instance
        }
    }

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

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