Використання контексту програми скрізь?


476

У додатку для Android чи не трапиться такий підхід:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

і передавати його скрізь (наприклад, SQLiteOpenHelper), де потрібен контекст (і не протікає звичайно)?


23
Просто розробити для інших виконавців цього, ви можете змінити <application>вузол файлу AndroidManifest.xml включити наступне визначення атрибута: android:name="MyApp". MyApp має бути в тому ж пакеті, що і ваші маніфестні посилання.
Метт Хаггінс

6
ДУМОВИЙ спосіб подолати проблему надання контексту SQLiteOpenHelper !! Я реалізував одиночний "SQLiteManager" і застряг у "як F я отримую контекст для одиночного"?
Хтось десь

8
Так що ви знаєте, що ви повертаєте свою програму через один із її супер-інтерфейсів, тож якби ви надали додаткові методи в MyApp, ви не змогли б ними користуватися. Натомість ваш getContext () повинен мати тип повернення MyApp, і таким чином ви можете використовувати методи, додані пізніше, а також усі методи ContextWrapper і Context.

5
Дивіться також goo.gl/uKcFn - це ще одна відповідь, пов’язана з подібним дописом . Краще встановити статичну змінну в onCreate, а не c'tor.
АлікЕльзін-кілака

1
@ChuongPham Якщо рамка знищила вашу програму, нічого не буде
доступно

Відповіді:


413

Є кілька можливих проблем із таким підходом, хоча за багатьох обставин (наприклад, вашого прикладу) він буде добре працювати.

Зокрема, ви повинні бути обережними, коли маєте справу з тим, що стосується того, GUIщо вимагає Context. Наприклад, якщо ви передасте програму "Контекст", LayoutInflaterви отримаєте Виняток. Взагалі кажучи, ваш підхід чудовий: це хороша практика використовувати Activity's Contextвсередині цього Activity, а також, Application Contextколи ви переходите контекст за межі рамки, Activityщоб уникнути витоку пам'яті .

Крім того , в якості альтернативи до вашого шаблоном ви можете використовувати ярлик виклику getApplicationContext()на Contextоб'єкт (наприклад, активність) , щоб отримати контекст додатків.


22
Дякую за натхненну відповідь. Я думаю, що я використовуватиму цей підхід виключно для шару стійкості (оскільки я не хочу спілкуватися з постачальниками контенту). Цікаво, якою була мотивація проектування SQLiteOpenHelper таким чином, що очікує, що Контекст буде наданий замість того, щоб отримувати його у самої програми. PS І ваша книга чудова!
Янченко

7
Використання контексту програми із LayoutInflatorлише що працювали для мене. Потрібно змінити протягом останніх трьох років.
Джейкоб Філіпс

5
@JacobPhillips Використання LayoutInflator без контексту діяльності пропустить стилізацію цієї діяльності. Так це працювало б в одному сенсі, а не в іншому.
Марк

1
@MarkCarter Ви маєте на увазі, що за допомогою контексту програми пропустять стилі діяльності?
Джейкоб Філіпс

1
@JacobPhillips так, контекст програми не може мати стилі, оскільки кожна діяльність може бути стилізована по-іншому.
Марк

28

На мій досвід, такий підхід не повинен бути необхідним. Якщо вам потрібен контекст для будь-чого, зазвичай ви можете отримати його за допомогою виклику View.getContext () і за допомогою Contextотриманого там ви можете зателефонувати Context.getApplicationContext (), щоб отримати Applicationконтекст. Якщо ви намагаєтеся отримати Applicationконтекст із цього, Activityви завжди можете зателефонувати Activity.getApplication (), який повинен бути спроможний передатись у міру Contextнеобхідності для дзвінка SQLiteOpenHelper().

В цілому, мабуть, не існує проблеми з вашим підходом до цієї ситуації, але, працюючи з Contextпросто переконайтеся, що ви нікуди не просочуєтесь пам’яттю, як описано в офіційному блозі розробників Google Android .


13

Деякі люди запитують: як однотонний може повернути нульовий покажчик? Я відповідаю на це питання. (Я не можу відповісти в коментарі, оскільки мені потрібно розмістити код.)

Він може повернути нуль між двома подіями: (1) клас завантажений, і (2) створений об'єкт цього класу. Ось приклад:

class X {
    static X xinstance;
    static Y yinstance = Y.yinstance;
    X() {xinstance=this;}
}
class Y {
    static X xinstance = X.xinstance;
    static Y yinstance;
    Y() {yinstance=this;}
}

public class A {
    public static void main(String[] p) {
    X x = new X();
    Y y = new Y();
    System.out.println("x:"+X.xinstance+" y:"+Y.yinstance);
    System.out.println("x:"+Y.xinstance+" y:"+X.yinstance);
    }
}

Запустимо код:

$ javac A.java 
$ java A
x:X@a63599 y:Y@9036e
x:null y:null

Другий рядок показує, що Y.xin substance та X.yinstance є нульовими ; вони є недійсними, оскільки змінні X.xinstance ans Y.yinstance були прочитані, коли вони були нульовими.

Це можна виправити? так,

class X {
    static Y y = Y.getInstance();
    static X theinstance;
    static X getInstance() {if(theinstance==null) {theinstance = new X();} return theinstance;}
}
class Y {
    static X x = X.getInstance();
    static Y theinstance;
    static Y getInstance() {if(theinstance==null) {theinstance = new Y();} return theinstance;}
}

public class A {
    public static void main(String[] p) {
    System.out.println("x:"+X.getInstance()+" y:"+Y.getInstance());
    System.out.println("x:"+Y.x+" y:"+X.y);
    }
}

і цей код не показує аномалії:

