在各处使用应用程序上下文?


476

在Android应用中,以下方法有什么问题:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

并将其传递到需要上下文的任何地方(例如SQLiteOpenHelper)(并且当然不会泄漏)?


23
只是为了详细说明实现此目标的其他人,然后可以修改<application>AndroidManifest.xml文件的节点以包括以下属性定义:android:name="MyApp"。MyApp必须与清单引用的包相同。
马特·哈金斯

6
令人敬畏的方式来解决为SQLiteOpenHelper提供上下文的问题!我已经实现了一个单例“ SQLiteManager”,并且被困在“如何获取单例上下文?”
某处某人

8
如此您就知道您正在通过其超级接口之一返回应用程序,因此,如果您在MyApp中提供了其他方法,则将无法使用它们。相反,您的getContext()应该具有MyApp的返回类型,这样您就可以使用稍后添加的方法以及ContextWrapper和Context中的所有方法。

5
另请参阅goo.gl/uKcFn-这是与类似帖子相关的另一种回复。最好在onCreate而不是c中设置静态变量。
2011年

1
@ChuongPham如果框架杀死了您的应用程序,那么将没有任何东西可以访问null上下文...
Kevin Krumwiede 2015年

Answers:


413

尽管在很多情况下(例如您的示例),这种方法可以很好地工作,但是这种方法存在一些潜在的问题。

特别是在处理任何GUI需要处理的内容时,请务必小心Context。例如,如果将应用程序上下文传递给LayoutInflater,则将获得异常。一般而言,您的方法非常好:Activity's Context在内使用Activity,以及在Application Context超出范围之外传递上下文Activity避免内存泄漏时,是一个很好的做法。

此外,作为一种替代你的方式,你可以使用电话的快捷键getApplicationContext()一对Context对象(如活动)来获取应用程序上下文。


22
谢谢您的回答。我想我只会在持久层上使用这种方法(因为我不想使用内容提供者)。想知道设计SQLiteOpenHelper的动机是什么,这种方式期望提供Context而不是从Application本身获取它。PS,您的书很棒!
扬琴科2009年

7
使用应用程序上下文LayoutInflator对我来说只是工作。在过去三年中必须进行了更改。
雅各布·菲利普斯

5
@JacobPhillips在没有活动上下文的情况下使用LayoutInflator将会错过该活动的样式。因此,它将在某种意义上起作用,但在另一种意义上则不会。
2014年

1
@MarkCarter您是说使用应用程序上下文会错过活动的样式吗?
Jacob Phillips 2014年

1
@JacobPhillips是的,应用程序上下文不能具有样式,因为每个活动的样式都可以不同。
标记

28

以我的经验,这种方法不是必需的。如果您需要任何上下文,通常可以通过调用View.getContext()并使用获得的上下文Context来调用Context.getApplicationContext()以获取Application上下文。如果您试图从中获取Application上下文,则Activity可以随时调用Activity.getApplication(),该方法应该能够根据需要传递ContextSQLiteOpenHelper()

总体而言,在这种情况下您的方法似乎没有问题,但是在进行处理时,请Context确保没有按照Google Android开发者官方博客上的描述在任何地方泄漏内存。


13

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

它可能在两个事件之间返回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.yinstancenull ; 它们为null,因为在它们为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对象的选项:程序员无法控制创建时间。

再一次:第一个示例和第二个示例的区别在于,如果静态指针为null,则第二个示例将创建一个实例。但是,程序员不能创建系统决定做之前的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 2015年

10

应用类别:

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()

1
容易发生内存泄漏。你永远都不要这样做。
Dragas

9

您正在尝试创建一个包装来获取应用程序上下文,并且它可能返回“ null”指针。

根据我的理解,我猜这是调用2 Context.getApplicationContext() 或的更好的方法 Activity.getApplication()


13
什么时候应该返回null?

25
我知道没有静态的Context.getApplicationContext()方法。我想念什么吗?
dalcantara '02

我还在应用程序中实现了相同的方法,但是在SQLiteOpenHelper中调用时,它返回空指针。这种情况的任何答案。
ashutosh 2012年

2
如果您在应用程序之前加载的contentprovider中调用SQLiteOpenHelper,则可能是这种情况。
Gunnar Bernstein 2014年

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

}

然后,测试类将使用重载的构造函数。

Android将使用默认构造函数。


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;
    }
}

31
扩展android.app.application已经可以保证单例,因此这是不必要的
Vincent

8
如果您想从非活动班获得成功怎么办?
Maxrunner 2011年

9
您不应该new自己使用该应用程序(单元测试可能例外)。操作系统将执行此操作。您也应该没有构造函数。那onCreate是为了什么。
马丁

@Vincent:您可以在此发布一些链接吗?最好的代码-我问这里:stackoverflow.com/questions/19365797/...
Mr_and_Mrs_D

@radzio为什么我们不应该在构造函数中这样做?
Miha_x64

1

我使用相同的方法,建议将单例写得更好:

public static MyApp getInstance() {

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

    return instance;
}

但是我并没有到处使用getContext(),而是在可能的地方使用getApplicationContext()它!


因此,请写评论以解释为什么您对答案不满意,以便我理解。单例方法被广泛用于在活动或视图主体之外获取有效的上下文……
塞拉芬

1
不需要,因为操作系统可确保将应用程序精确实例化一次。如果有的话,我建议在onCreate()中设置Singelton。
马丁

1
延迟初始化单例的一种很好的线程安全方法,但在这里不是必需的。
naXa 2014年

2
哇,就在我以为人们终于使用双重检查锁定停止... cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
索伦Boisen
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.