在Android中获取“上下文”的静态方法?


970

有没有办法Context在静态方法中获取当前实例?

我正在寻找这种方式,因为我讨厌每次更改“上下文”实例时都将其保存。


57
不保存上下文是一个好主意,不仅因为它不方便,而且还因为它可能导致大量内存泄漏!
Vikram Bodicherla'4

12
@VikramBodicherla是的,但是下面的答案假设我们正在谈论应用程序上下文。因此,内存泄漏不是问题,但是用户仅应在正确的上下文中使用这些解决方案。
汤姆(Tom)

如果必须使用静态的获取方法Context,那么可能会有更好的方法来设计代码。
Anonsage 2015年

3
Android文档建议将上下文传递给单例的获取者。developer.android.com/reference/android/app/Application.html
Marco Luglio

对于偏爱通过静态实例通过getInstance()传递的单例和上下文,请看一下,我试图在这里解释我的推理,并通过以下工作代码提供支持: stackoverflow.com/a/38967293/4469112
Alessio

Answers:


1302

做这个:

在Android Manifest文件中,声明以下内容。

<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()以静态获取您的应用程序上下文。


81
这种方法有什么缺点吗?这似乎是作弊。(是黑客吗?)
jjnguy 2011年

203
缺点是不能保证在某些静态初始化代码尝试获取Context对象之前,将已调用非静态onCreate()。这意味着您的调用代码将需要准备好处理空值,这将使这个问题的重点落空。
梅琳达·格林

8
也可能..我们应该将此static context变量声明为volatile吗?
弗拉基米尔·索罗金

14
@Tom这不是静态数据成员最初是静态的。在给定的代码中,静态成员在onCreate()中被非静态地初始化。在这种情况下,即使静态初始化的数据也不够好,因为无法确保给定类的静态初始化会在其他类的静态初始化期间被访问之前发生。
Melinda Green

10
@MelindaGreen根据Application的文档,在创建任何活动,服务或接收者(不包括内容提供者)之前,将调用onCreate()。因此,只要您不尝试从内容提供者访问getAppContext(),此解决方案就不会安全吗?
Magnus W

86

大多数希望使用便捷方法获取应用程序上下文的应用程序都会创建自己的扩展类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中,您应该在AndroidManifest.xml的标签中指定类的名称:

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

然后,您可以使用以下任何静态方法检索应用程序上下文:

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

警告

在将上述内容添加到您的项目之前,应考虑文档说明:

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


反射

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

要检索应用程序上下文,我们必须在自API 1开始可用的隐藏类(ActivityThread)上调用方法。

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

编码愉快!


56

假设我们正在讨论获取应用程序上下文,我按照@Rohit Ghatol扩展Application的建议实现了它。然后发生的事情是,不能保证以这种方式检索的上下文将始终为非null。在您需要它时,通常是因为您想初始化一个助手或获取资源,所以您不能延迟时间。处理null大小写将无济于事。因此,我知道我基本上是在与Android体系结构作斗争,如文档所述

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

并由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

2
@codephillip我不明白您在说什么。单例引用从传递的活动而不是主机活动中检索到的应用程序上下文。这是合法的,不会造成任何内存泄漏。那就是我写的博客的重点。如果您确实认为自己是对的,请给我发送示例代码,在这里我可以重现您所谈论的内存泄漏,因为事实并非如此。
Alessio

1
我认为@KigenyiPhillip是正确的,但这仍然表示资源泄漏。首次致电时,请为参考图绘制图片getInstance(ctx)。你有一个GC根instance型的MyHelper,其中有一个私有字段mContext类型Context,其中引用通过上下文收集应用程序上下文传递getInstance()instance不会再次设置或清除,因此GC永远不会捕获所引用的appcontext instance。您不会泄漏任何活动,因此它是低成本的IMO。
马克·麦肯纳

1
@MarkMcKenna声明为“其中具有Context类型的私有字段mContext,它引用了应用程序上下文”,因此您可以清楚地看到mContext是对应用程序上下文的引用,而不是对任何上下文的引用。在getApplicationContext()文档中,您将阅读:“其生命周期与当前上下文分开的上下文,该上下文与流程的生存期而不是当前组件的生存期有关”。如何造成内存泄漏?仅在进程退出时才对应用程序上下文进行GC。
Alessio '18

1
@Alessio如果您接受对应用程序上下文的引用不符合资源泄漏的条件,则可以通过this在in中发布静态引用来简化此操作Application.onCreate(),这将使接受的答案更好。
马克·麦肯纳


38

这是从UI线程中任何位置获取应用程序(即上下文)的未公开方法。它依赖于隐藏的静态方法。它至少应在Android 4.x上运行。ActivityThread.currentApplication()

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
}

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

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


