Статичний спосіб отримати "Контекст" в Android?


970

Чи є спосіб отримати поточний Contextекземпляр всередині статичного методу?

Я шукаю такий спосіб, тому що я ненавиджу зберігати екземпляр "Контексту" кожного разу, коли він змінюється.


57
Не економити контекст - це гарна ідея не лише тому, що це незручно, а більше тому, що може призвести до величезних витоків пам'яті!
Вікрам Бодіхерла

12
@VikramBodicherla Так, але відповіді нижче передбачають, що ми говоримо про контекст програми. Отже, витоки пам'яті не є проблемою, але користувач повинен використовувати ці рішення лише тоді, коли це правильний контекст.
Том

Якщо вам доведеться використовувати статичний спосіб отримання Context, то може бути кращий спосіб розробити код.
Anonsage

3
Документація на Android рекомендує передавати контекст метеорам. developer.android.com/reference/android/app/Application.html
Marco Luglio

Для того, щоб віддати перевагу одиночним і контексту, переданим getInstance () над статичним контекстом, будь ласка, подивіться, я намагався пояснити свої міркування тут, підтримувані робочим кодом: stackoverflow.com/a/38967293/4469112
Алессіо,

Відповіді:


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

203
Мінусом є те, що немає гарантії, що нестатичний onCreate () буде викликаний до того, як якийсь статичний код ініціалізації намагатиметься отримати ваш об'єкт Context. Це означає, що ваш код виклику повинен бути готовий мати справу з нульовими значеннями, які ніби перемагають всю точку цього питання.
Мелінда Грін

8
Можливо також .. чи слід оголосити цю static contextзмінну як volatile?
Володимир Сорокін

14
@Tom Це не випадок, коли статичний член даних спочатку є статичним. У даному коді статичний член ініціалізується нестатично в onCreate (). Навіть статично ініціалізовані дані в цьому випадку недостатньо хороші, тому що ніщо не гарантує, що статична ініціалізація даного класу відбудеться до того, як до нього звертатимуться під час статичної ініціалізації якогось іншого класу.
Мелінда Зелена

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, і я особисто думаю, що це не слід використовувати у виробництві.

Щоб вивести контекст програми, ми повинні викликати метод у прихованому класі ( 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, як зазначено в документах

Примітка: Зазвичай не потрібно підкласових додатків. У більшості ситуацій статичні одиночні кнопки можуть забезпечувати однакову функціональність більш модульним способом. Якщо для вашого одиночного потрібен глобальний контекст (наприклад, для реєстрації широкомовних приймачів), включіть Context.getApplicationContext () як аргумент контексту при виклику методу getInstance () вашого одиночного.

і пояснила Діанна Хакборн

Єдина причина, що Програма існує як щось, з чого можна отримати, - це те, що під час розробки до 1,0 розробник одного з наших додатків постійно переслідував мене про необхідність мати об'єкт додатка вищого рівня, з якого вони можуть виходити, щоб вони могли мати більш "нормальну" "для них модель застосування, і я врешті-решт поступився. Я назавжди пошкодую, що віддав цю програму. :)

Вона також пропонує вирішити цю проблему:

Якщо ви хочете, щоб це було деяким глобальним станом, яке можна ділити в різних частинах вашої програми, скористайтеся однотонним. [...] І це природніше призводить до того, як вам слід керувати цими речами - ініціалізуючи їх на вимогу.

так що я зробив позбавлення від розширення програми і передав контекст безпосередньо до 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
@ Алессіо Цей метод не призводить до витоку пам’яті
Phillip Kigenyi

2
@codephillip Я не розумію, про що ти говориш. Сингл посилається на контекст програми, отриманий з переданої активності, а не на діяльність хоста. Це законно, і це не спричинить витоку пам'яті. Це головний пункт блогу, який я написав. Якщо ви дійсно вважаєте, що маєте рацію, надішліть мені зразок коду, де я можу відтворити витік пам'яті, про який ви говорите, тому що це не так.
Алессіо

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

1
@MarkMcKenna, як ви заявляєте, "який має приватне поле mContext типу Context, яке посилається на контекст програми", тож вам зрозуміло, що mContext є посиланням на контекст програми, а не на будь-який контекст. У документах getApplicationContext () ви читаєте: "Контекст, життєвий цикл якого відокремлений від поточного контексту, який прив'язаний до тривалості процесу, а не до поточного компонента". Як це може створити витік пам'яті? Контекст програми GC'd лише тоді, коли процес закінчується.
Алессіо

1
@Alessio якщо ви приймаєте, що посилання на контекст програми не кваліфікується як витік ресурсу, ви можете спростити це, розмістивши статичну посилання на thisв 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
}

