Использование контекста приложения везде?

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» и застрял на «как я могу получить контекст для одноэлементного?»
Кто-то где-то
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 .

snctln
источник
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.xinstance и X.yinstance равны нулю ; они являются нулевыми, потому что переменные X.xinstance и 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

Обратите внимание, что вы не можете переместить объявление статической переменной на одну строку выше, код не будет компилироваться.

18446744073709551615
источник
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()
Toha
источник
1
Склонен к утечкам памяти. Ты никогда не должен делать это.
Dragas
9

Вы пытаетесь создать оболочку для получения контекста приложения, и существует вероятность, что он может вернуть nullуказатель « ».

Насколько я понимаю, я думаю, что лучше подходить к любому из 2 Context.getApplicationContext() или Activity.getApplication().

Prasanta
источник
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.application уже гарантирует синглтон, так что в этом нет необходимости
Винсент
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
Сорен Бойсен