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

And*_*ega 931 android android-context

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

我正在寻找那种方式,因为我讨厌每次更改时保存"Context"实例.

小智 1261

做这个:

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

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

</application>
Run Code Online (Sandbox Code Playgroud)

然后写下课程:

public class MyApplication extends Application {

    private static Context context;

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

    public static Context getAppContext() {
        return MyApplication.context;
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,到处调用MyApplication.getAppContext()以静态获取应用程序上下文.

  • 缺点是在某些静态初始化代码尝试获取Context对象之前,无法保证会调用非静态onCreate().这意味着你的调用代码需要准备好处理空值,这有点打败了这个问题的全部要点. (197认同)
  • 这种方法有什么缺点吗?这似乎是作弊.(一个黑客?) (78认同)
  • 这似乎不适用于库项目. (38认同)
  • @Tom这不是静态数据成员最初静态的情况.在给定的代码中,静态成员在onCreate()中非静态地初始化.在这种情况下,即使是静态初始化的数据也不够好,因为没有什么能够确保给定类的静态初始化将在其他类的静态初始化期间被访问之前发生. (14认同)
  • 也许..我们应该将这个`static context`变量声明为`volatile`吗? (8认同)
  • @MelindaGreen根据Application的文档,在创建任何活动,服务或接收者(不包括内容提供者)之前调用onCreate().因此,只要您不尝试从内容提供商访问getAppContext(),这个解决方案就不安全吗? (7认同)
  • @MelindaGreen我同意你刚才的发言.由于您声明的原因,我根本不会在静态初始化程序中引用getApplicationContext.OP想要一种通过静态方法访问上下文的方法,但我并不认为这意味着他希望能够在静态初始化器中使用该静态方法. (5认同)
  • @RaphMclee:你不能把它作为最终版,因为它不是在c-tor中初始化的.仍然可以通过多个线程访问`context`变量.并且根据_"Java Concurrency In Practice - 3.5.3.安全发布惯用法"_:要发布参考资料,您应该使用以下方法之一:静态初始化器,volatile字段,AtomicReference,final字段,锁.否则,您可能会遇到并发问题.但我对我们的具体情况并不是100%肯定,有不同的微妙细微差别,所以我决定使用`volatile`以防万一,它不会影响性能. (4认同)
  • @Tom再次,这不是关于静态初始化而是一般的初始化顺序.建议的解决方案只是建立竞争条件.如果它通常似乎工作,这似乎是好运(IE运气不好).这是我在第一篇评论中描述的缺点,即为了安全起见,调用者需要准备好获取空值.不幸的是,这似乎是OP试图解决的问题.根据具体情况,OP可能没有意识到活动和服务*是*上下文,并且可能没有任何问题. (4认同)
  • 有人知道在onCreate()而不是在构造函数中设置上下文变量的原因吗? (3认同)
  • @TeeTracker在这种情况下,不存在泄漏,因为只要应用程序本身处于活动状态,Application对象就处于活动状态,即它在应用程序的生命周期内不会被垃圾收集. (3认同)
  • 有一些明显的情况,当一个应用程序有一个内容提供者,其中*应用程序对象之前初始化内容提供者,因此在这种情况下你不能指望要初始化的静态字段. (3认同)
  • @VladimirSorokin:不需要volatile关键字,因为上下文始终引用同一对象。指针的值可以设为最终值,因为一旦赋值就不会改变。 (2认同)
  • @MelindaGreen 我不同意。如果您有使用静态初始化的静态属性,那么您已经知道您正在做一些取决于初始化顺序的事情,需要小心。大多数情况下,您不会以这种方式使用它。 (2认同)
  • @BadCash,谢谢你指出文档.它预先说明通常不需要从Application子类化,因为单例可能更好.然后它说要实现这样的单例,你获取实例的函数应该传递给你的单例初始化调用getApplicationContext()的Context.这样第一次调用将导致设置单例,并且调用者不必担心获取空值.不幸的是,这意味着他们必须有一个Context,这是最初的问题.Erich的答案是正确的. (2认同)
  • 只要您不初始化将静态尝试引用静态存储上下文的类,在 Application 的子类中执行此操作(在 AndroidManifest.xml 中定义为应用程序,这是安全的,因为 Android 系统保证您的应用程序instance .onCreate 将是在其他任何事情之前调用的第一件事。所以,虽然在普通 Java 中不是一种“安全”的方式来做到这一点,但这就像在常规 java main 中设置一个静态字段......安全,这么长时间因为你没有用其他静态初始化字段做可怕的静态事情。 (2认同)
  • 此解决方案会触发 Android Lint Performance 警告,因为放置在静态字段中的 Android Contexts 构成内存泄漏...您可以在 Android Studio 中自行检查:Analyze&gt;Inspect Code... (2认同)
  • 按照“应该轻松的事情应该很容易”的现代精神,实际上应该有一个Context.getApplicationContext()或Application.getApplicationContext()静态方法。该对象是一个单例对象,应如此访问,而不用跳过箍。在解决之前,在静态初始化程序或内容提供程序之外,即在我的代码的99%中,此答案提供了合理的解决方法。感谢评论者指出这种技术不安全的特定情况。 (2认同)
  • @CrearoRotar 这实际上并没有错,在我看来,存储对应用程序上下文的静态引用并不是一个坏习惯,不会有任何内存泄漏,因为 android 系统已经杀死了你的进程,尽管我同意它应谨慎使用。 (2认同)
  • 注意:在极少数情况下,该代码块不会被调用,这可能会导致 NPE 崩溃:当应用程序数据从云端恢复时,Application.onCreate() 在下次启动时不会被调用。/sf/answers/4241840291/ (2认同)

Jar*_*ler 80

大多数需要方便的方法来获取应用程序上下文的应用程序都会创建自己的扩展类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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,在AndroidManifest中,您应该在AndroidManifest.xml的标记中指定您的类的名称:

<application 
    ...
    android:name="com.example.App" >
    ...
</application>
Run Code Online (Sandbox Code Playgroud)

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

public static void someMethod() {
    Context context = App.getContext();
}
Run Code Online (Sandbox Code Playgroud)

警告

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

通常不需要子类Application.在大多数情况下,静态单例可以以更模块化的方式提供相同的功能.如果你的单例需要一个全局上下文(例如注册广播接收器),那么检索它的函数可以给一个Context,它在第一次构造单例时在内部使用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);
}
Run Code Online (Sandbox Code Playgroud)

还有一个隐藏类(AppGlobals)提供了一种以静态方式获取应用程序上下文的方法.它使用上下文,ActivityThread所以下面的方法和上面发布的方法之间确实没有区别:

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

快乐的编码!


Ale*_*sio 56

假设我们正在讨论获取应用程序上下文,我按照@Rohit Ghatol扩展Application的建议实现了它.那时发生的事情是,不能保证以这种方式检索的上下文总是非空的.当你需要它时,通常是因为你想要初始化一个帮助器,或者获得一个你不能及时延迟的资源; 处理null case对你没有帮助.所以我理解我基本上是在与Android架构作斗争,正如文档中所述

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

并由Dianne Hackborn解释

应用程序存在的唯一原因是因为在1.0之前的开发过程中,我们的一个应用程序开发人员不断地烦恼我需要拥有一个他们可以从中获得的顶级应用程序对象,这样他们就可以更加"正常" "对他们的应用模式,我最终屈服了.我会永远后悔屈服于那个.:)

她也建议解决这个问题:

如果你想要的是一个可以在应用程序的不同部分共享的全局状态,请使用单例.[...]这更自然地导致你应该如何管理这些东西 - 按需初始化它们.

所以我做的是摆脱扩展Application,并将上下文直接传递给singleton帮助器的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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后调用者将本地上下文传递给帮助者:

Helper.getInstance(myCtx).doSomething();
Run Code Online (Sandbox Code Playgroud)

因此,要正确回答这个问题:有一些方法可以静态访问应用程序上下文,但是所有这些都应该是不鼓励的,你应该更喜欢将本地上下文传递给单例的getInstance().


对于任何有兴趣的人,您可以在fwd博客上阅读更详细的版本