1
我使用了上述方法KennyTM,但有时该方法返回null。还有其他替代方法吗?就像在这里得到null一样,我们可以从其他地方检索上下文。就我而言,不调用Application的onCreate()。但是上面的方法在它之前被调用。Plzzz帮助
AndroidGuy

在GC清除了所有与活动相关的内容的情况下,这并不总是有效。
AlexVPerl 2015年

32

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

如果您尝试使用创建一个AlertDialogwith AlertDialog.Builder,则Application上下文将不起作用。我相信您需要当前的背景Activity...


6
那就对了。如果为此使用应用程序上下文,则可能会看到对话框隐藏在前台活动下。
Nate

3
首先+1。并且可能出现的错误是无法启动活动ComponentInfo {com.samples / com.MyActivity}:android.view.WindowManager $ BadTokenException:无法添加窗口-令牌null不适用于应用程序
Govind

15

科特林方式

表现:

<application android:name="MyApplication">

</application>

MyApplication.kt

class MyApplication: Application() {

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

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

然后,您可以通过访问属性 MyApplication.instance


11

如果您愿意使用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) {
        }
    }
}

8

我在某些时候使用过:

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

这是我用于获取系统服务并工作的有效上下文。

但是,我仅在框架/基础修改中使用它,而未在Android应用程序中尝试过。

一个警告,你必须知道:当广播接收机同此背景下注册,它不会工作,你会得到:

java.lang.SecurityException:给定的调用程序包android不在进程ProcessRecord中运行


7

科特林

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

4

您可以使用以下内容:

MainActivity.this.getApplicationContext();

MainActivity.java:

...
public class MainActivity ... {
    static MainActivity ma;
...
    public void onCreate(Bundle b) {
         super...
         ma=this;
         ...

任何其他类别:

public ...
    public ANY_METHOD... {
         Context c = MainActivity.ma.getApplicationContext();

3
仅当您在内部类内部时才有效,而在OP中几乎不是这种情况。
理查德·罗斯三世

3
只要在MainActivity创建之后调用ANY_METHOD,这就会起作用,但是保持对活动的静态引用几乎不可避免地会导致内存泄漏(因为已经提到了OP的其他问题),因此,如果您确实必须保持静态引用,请使用该应用程序仅上下文。
handtwerk

1
内部阶级是邪恶的。最糟糕的是,很多人都为AsyncTasks这样做,因为许多教程都是这样做的……
Reinherd 2013年

4

如果您不想修改清单文件,则可以在初始活动中将上下文手动存储在静态变量中:

public class App {
    private static Context context;

    public static void setContext(Context cntxt) {
        context = cntxt;
    }

    public static Context getContext() {
        return context;
    }
}

只需在活动开始时设置上下文:

// MainActivity

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Set Context
    App.setContext(getApplicationContext());

    // Other stuff
}

注意:像所有其他答案一样,这是潜在的内存泄漏。


1
既然上下文在这种情况下绑定到了应用程序,它将到底泄漏了什么?如果应用程序死了,那么其他所有事情也都消失了。
TheRealChx101 '19

3

我认为该getAppContext()方法需要一个主体:

public static Context getAppContext()
   return MyApplication.context; 

3

根据此资源,您可以通过扩展ContextWrapper获得自己的上下文

public class SomeClass extends ContextWrapper {

    public SomeClass(Context base) {
      super(base);
    }

    public void someMethod() {
        // notice how I can use "this" for Context
        // this works because this class has it's own Context just like an Activity or Service
        startActivity(this, SomeRealActivity.class);

        //would require context too
        File cacheDir = getCacheDir();
    }
}

ContextWrapper的JavaDoc

Context的代理实现,该实现只是将其所有调用委派给另一个Context。可以子类化以修改行为,而无需更改原始上下文。


1
这是有趣的。很高兴了解ContextWrapper。但是,如果您需要将应用程序上下文传递给此构造函数,则仍然需要从某个地方获取它。
jk7

2

如果您出于某种原因想要在任何类中使用Application上下文,而不仅仅是那些扩展应用程序/活动的类,则可能是在某些工厂或助手类中使用的。您可以将以下单例添加到您的应用中。

public class GlobalAppContextSingleton {
    private static GlobalAppContextSingleton mInstance;
    private Context context;

    public static GlobalAppContextSingleton getInstance() {
        if (mInstance == null) mInstance = getSync();
        return mInstance;
    }

    private static synchronized GlobalAppContextSingleton getSync() {
        if (mInstance == null) mInstance = 
                new GlobalAppContextSingleton();
        return mInstance;
    }

    public void initialize(Context context) {
        this.context = context;
    }

    public Context getApplicationContext() {
        return context;
    }
}

然后在应用程序类的onCreate中对其进行初始化

GlobalAppContextSingleton.getInstance().initialize(this);

致电即可在任何地方使用

GlobalAppContextSingleton.getInstance().getApplicationContext()

除了应用程序上下文之外,我不建议将此方法用于任何其他情况。因为这可能导致内存泄漏。


并不是说类/方法的名称是一成不变的,要保持很长,并且(希望)描述问题和答案,并缩短它以供我使用。
Versa

1

我使用Singleton设计模式的一种变体来帮助我。

import android.app.Activity;
import android.content.Context;

public class ApplicationContextSingleton {
    private static Activity gContext;

    public static void setContext( Activity activity) {
        gContext = activity;
    }

    public static Activity getActivity() {
        return gContext;
    }

    public static Context getContext() {
        return gContext;
    }
}

然后我把ApplicationContextSingleton.setContext( this );我的activity.onCreate()ApplicationContextSingleton.setContext( null );的onDestroy() ;


如果您只需要上下文,则可以调用activity.getApplicationContext();。可以将其静态保留,而不必担心泄漏。
MinceMan 2015年

2
这将导致内存泄漏
BlueWizard

1

我刚刚发布了一个受Android启发的jQuery框架,称为Vapor API,旨在简化应用程序开发。

中心的$外观类维护着一个WeakReference(到Ethan Nicholas的Java博客文章的链接)到当前Activity上下文,您可以通过调用以下内容进行检索:

$.act()

A会WeakReference在不阻止垃圾回收回收原始对象的情况下维护引用,因此您不会遇到内存泄漏的问题。

当然,缺点是您冒着$.act()可能返回null 的风险。不过,我还没有遇到这种情况,所以值得一提的只是很小的风险。

如果您不用VaporActivityActivity课程,也可以手动设置上下文:

$.act(Activity);

另外,许多Vapor API框架都固有地使用此存储的上下文,这意味着如果您决定使用该框架,则根本不需要自己存储它。请访问该站点以获取更多信息和样本。

希望对您有所帮助:)


1
显然,这只是被否决了..一个解释会很好!
Darius

1
我没有对此进行否决,但是Javascript与当前问题无关,这可以解释您可能遇到的所有否决!干杯。
Ernani Joppert 2014年

考虑到它 jQuery某些方面(如流畅的界面)及其抽象的启发,这将是毫无意义的。这些都是与底层语言无关的原理!
Darius 2014年

1
所以您不赞成它,因为它是受不在同一平台上的框架的API语义启发的!我认为你们都错过了应用与平台无关的原则的意义。
Darius

3
这个答案与JavaScript完全无关。
投票否决

1

罗希特的答案似乎是正确的。但是,据我所知,AndroidStudio的“即时运行”取决于static Context代码中是否没有属性。


1
你是对的。而且还会导致内存泄漏!
user1506104

1

在Kotlin中,将Context / App Context放在伴随对象中仍会产生警告 Do not place Android context classes in static fields; this is a memory leak (and also breaks Instant Run)

或者,如果您使用类似这样的内容:

    companion object {
        lateinit var instance: MyApp
    }

只是愚弄了皮棉而没有发现内存泄漏,因为Application类及其后代是Context,所以App实例仍然会产生内存泄漏。

另外,您可以使用功能接口或功能属性来帮助您获取应用上下文。

只需创建一个对象类:

object CoreHelper {
    lateinit var contextGetter: () -> Context
}

或者您可以使用可为空的类型来更安全地使用它:

object CoreHelper {
    var contextGetter: (() -> Context)? = null
}

并在您的App类中添加以下行:


class MyApp: Application() {

    override fun onCreate() {
        super.onCreate()
        CoreHelper.contextGetter = {
            this
        }
    }
}

并在清单中声明应用名称为 . MyApp


    <application
            android:name=".MyApp"

当您想获取上下文时,只需调用:

CoreHelper.contextGetter()

// or if you use the nullable version
CoreHelper.contextGetter?.invoke()

希望它会有所帮助。


这个corehelper的对象类将被初始化,并可以在以后的活动中使用?抱歉,我是Kotlin的新手
Dr. aNdRO

对,就是这样。
Hayi Nukman

-1

试试这个

import androidx.appcompat.app.AppCompatActivity;  
import android.content.Context; 
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    private static Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = getApplicationContext();
    }

    public static void getContext(View view){
        Toast.makeText(context, "Got my context!",
                    Toast.LENGTH_LONG).show();    
    }
}
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.