Статический способ получить «Контекст» в Android?


970

Есть ли способ получить текущий Contextэкземпляр внутри статического метода?

Я ищу этот путь, потому что ненавижу сохранять экземпляр Context каждый раз, когда он меняется.


57
Не сохранять Context - хорошая идея не только потому, что это неудобно, но и потому, что это может привести к огромным утечкам памяти!
Викрам Бодичерла

12
@VikramBodicherla Да, но ответы ниже предполагают, что мы говорим о контексте приложения. Итак, утечки памяти не являются проблемой, но пользователь должен использовать эти решения только там, где это правильный контекст для использования.
Том

Если вам нужно использовать статический способ получения Context, тогда может быть лучший способ разработки кода.
Anonsage

3
Документация Android рекомендует передавать контекст получателям синглетонов. developer.android.com/reference/android/app/Application.html
Марко Луглио

Для того, чтобы отдать предпочтение одиночным объектам и контексту, передаваемому с помощью getInstance (), а не статическому контексту, пожалуйста, посмотрите, я попытался объяснить свои соображения, которые здесь поддерживаются рабочим кодом: stackoverflow.com/a/38967293/4469112
Alessio

Ответы:


1302

Сделай это:

В файле манифеста 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()чтобы получить статически контекст вашего приложения.


81
Есть ли недостатки этого метода? Это похоже на обман. (
Взломать

203
Недостатком является то, что нет гарантии, что нестатический onCreate () будет вызван до того, как какой-то статический код инициализации попытается получить ваш объект Context. Это означает, что ваш вызывающий код должен быть готов к работе с нулевыми значениями, что побеждает весь смысл этого вопроса.
Мелинда Грин

8
Также возможно .. мы должны объявить эту static contextпеременную как volatile?
Владимир Сорокин

14
@Tom Это не тот случай, когда статический элемент данных изначально статически. В данном коде статический член инициализируется нестатически в onCreate (). Даже статически инициализированные данные в этом случае недостаточно хороши, потому что ничто не гарантирует, что статическая инициализация данного класса произойдет до того, как к ним будет получен доступ во время статической инициализации какого-либо другого класса.
Мелинда Грин

10
@MelindaGreen В соответствии с документацией для Application onCreate () вызывается до того, как будут созданы какие-либо действия, услуги или получатели (за исключением поставщиков контента). Так не будет ли это решение безопасным, если вы не пытаетесь получить доступ к getAppContext () от поставщика контента?
Магнус 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 свысока, и я лично считаю, что его нельзя использовать в производстве.

Чтобы получить контекст приложения, мы должны вызвать метод для скрытого класса ( 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);
} 

Удачного кодирования!


56

Предполагая, что мы говорим о получении контекста приложения, я реализовал его в соответствии с предложением @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


1
@Alessio Не приводит ли этот метод к утечкам памяти
Филипп Кигени

2
@ codephillip Я не понимаю, о чем ты говоришь. Синглтон ссылается на контекст приложения, извлеченный из переданного действия, а не на действие хоста. Это законно, и это не приведет к утечке памяти. Это главное в блоге, который я написал. Если вы действительно считаете, что правы, пришлите мне пример кода, где я могу воспроизвести утечку памяти, о которой вы говорите, потому что это не так.
Алессио

1
Я думаю, что @KigenyiPhillip правильно, и это все еще представляет утечку ресурса. Изобразите справочную таблицу после вашего первого звонка getInstance(ctx). У вас есть корень instanceтипа GC MyHelper, у которого есть закрытое поле mContextтипа Context, которое ссылается на контекст приложения, собранный через передаваемый контекст getInstance(). instanceникогда не устанавливается и не очищается, поэтому GC никогда не будет перехватывать appcontext, на который ссылается instance. Вы не пропускаете никакие действия, таким образом это - низкая стоимость IMO
Марк МакКенна

1
@MarkMcKenna, как вы заявляете, «у которого есть личное поле mContext типа Context, который ссылается на контекст приложения», так что вам ясно, что mContext - это ссылка на контекст приложения, а не на какой-либо контекст. В документах getApplicationContext () вы читаете: «Контекст, жизненный цикл которого отделен от текущего контекста, который связан с временем жизни процесса, а не с текущим компонентом». Как это может создать утечку памяти? Контекст приложения определяется GC только при выходе из процесса.
Алессио

1
@Alessio, если вы признаете, что ссылка на контекст приложения не может считаться утечкой ресурса, вы можете упростить это, разместив статическую ссылку на thisin Application.onCreate(), что сделает принятый ответ лучше.
Марк МакКенна


38

Вот недокументированный способ получить приложение (которое является контекстом) из любой точки потока пользовательского интерфейса. Он опирается на скрытый статический метод 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 , если вы можете изменить код приложения.


