Статический способ получить "Контекст" в 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();
    }
}

JavaDoc для ContextWrapper

Прокси-реализация контекста, которая просто делегирует все свои вызовы другому контексту. Может быть разделено на подклассы для изменения поведения без изменения исходного контекста.

Если по какой-то причине вам нужен контекст приложения в любом классе, а не только в тех, которые расширяют приложение / активность, возможно, для некоторых фабричных или вспомогательных классов. Вы можете добавить следующий синглтон в ваше приложение.

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

это работает нормально без каких-либо ошибок.

Другие вопросы по тегам