Статический способ получить "Контекст" в Android?
Есть ли способ получить текущий Context
экземпляр внутри статического метода?
Я ищу этот путь, потому что я ненавижу сохранять экземпляр Context каждый раз, когда он меняется.
23 ответа
Сделай это:
В файле манифеста 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()
чтобы получить контекст приложения статически.
Большинство приложений, которым нужен удобный метод для получения контекста приложения, создают свой собственный класс, который расширяет 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 свысока, и я лично считаю, что его не следует использовать в производстве.
Чтобы получить контекст приложения, мы должны вызвать метод для скрытого класса ( 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);
}
Удачного кодирования!
Предполагая, что мы говорим о получении контекста приложения, я реализовал его в соответствии с предложением @Rohit Ghatol, расширяющего приложение. Что случилось потом, так это то, что нет никакой гарантии, что контекст, полученный таким образом, всегда будет ненулевым. В то время, когда вам это нужно, обычно потому, что вы хотите инициализировать помощника или получить ресурс, который вы не можете отложить во времени; обработка нулевого случая не поможет вам. Так что я понял, что в основном борюсь с архитектурой Android, как указано в документации
Примечание. Обычно нет необходимости создавать подкласс Application. В большинстве ситуаций статические синглтоны могут предоставлять ту же функциональность более модульным способом. Если вашему синглтону нужен глобальный контекст (например, для регистрации широковещательных приемников), включите Context.getApplicationContext() в качестве аргумента Context при вызове метода getInstance() вашего синглтона.
и объяснил Дайан Хэкборн
Единственная причина, по которой Application существует как нечто, из чего вы можете извлечь информацию, заключается в том, что во время разработки до версии 1.0 один из наших разработчиков приложений постоянно задавал мне вопрос о необходимости иметь объект приложения верхнего уровня, из которого они могут извлекать данные, чтобы они могли иметь более "нормальный" "им модель приложения, и я в конце концов сдался. Я всегда буду сожалеть о том, что уступил в этом.:)
Она также предлагает решение этой проблемы:
Если вам нужно глобальное состояние, которое можно использовать в разных частях вашего приложения, используйте одиночный код. [...] И это более естественно приводит к тому, как вы должны управлять этими вещами - инициализировать их по требованию.
Итак, я избавился от расширения Application и передал контекст напрямую getInstance() хелпера singleton, сохранив ссылку на контекст приложения в приватном конструкторе:
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
Нет, я не думаю, что есть. К сожалению, вы застряли звонить getApplicationContext()
от Activity
или один из других подклассов Context
, Также этот вопрос несколько связан.
Котлин путь:
Manifest:
<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.
Вот недокументированный способ получить приложение (которое является контекстом) из любой точки потока пользовательского интерфейса. Он опирается на скрытый статический метод 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, например, когда вы вызываете метод вне потока пользовательского интерфейса или если приложение не связано с этим потоком.
Еще лучше использовать решение @RohitGhatol, если вы можете изменить код приложения.
Это зависит от того, для чего вы используете контекст. Я могу вспомнить хотя бы один недостаток этого метода:
Если вы пытаетесь создать AlertDialog
с AlertDialog.Builder
, Application
контекст не сработает. Я считаю, что вам нужен контекст для текущего Activity
...
В котлине
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()
Если вы открыты для использования 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) {
}
}
}
Я использовал это в какой-то момент:
ActivityThread at = ActivityThread.systemMain();
Context context = at.getSystemContext();
Это действительный контекст, который я использовал при получении системных сервисов и работал.
Но я использовал его только в модификациях фреймворка / базы и не пробовал в приложениях Android.
Предупреждение, которое вы должны знать: при регистрации для широковещательных приемников с этим контекстом это не будет работать, и вы получите:
java.lang.SecurityException: данный вызывающий пакет Android не запущен в процессе ProcessRecord
Если вы не хотите изменять файл манифеста, вы можете вручную сохранить контекст в статической переменной в вашей начальной активности:
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
}
Примечание. Как и все остальные ответы, это потенциальная утечка памяти.
В Котлине размещение контекста / контекста приложения в сопутствующем объекте по-прежнему вызывает предупреждение 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
}
Это просто обман, чтобы не обнаружить утечку памяти, экземпляр приложения по-прежнему может вызывать утечку памяти, поскольку класс приложения и его потомок являются контекстом.
Кроме того, вы можете использовать функциональный интерфейс или функциональные свойства, чтобы помочь вам получить контекст вашего приложения.
Просто создайте класс объекта:
object CoreHelper {
lateinit var contextGetter: () -> Context
}
или вы можете использовать его более безопасно, используя тип, допускающий значение NULL:
object CoreHelper {
var contextGetter: (() -> Context)? = null
}
и в своем классе приложения добавьте эту строку:
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()
Надеюсь, это поможет.
Согласно этому источнику вы можете получить свой собственный контекст, расширив 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();
}
}
Прокси-реализация контекста, которая просто делегирует все свои вызовы другому контексту. Может быть разделено на подклассы для изменения поведения без изменения исходного контекста.
Если по какой-то причине вам нужен контекст приложения в любом классе, а не только в тех, которые расширяют приложение / активность, возможно, для некоторых фабричных или вспомогательных классов. Вы можете добавить следующий синглтон в ваше приложение.
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()
Однако я не рекомендую этот подход ни к чему, кроме контекста приложения. Как это может вызвать утечки памяти.
Вы можете использовать следующее:
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();
Я думаю, вам нужно тело для getAppContext()
метод:
public static Context getAppContext()
return MyApplication.context;
Я только что выпустил JQuery-фреймворк для Android под названием Vapor API, который призван упростить разработку приложений.
Центральный $
класс фасада поддерживает WeakReference
(ссылка на удивительный пост в блоге Java об этом Итана Николаса) к текущему Activity
контекст, который вы можете получить, вызвав:
$.act()
WeakReference
поддерживает ссылку, не предотвращая сборку мусора, возвращая исходный объект, поэтому у вас не должно быть проблем с утечками памяти.
Недостатком, конечно, является то, что вы рискуете $.act()
может вернуть ноль. Я еще не сталкивался с этим сценарием, так что, возможно, это просто минимальный риск, о котором стоит упомянуть.
Вы также можете установить контекст вручную, если вы не используете VaporActivity
как твой Activity
учебный класс:
$.act(Activity);
Кроме того, большая часть инфраструктуры Vapor API использует этот хранимый контекст по своей сути, что может означать, что вам вообще не нужно хранить его самостоятельно, если вы решите использовать среду. Проверьте сайт для получения дополнительной информации и образцов.
Надеюсь, это поможет:)
Ответ Рохита кажется правильным. Тем не менее, имейте в виду, что "Мгновенный запуск" AndroidStudio зависит от отсутствия static Context
атрибуты в вашем коде, насколько я знаю.
Я использую вариацию шаблона проектирования 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 );
в моей деятельности.onCreate() и ApplicationContextSingleton.setContext( null );
в onDestroy ();
Сегодня правильный способ - использовать внедрение зависимостей. Например, можно использовать Hilt для внедрения контекста в любом месте, где это необходимо. Скажем, нужно
context
в некоторых менеджерах баз данных это можно решить следующим образом:
Добавьте рукоять в Gradle:
implementation "com.google.dagger:hilt-android:2.35"
kapt "com.google.dagger:hilt-android-compiler:2.35"
Определите класс приложения с помощью
@HiltAndroidApp
аннотация (пусть она, например, внедрит менеджер баз данных):
@HiltAndroidApp
class MyApplication : Application() {
@Inject
lateinit var dbManager: DBManager
override fun onCreate() {
super.onCreate()
dbManager.initDB()
}
}
Определите диспетчер баз данных (пусть будет
@Singleton
например, тоже):
@Singleton
class DBManager @Inject constructor(
@ApplicationContext private val context: Context
) {
fun initDB() {
// context is avaiable
databaseInit(context)
}
}
Вот и все. В
DBManager
может получить доступ к контексту правильным образом без утечек памяти.
Еще один вариант получения
context
без подкласса
Application
объект и без использования скрытых классов будет использовать ContentProvider. Однажды
onCreate
вызывается метод, контекст должен быть доступен. Вы можете сделать что-то подобное в Котлине
class ContextContentProvider : ContentProvider() {
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) = 0
override fun getType(uri: Uri): String? = null
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
override fun onCreate(): Boolean {
applicationContext = context
return true
}
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? = null
override fun update(
uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>?
) = 0
companion object {
private var applicationContext: Context? = null
@JvmStatic
fun applicationContext() = applicationContext
}
}
Везде, где вам нужен контекст, вы можете позвонить
ContextContentProvider.applicationContext()
метод
Убедитесь, что в
AndroidManifest.xml
если у вас уже есть другой поставщик содержимого и поставщик содержимого не экспортирован.
<application>
<provider
android:name=".ContextContentProvider"
android:authorities="${applicationId}.ContextContentProvider"
android:enabled="true"
android:exported="false" />
</application>
Поэтому я изменил принятый ответ, потому что он вызывает утечку памяти, это то, что я придумал...
AndroidManifest.xml
<application android:name="com.xyz.MyApplication">
...
</application>
MyApplication.java
public class MyBakingAppContext extends Application {
private static Object mContext;
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
}
public static Context getAppContext() {
return (Context)mContext;
}
}
Что я на самом деле сделал, так это назначил контекст объекту и возвратил объект как контекст (приведение его к контексту). Надеюсь, это поможет.
Я проверил все здесь описанные предложения и могу сказать:
если я использую следующий код:
public class App extends Application {
private static Context context;
public void onCreate() {
super.onCreate();
context = getApplicationContext();
}
public static Context getAppContext() {
return context;
}
}
я получаю в Studio 3 сообщение об ошибке:
Do not place Android context classes in static fields; this is a memory leak (and also breaks Instant Run)
,
Но если я использую следующий код:
public class App extends Application {
private static List<Context> context = new ArrayList<>();
@Override
public void onCreate() {
super.onCreate();
context.add(0, getApplicationContext());
}
public static Context getAppContext() {
return context.get(0);
}
}
это работает нормально без каких-либо ошибок.