Korzystasz z kontekstu aplikacji wszędzie?

476

Czy w aplikacji na Androida jest coś nie tak z następującym podejściem:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

i przekazać go wszędzie (np. SQLiteOpenHelper) tam, gdzie wymagany jest kontekst (i oczywiście nie przecieka)?

Janchenko
źródło
23
Wystarczy, aby opracować dla innych wykonawczych ten można następnie modyfikować <application>węzeł pliku AndroidManifest.xml zawierać następującą definicję atrybutu: android:name="MyApp". Moja aplikacja musi znajdować się w tym samym pakiecie, do którego odwołują się twoje manifesty.
Matt Huggins,
6
NIESAMOWITY sposób na obejście problemu dostarczenia kontekstu do SQLiteOpenHelper !! Wdrożyłem singleton „SQLiteManager” i utknąłem w „jak F mogę uzyskać kontekst do singletonu?”
Ktoś gdzieś
8
Właśnie dlatego wiesz, że zwracasz aplikację przez jeden z jej super interfejsów, więc jeśli podasz dodatkowe metody w MyApp, nie będziesz mógł ich używać. Twoja metoda getContext () powinna zamiast tego mieć typ zwracany przez MyApp, dzięki czemu możesz używać metod dodanych później, a także wszystkich metod w ContextWrapper i Context.
5
Zobacz także goo.gl/uKcFn - to kolejna odpowiedź związana z podobnym postem. Lepiej ustaw zmienną statyczną w onCreate, a nie c'tor.
AlikElzin-kilaka
1
@ChuongPham Jeśli środowisko zabiło twoją aplikację, nic nie uzyska dostępu do kontekstu zerowego ...
Kevin Krumwiede

Odpowiedzi:

413

Istnieje kilka potencjalnych problemów z tym podejściem, chociaż w wielu okolicznościach (takich jak twój przykład) będzie działać dobrze.

W szczególności powinieneś zachować ostrożność, mając do czynienia ze wszystkim, co dotyczy tego, GUIco wymaga Context. Na przykład, jeśli przekażesz kontekst aplikacji do niego LayoutInflater, otrzymasz wyjątek. Ogólnie rzecz biorąc, twoje podejście jest doskonałe: dobrą praktyką jest użycie Activity's Contextwewnątrz tego Activity, a Application Contextkiedy przekazujesz kontekst poza zakresActivity aby uniknąć wycieków pamięci .

Ponadto, jako alternatywa dla Państwa wzoru można użyć skrótu dzwoniąc getApplicationContext()na Contextobiekt (taki jak aktywny), aby uzyskać kontekstu aplikacji.

Reto Meier
źródło
22
Dzięki za inspirującą odpowiedź. Myślę, że zastosuję to podejście wyłącznie do warstwy trwałości (ponieważ nie chcę iść z dostawcami treści). Zastanawiasz się, co było motywacją do zaprojektowania SQLiteOpenHelper w sposób, który oczekuje dostarczenia kontekstu zamiast pobierania go od samej aplikacji. PS A twoja książka jest świetna!
yanchenko
7
Korzystanie z kontekstu aplikacji LayoutInflatorwłaśnie mi działało. Musiał zostać zmieniony w ciągu ostatnich trzech lat.
Jacob Phillips
5
@JacobPhillips Korzystanie z LayoutInflator bez kontekstu działania spowoduje pominięcie stylu tego działania. To działałoby w pewnym sensie, ale nie w innym.
Mark
1
@MarkCarter Czy masz na myśli, że użycie kontekstu aplikacji spowoduje utratę stylu działania?
Jacob Phillips
1
@JobobPhillips tak, kontekst aplikacji nie może mieć stylu, ponieważ każda czynność może być stylizowana w inny sposób.
Mark
28

Z mojego doświadczenia wynika, że ​​takie podejście nie powinno być konieczne. Jeśli potrzebujesz kontekstu do czegokolwiek, zwykle możesz go uzyskać przez wywołanie View.getContext () i używając Contextuzyskanego tam możesz wywołać Context.getApplicationContext (), aby uzyskać Applicationkontekst. Jeśli próbujesz uzyskać Applicationkontekst z tego Activity, zawsze możesz wywołać Activity.getApplication (), które powinno być możliwe do przekazania jako Contextpotrzebne do wywołania SQLiteOpenHelper().

Ogólnie rzecz biorąc, wydaje się, że nie ma problemu z twoim podejściem do tej sytuacji, ale radząc sobie z Contexttym, upewnij się, że nigdzie nie tracisz pamięci, jak opisano na oficjalnym blogu Google Android Developers .

snctln
źródło
13

Niektórzy pytają: jak singleton może zwrócić wskaźnik zerowy? Odpowiadam na to pytanie. (Nie mogę odpowiedzieć w komentarzu, ponieważ muszę wysłać kod).

Może zwracać wartość null pomiędzy dwoma zdarzeniami: (1) klasa jest ładowana i (2) tworzony jest obiekt tej klasy. Oto przykład:

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

Uruchommy kod:

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

Drugi wiersz pokazuje, że instancja Y.xinstance i X.yinstance ma wartość NULL ; są one puste, ponieważ zmienne X.xinstance i Y.yinstance zostały odczytane, gdy były puste.