  • 这是最有趣和正确的答案,尽管已经接受600多个赞成票.隐藏的珍珠 (5认同)
  • @Shine我猜这个需要一些upvotes :)使用建议的方法6个月后,我永远不会回去静态检索上下文; 这对我来说是一个很好的学习,我希望能更频繁地阅读(和使用)这个答案.谢谢你的评论! (2认同)
  • @Gero最后我写了[Android上下文点播]的帖子(https://www.fwd.cloud/commit/post/android-context-on-demand),看看它对你有帮助! (2认同)
  • @codephillip 我不明白你在说什么。单例引用从传递的活动中检索到的应用程序上下文,而不是宿主活动。这是合法的,它不会导致任何内存泄漏。这是我写的 [博客](https://www.fwd.cloud/commit/post/android-context-on-demand/) 的主要观点。如果你真的认为你是对的,请给我发送一个示例代码,我可以在其中重现你所说的内存泄漏,因为事实并非如此。 (2认同)
  • 对于任何怀疑使用 getInstance 单例模式 + getApplicationContext 不会导致内存泄漏的人,请检查 LocalBroadcastManager 源代码,它正是这样做的。 (2认同)

Eri*_*ass 48

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

  • 该文章的正确链接:http://android-developers.blogspot.co.il/2009/01/avoiding-memory-leaks.html (8认同)

ken*_*ytm 37

这是一种从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
}
Run Code Online (Sandbox Code Playgroud)

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

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


gul*_*der 32

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

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

  • 那就对了.如果您使用应用程序上下文,则可能会在前台活动中看到对话框. (6认同)
  • 首先是+1.并且可能出现的错误是无法启动活动ComponentInfo {com.samples/com.MyActivity}:android.view.WindowManager $ BadTokenException:无法添加窗口 - 令牌null不适用于应用程序 (3认同)

use*_*331 11

如果您愿意使用RoboGuice,则可以将上下文注入到您想要的任何类中.以下是RoboGuice 2.0(在撰写本文时为测试版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) {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


ung*_*rys 8

我在某个时候用过这个:

ActivityThread at = ActivityThread.systemMain();
Context context = at.getSystemContext();
Run Code Online (Sandbox Code Playgroud)

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

但是,我只在框架/基础修改中使用它,并没有在Android应用程序中尝试它.

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

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


phn*_*mnn 7

科特林方式

表现:

<application android:name="MyApplication">

</application>
Run Code Online (Sandbox Code Playgroud)

MyApplication.kt

class MyApplication: Application() {

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

    companion object {
        lateinit var instance: MyApplication
            private set
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Blu*_*ard 6

根据此来源,您可以通过扩展 ContextWrapper 来获取自己的 Context

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

ContextWrapper 的 JavaDoc

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

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

She*_*yar 6

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

public class App {
    private static Context context;

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

    public static Context getContext() {
        return context;
    }
}
Run Code Online (Sandbox Code Playgroud)

并在您的活动(或活动)开始时设置上下文:

// MainActivity

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

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

    // Other stuff
}
Run Code Online (Sandbox Code Playgroud)

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

  • 由于这种情况下的上下文绑定到应用程序,它到底会泄漏什么?如果应用程序终止,其他所有内容也会终止。 (3认同)

bar*_*ikk 5

您可以使用以下内容:

MainActivity.this.getApplicationContext();
Run Code Online (Sandbox Code Playgroud)

MainActivity.java:

...
public class MainActivity ... {
    static MainActivity ma;
...
    public void onCreate(Bundle b) {
         super...
         ma=this;
         ...
Run Code Online (Sandbox Code Playgroud)

任何其他类别:

public ...
    public ANY_METHOD... {
         Context c = MainActivity.ma.getApplicationContext();
Run Code Online (Sandbox Code Playgroud)

  • 这仅在您位于内部类内部时才有效,而在OP中几乎不是这种情况。 (4认同)
  • 只要在创建 MainActivity 之后调用 ANY_METHOD 就可以工作,但是保留对活动的静态引用几乎不可避免地会导致内存泄漏(正如对 OP 问题的其他回答已经提到的那样),因此如果您确实必须保留静态引用,请使用该应用程序仅上下文。 (3认同)

Ver*_*rsa 5

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在应用程序类的 onCreate 中初始化它

GlobalAppContextSingleton.getInstance().initialize(this);
Run Code Online (Sandbox Code Playgroud)

通过调用在任何地方使用它

GlobalAppContextSingleton.getInstance().getApplicationContext()
Run Code Online (Sandbox Code Playgroud)

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


Khe*_*raj 5

科特林

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

    companion object {
        lateinit var mInstance: MyApp
        fun getContext(): Context? {
            return mInstance.applicationContext
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

并获得像

MyApp.mInstance
Run Code Online (Sandbox Code Playgroud)

要么

MyApp.getContext()
Run Code Online (Sandbox Code Playgroud)


Hay*_*man 5

在 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
    }
Run Code Online (Sandbox Code Playgroud)

这只是愚弄 lint 没有发现内存泄漏,App 实例仍然会产生内存泄漏,因为 Application 类及其后代是一个 Context。

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

只需创建一个对象类:

object CoreHelper {
    lateinit var contextGetter: () -> Context
}
Run Code Online (Sandbox Code Playgroud)

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

object CoreHelper {
    var contextGetter: (() -> Context)? = null
}
Run Code Online (Sandbox Code Playgroud)

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


class MyApp: Application() {

    override fun onCreate() {
        super.onCreate()
        CoreHelper.contextGetter = {
            this
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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


    <application
            android:name=".MyApp"
Run Code Online (Sandbox Code Playgroud)

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

CoreHelper.contextGetter()

// or if you use the nullable version
CoreHelper.contextGetter?.invoke()
Run Code Online (Sandbox Code Playgroud)

希望它会有所帮助。