1
Я использовал вышеупомянутый метод KennyTM, но иногда метод возвращает ноль. Есть ли другая альтернатива этому? Например, если мы получим ноль здесь, мы можем получить контекст из другого места. В моем случае onCreate () приложения не вызывается. Но вышеупомянутый метод вызывается перед этим. Plzzz помощь
AndroidGuy

Это не всегда будет работать в случае, когда GC вычистил все связанные с деятельностью вещи.
AlexVPerl

32

Это зависит от того, для чего вы используете контекст. Я могу вспомнить хотя бы один недостаток этого метода:

Если вы пытаетесь создать AlertDialogс AlertDialog.Builder, Applicationконтекст не будет работать. Я считаю, что вам нужен контекст для текущего Activity...


6
Вот так. Если вы используете для этого контекст приложения, вы можете увидеть свой диалог скрытым под основными действиями.
Nate

3
+1 в первую очередь. И возможная ошибка: Невозможно запустить действие ComponentInfo {com.samples / com.MyActivity}: android.view.WindowManager $ BadTokenException: Невозможно добавить окно - нулевой токен не для приложения
Govind

15

Котлин путь :

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


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

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
Это работает, только если вы находитесь внутри внутреннего класса, что вряд ли имеет место в ОП.
Ричард Дж. Росс III

3
Это будет работать до тех пор, пока ANY_METHOD вызывается после создания MainActivity, но сохранение статических ссылок на действия почти неизбежно приводит к утечкам памяти (как уже упоминалось в других ответах на вопрос OP), поэтому, если вы действительно должны сохранять статическую ссылку, используйте приложение только контекст.
handtwerk

1
Внутренние классы - это зло. Хуже всего то, что многие люди делают это для AsyncTasks и тому подобного, потому что многие учебники делают это таким образом ...
Reinherd

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

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

JavaDoc для ContextWrapper

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


1
Это интересно. Хорошо узнать о ContextWrapper. Однако, если вам нужно передать контекст приложения этому конструктору, вам все равно нужно получить его откуда-то.
jk7

2

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

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

2
это приведет к утечкам памяти
BlueWizard

1

Я только что выпустил JQuery-фреймворк для Android под названием Vapor API, который призван упростить разработку приложений.

Класс центрального $фасада поддерживает WeakReference(ссылка на потрясающую статью в блоге Java об этом Итана Николаса) в текущий Activityконтекст, который вы можете получить, вызвав:

$.act()

A WeakReferenceподдерживает ссылку, не предотвращая сборку мусора, возвращая исходный объект, поэтому у вас не должно быть проблем с утечками памяти.

Недостатком, конечно, является то, что вы рискуете, что $.act()может вернуть ноль. Я еще не сталкивался с этим сценарием, так что, возможно, это просто минимальный риск, о котором стоит упомянуть

Вы также можете установить контекст вручную, если вы не используете его в VaporActivityкачестве Activityкласса:

$.act(Activity);

Кроме того, большая часть инфраструктуры Vapor API использует этот хранимый контекст по своей сути, что может означать, что вам вообще не нужно хранить его самостоятельно, если вы решите использовать среду. Проверьте сайт для получения дополнительной информации и образцов.

Надеюсь, это поможет :)


1
Видимо, это только что было отвергнуто .. объяснение было бы хорошо !?
Дарий

1
Я не отрицал это, но Javascript не имеет никакого отношения к рассматриваемому вопросу, который объяснил бы любые ваши отрицательные голоса! Приветствия.
Эрнани Джопперт

Это было бы довольно бессмысленно, учитывая, что он вдохновлен некоторыми аспектами jQuery, такими как свободный интерфейс и его абстракции ... это не зависит от принципов языка!
Дарий

1
Таким образом, вы отрицаете его, потому что он был вдохновлен семантикой API фреймворка, который не на той же платформе ?! Я думаю, что вы, ребята, упускаете из виду применение принципов, не зависящих от платформы .....................................
Дариус

3
Этот ответ совершенно не связан с JavaScript. Прочитайте ответ, прежде чем понизить голос: /
BlueWizard

1

Ответ Рохита кажется правильным. Тем не менее, имейте в виду, что «Instant Run» в 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
    }

Это просто дурак, чтобы не обнаружить утечку памяти, экземпляр App все еще может вызвать утечку памяти, так как класс Application и его потомок являются Context.

Кроме того, вы можете использовать функциональный интерфейс или функциональные свойства, чтобы помочь вам получить контекст вашего приложения.

Просто создайте объектный класс:

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

или вы можете использовать его более безопасно, используя обнуляемый тип:

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

Надеюсь, это поможет.


Класс объекта этого corehelper будет инициализирован и может использоваться во время последующих действий? Извините, я новичок в kotlin
доктор АНДРО

Да, точно.
Хай Нукман

-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();    
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.