$ javac A.java 
$ java A
x:X@1c059f6 y:Y@152506e
x:X@1c059f6 y:Y@152506e

АЛЕ це не варіант для Applicationоб’єкта Android : програміст не контролює час його створення.

Ще раз: різниця між першим прикладом і другим полягає в тому, що другий приклад створює екземпляр, якщо статичний покажчик є нульовим. Але програміст не може створити на об'єкт Android програми , перш ніж система вирішує зробити це.

ОНОВЛЕННЯ

Ще один дивний приклад, коли трапляються ініціалізовані статичні поля null.

Main.java :

enum MyEnum {
    FIRST,SECOND;
    private static String prefix="<", suffix=">";
    String myName;
    MyEnum() {
        myName = makeMyName();
    }
    String makeMyName() {
        return prefix + name() + suffix;
    }
    String getMyName() {
        return myName;
    }
}
public class Main {
    public static void main(String args[]) {
        System.out.println("first: "+MyEnum.FIRST+" second: "+MyEnum.SECOND);
        System.out.println("first: "+MyEnum.FIRST.makeMyName()+" second: "+MyEnum.SECOND.makeMyName());
        System.out.println("first: "+MyEnum.FIRST.getMyName()+" second: "+MyEnum.SECOND.getMyName());
    }
}

І ви отримуєте:

$ javac Main.java
$ java Main
first: FIRST second: SECOND
first: <FIRST> second: <SECOND>
first: nullFIRSTnull second: nullSECONDnull

Зауважте, що ви не можете перемістити декларацію статичної змінної на один рядок вгорі, код не буде компілюватися.


3
Корисний приклад; добре знати, що є така дірка. Що я забираю від цього, це те, що слід уникати посилань на таку статичну змінну під час статичної ініціалізації будь-якого класу.
ToolmakerSteve

10

Клас застосування:

import android.app.Application;
import android.content.Context;

public class MyApplication extends Application {

    private static Context mContext;

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

    public static Context getAppContext() {
        return mContext;
    }

}

Декларуйте програму в AndroidManifest:

<application android:name=".MyApplication"
    ...
/>

Використання:

MyApplication.getAppContext()

1
Схильний до витоку пам'яті. Ти ніколи цього не повинен робити.
Драгас

9

Ви намагаєтеся створити обгортку, щоб отримати контекст програми, і є ймовірність, що він може повернути " null" покажчик.

Згідно з моїм розумінням, я думаю, що його кращий підхід до виклику - будь-який із 2 Context.getApplicationContext() або Activity.getApplication().


13
коли він повинен повернути нуль?
Застряг

25
Не існує статичного методу Context.getApplicationContext (), про який я знаю. Я щось пропускаю?
далькантара

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

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

5

Це хороший підхід. Я і сам його використовую. Я б тільки запропонував переосмислитиonCreate щоб встановити синглтон замість використання конструктора.

А відколи ви згадали SQLiteOpenHelper: ВonCreate () вас також можна відкрити базу даних.

Особисто я вважаю, що документація помилилася, сказавши, що зазвичай немає необхідності в підкласі Application . Я думаю, що це навпаки: Ви завжди повинні підклас Application.


3

Я використовував би Application Context, щоб отримати системну службу в конструкторі. Це полегшує тестування та користь композиції

public class MyActivity extends Activity {

    private final NotificationManager notificationManager;

    public MyActivity() {
       this(MyApp.getContext().getSystemService(NOTIFICATION_SERVICE));
    }

    public MyActivity(NotificationManager notificationManager) {
       this.notificationManager = notificationManager;
    }

    // onCreate etc

}

Тестовий клас використовував би перевантажений конструктор.

Android використовуватиме конструктор за замовчуванням.


1

Мені це подобається, але я б запропонував замість цього сингл:

package com.mobidrone;

import android.app.Application;
import android.content.Context;

public class ApplicationContext extends Application
{
    private static ApplicationContext instance = null;

    private ApplicationContext()
    {
        instance = this;
    }

    public static Context getInstance()
    {
        if (null == instance)
        {
            instance = new ApplicationContext();
        }

        return instance;
    }
}

31
Розширення програми для android.app. вже гарантує синглтон, тому це зайве
Вінсент

8
Що робити, якщо ви хочете отримати доступ від занять без занять?
Maxrunner

9
Ніколи не слід newзастосовувати програму самостійно (за винятком можливого тестування одиниць). Операційна система зробить це. У вас також не повинно бути конструктора. Це те onCreate, для чого.
Мартін

@Vincent: чи можете ви опублікувати якесь посилання на це? переважно код - Я питаю тут: stackoverflow.com/questions/19365797 / ...
Mr_and_Mrs_D

@radzio чому ми не повинні цього робити в конструкторі?
Miha_x64

1

Я використовую той самий підхід, пропоную написати сингл трохи краще:

public static MyApp getInstance() {

    if (instance == null) {
        synchronized (MyApp.class) {
            if (instance == null) {
                instance = new MyApp ();
            }
        }
    }

    return instance;
}

але я не використовую всюди, я використовую getContext()і getApplicationContext()де можу це зробити!


Тож напишіть коментар, щоб пояснити, чому ви спростували відповідь, щоб я міг зрозуміти. Однотонний підхід широко використовується, щоб отримати дійсний контекст поза діяльністю чи поглядами ...
Серафім

1
Немає необхідності, оскільки операційна система гарантує, що додаток буде інстанційовано рівно один раз. Якщо є, я б запропонував встановити Singelton в onCreate ().
Мартін

1
Хороший безпечний для ниток спосіб ініціалізувати одиноку, але тут не потрібний.
naXa

2
Нічого собі, коли я подумав, що люди нарешті перестали використовувати подвійну перевірку блокування ... cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Søren Boisen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.