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

StackOverflow https://stackoverflow.com/questions/987072

  •  13-09-2019
  •  | 
  •  

Вопрос

В приложении для Android есть ли что-то неправильное в следующем подходе:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

и передавать его повсюду (например.SQLiteOpenHelper), где требуется контекст (и, конечно, не утечка)?

Это было полезно?

Решение

У этого подхода есть несколько потенциальных проблем, хотя во многих случаях (например, в вашем примере) он будет работать хорошо.

В частности, вам следует быть осторожными при работе со всем, что связано с GUI это требует Context.Например, если вы передаете контекст приложения в LayoutInflater вы получите исключение.В общем, ваш подход превосходен:рекомендуется использовать Activity's Context внутри этого Activity, и Application Context при передаче контекста, выходящего за рамки Activity к избежать утечек памяти.

Кроме того, в качестве альтернатива к вашему шаблону вы можете использовать ярлык вызова getApplicationContext() на Context объект (например, Activity), чтобы получить контекст приложения.

Другие советы

По моему опыту, в таком подходе нет необходимости.Если вам нужен контекст для чего-либо, вы обычно можете получить его, вызвав Просмотр.getContext() и используя Context Получив там, вы можете позвонить Контекст.getApplicationContext() чтобы получить Application контекст.Если вы пытаетесь получить Application контекст это из Activity ты всегда можешь позвонить Активность.getApplication() который должен быть передан как Context нужен для звонка SQLiteOpenHelper().

В целом, похоже, что ваш подход к этой ситуации не представляет проблемы, но когда вы имеете дело с Context просто убедитесь, что у вас нигде нет утечки памяти, как описано на официальном сайте. Блог разработчиков Google Android.

Некоторые люди спрашивали: как синглтон может вернуть нулевой указатель?Я отвечаю на этот вопрос.(Я не могу ответить в комментарии, потому что мне нужно опубликовать код.)

Он может возвращать значение null между двумя событиями:(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.экземпляр были прочитаны, когда они были нулевыми.

Можно ли это исправить?Да,

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

НО это не вариант для Android Application объект:программист не контролирует время его создания.

Снова:разница между первым примером и вторым заключается в том, что второй пример создает экземпляр, если статический указатель равен нулю.Но программист не может создать тот Объект приложения 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

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

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

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

Класс приложения:

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

Это хороший подход.Я тоже им пользуюсь.Я бы только предложил переопределить onCreate чтобы установить синглтон вместо использования конструктора.

И поскольку вы упомянули SQLiteOpenHelperonCreate () вы также можете открыть базу данных.

Лично я думаю, что документация ошибается, говоря, что Обычно нет необходимости создавать подкласс Application.Я думаю, что все наоборот:Вы всегда должны создавать подклассы Application.

Я бы использовал контекст приложения, чтобы получить системную службу в конструкторе.Это упрощает тестирование и дает преимущества от композиции.

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 будет использовать конструктор по умолчанию.

Мне это нравится, но вместо этого я бы предложил синглтон:

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

Я использую тот же подход, предлагаю написать синглтон немного лучше:

public static MyApp getInstance() {

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

    return instance;
}

но я не везде использую, я использую getContext() и getApplicationContext() где я могу это сделать!

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top