Зауважте, що цей метод може повернути нуль, наприклад, коли ви викликаєте метод поза потоком інтерфейсу або програма не пов'язана з потоком.

Ще краще використовувати рішення @RohitGhatol , якщо ви можете змінити код програми.


1
Я використовував вищевказаний метод KennyTM, але іноді метод повертає null. Чи є якась інша альтернатива цьому? Як і якщо ми тут отримаємо нуль, ми можемо отримати контекст з іншого місця. У моєму випадку onCreate () програми не викликається. Але вищевказаний метод називається перед цим. Довідка Plzzz
AndroidGuy

Це не завжди спрацює в тому випадку, коли GC очистив усі речі, пов'язані з діяльністю.
AlexVPerl

32

Це залежить від того, для чого ви використовуєте контекст. Я можу придумати хоча б один недолік цього методу:

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


6
Це вірно. Якщо ви використовуєте для цього контекст програми, то, можливо, ви побачите діалог, прихований під переднім планом.
Нейт

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 (бета-версія 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: Даний пакет виклику андроїд не запускається в процесі 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
Це буде працювати до тих пір, поки ANE_METHOD буде викликаний після створення MainActivity, але зберігання статичних посилань на діяльність майже неминуче вводить витоки пам’яті (як уже згадуються інші відповіді на запитання ОП), тому, якщо ви дійсно повинні зберігати статичну посилання, використовуйте додаток лише контекст.
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()

Я не рекомендую цей підхід ні до чого, крім контексту програми. Оскільки це може спричинити витік пам'яті.


Це не так, як назви класів / методів встановлені в камені, зберегли його довгим і (сподіваюся) описовим для запитань і запитань, скоротили його для мого власного використання.
Верса

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 );у свою активність.onCreate () та ApplicationContextSingleton.setContext( null );в onDestroy () ;


Якщо все, що вам потрібно, це контекст, ви можете зателефонувати Activity.getApplicationContext (); Це можна утримувати статично, не турбуючись про витоки.
MinceMan

2
це призведе до витоку пам'яті
BlueWizard

1

Щойно я випустив натхненну jQuery рамку для Android під назвою API Vapor, яка має на меті спростити розробку додатків.

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

$.act()

A WeakReferenceпідтримує посилання, не перешкоджаючи збору сміття відновлювати оригінальний об'єкт, тому у вас не повинно виникнути проблем із витоками пам'яті.

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

Ви також можете встановити контекст вручну, якщо ви не використовуєте VaporActivityв якості Activityкласу:

$.act(Activity);

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

Я сподіваюся, що це допомагає :)


1
Мабуть, це щойно принизило силу .. пояснення було б добре!
Дарій

1
Я не спростовував цього, але Javascript не має нічого спільного з наявним питанням, яке б пояснило будь-які голоси, які ви, можливо, мали! Ура.
Ернані Джопперт

Це було б дуже безглуздо, враховуючи, що він натхненний деякими аспектами jQuery, як вільний інтерфейс, і його абстракцій. Це принципи, агностичні основної мови!
Дарій

1
Отже, ви це зводите, тому що його надихнула семантика API рамки, яка не знаходиться на одній платформі ?! Думаю, ви, хлопці, сумуєте за застосуванням платформно-агностичних принципів ...
Дарій

3
ця відповідь абсолютно не пов'язана з JavaScript. Прочитайте відповідь перед тим, як звернутись до голосу: /
BlueWizard

1

Відповідь Рохіта здається правильною. Однак майте на увазі, що "Миттєвий запуск" AndroidStudio залежить від того, static Contextщо у вашому коді немає атрибутів, наскільки я знаю.


1
Ти правий. І це також призведе до витоку пам'яті!
user1506104

1

у Котліні, розміщення 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
    }

Це просто обманювати підказку, щоб не виявити витік пам'яті, екземпляр програми все ще може створити витік пам'яті, оскільки клас програми та його нащадок є контекстом.

Крім того, ви можете використовувати функціональний інтерфейс або функціональні властивості, щоб допомогти вам отримати контекст програми.

Просто створіть клас об’єктів:

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

Сподіваюся, це допоможе.


Об'єктний клас цього основного помічника буде ініціалізований і може бути використаний через подальші дії на подальшому етапі? Вибачте, я новачок у котліні
доктор aNdRO

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

-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.