Czy można to naprawić? Tak,

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

a ten kod nie wykazuje anomalii:

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

ALE nie jest to opcja dla Applicationobiektu z Androidem : programista nie kontroluje czasu jego utworzenia.

Jeszcze raz: różnica między pierwszym przykładem a drugim polega na tym, że drugi przykład tworzy instancję, jeśli wskaźnik statyczny ma wartość NULL. Ale programista nie może tworzyć z Androidem obiektu aplikacji zanim system zdecyduje się to zrobić.

AKTUALIZACJA

Jeszcze jeden zagadkowy przykład, w którym znajdują się zainicjowane pola statyczne 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());
    }
}

I dostajesz:

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

Pamiętaj, że nie można przenieść deklaracji zmiennej statycznej o jeden wiersz wyżej, kod nie zostanie skompilowany.

18446744073709551615
źródło
3
Przydatny przykład; dobrze wiedzieć, że jest taka dziura. Odchodzę od tego, że należy unikać odwoływania się do takiej zmiennej statycznej podczas statycznej inicjalizacji dowolnej klasy.
ToolmakerSteve
10

Klasa zastosowania:

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

}

Zadeklaruj aplikację w AndroidManifest:

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

Stosowanie:

MyApplication.getAppContext()
toha
źródło
1
Podatne na wycieki pamięci. Nigdy nie powinieneś tego robić.
Dragas,
9

Próbujesz utworzyć opakowanie, aby uzyskać kontekst aplikacji, ale istnieje możliwość, że zwróci on nullwskaźnik.

Zgodnie z moim rozumieniem, wydaje mi się, że jest to lepsze podejście do call-any z 2 Context.getApplicationContext() lub Activity.getApplication().

Prasanta
źródło
13
kiedy powinien zwrócić null?
Utknął
25
Nie ma żadnej statycznej metody Context.getApplicationContext (), o której wiem. Czy coś brakuje?
dalcantara
Implementuję również to samo podejście w mojej aplikacji, ale podczas wywoływania w SQLiteOpenHelper zwraca wskaźnik zerowy. Jakakolwiek odpowiedź na tego rodzaju sytuację.
ashutosh
2
Może tak być w przypadku wywołania SQLiteOpenHelper w dostawcy treści, który jest ładowany przed aplikacją.
Gunnar Bernstein
5

To dobre podejście. Ja też go używam. Sugerowałbym jedynie przesłonięcie, onCreateaby ustawić singleton zamiast używania konstruktora.

A skoro wspomniałeś SQLiteOpenHelper: WonCreate () możesz również otworzyć bazę danych.

Osobiście uważam, że dokumentacja niesłusznie mówi, że zwykle nie ma potrzeby podklasy aplikacji . Myślę, że jest odwrotnie: zawsze należy podklasować aplikację.

Jaskółka oknówka
źródło
3

Chciałbym użyć kontekstu aplikacji, aby uzyskać konstruktor usługi systemowej. Ułatwia to testowanie i zapewnia korzyści ze składu

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

}

Klasa testowa użyłaby wtedy przeciążonego konstruktora.

Android użyłby domyślnego konstruktora.

Blundell
źródło
1

Podoba mi się, ale zamiast tego zaproponowałbym singleton:

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;
    }
}
Franklin Peña
źródło
31
Rozszerzenie aplikacji android.app. gwarantuje już singleton, więc nie jest to konieczne
Vincent
8
Co zrobić, jeśli chcesz uzyskać dostęp do zajęć niezwiązanych z aktywnością?
Maxrunner,
9
Nigdy nie powinieneś newsam aplikować (z możliwym wyjątkiem testów jednostkowych). System operacyjny to zrobi. Nie powinieneś także mieć konstruktora. Po to onCreatejest.
Martin
@Vincent: czy możesz zamieścić link na ten temat? najlepiej kod - pytam tutaj: stackoverflow.com/questions/19365797/…
Mr_and_Mrs_D
@radzio dlaczego nie powinniśmy tego robić w konstruktorze?
Miha_x64
1

Używam tego samego podejścia, proponuję napisać singleton nieco lepiej:

public static MyApp getInstance() {

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

    return instance;
}

ale nie używam wszędzie, używam getContext()i getApplicationContext()gdzie mogę to zrobić!

Serafini
źródło
Napisz komentarz, aby wyjaśnić, dlaczego głosowałeś za odpowiedzią, abym mógł zrozumieć. Podejście singletonowe jest szeroko stosowane, aby uzyskać prawidłowy kontekst poza zajęciami lub widokami ciała ...
Seraphim's
1
Nie ma potrzeby, ponieważ system operacyjny zapewnia, że ​​aplikacja zostanie utworzona dokładnie raz. Jeśli tak, sugerowałbym ustawienie Singelton w onCreate ().
Martin
1
Dobry, bezpieczny dla wątków sposób na leniwe zainicjowanie singletonu, ale tutaj nie jest to konieczne.
naXa
2
Wow, po prostu, kiedy myślałem, że ludzie w końcu zatrzymał się przy użyciu Blokada z podwójnym zatwierdzeniem ... cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Søren Boisen