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

В приложении Android есть что-то не так с таким подходом:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

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

11 ответов

Решение

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

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

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

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

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

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

Он может возвращать ноль между двумя событиями: (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

НО это не вариант для 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

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

Класс применения:

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

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

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

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

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

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

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

Статическое определение контекста приведет к утечке памяти

Стандартный способ получить контекст везде:

public class App extends Application {

    public static transient SoftReference<Context> contextReference;

    @Override
    public void onCreate() {
        super.onCreate();
        contextReference = new SoftReference<Context>(getApplicationContext());
    }
}

Таким образом, вы будете иметь контекст где-нибудь в коде, как это:

App.contextReference.get();

Любой другой способ снизит производительность и вызовет утечку памяти

Надеюсь быть полезным...

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

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

Я знаю, что исходный вопрос был опубликован 13 лет назад, и это версия Kotlin для получения контекста повсюду.

      class MyApplication : Application() {
    companion object {
        @JvmStatic
        private var instance: MyApplication? = null

        @JvmStatic
        public final fun getContext(): Context? {
            return instance
        }
    }

    override fun onCreate() {
        instance = this
        super.onCreate()
    }
}

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

public static MyApp getInstance() {

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

    return instance;
}

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

Другие вопросы по тегам