Jaka jest różnica między różnymi metodami uzyskania kontekstu?

390

W różnych fragmentach kodu Androida widziałem:

 public class MyActivity extends Activity {
    public void method() {
       mContext = this;    // since Activity extends Context
       mContext = getApplicationContext();
       mContext = getBaseContext();
    }
 }

Nie mogę jednak znaleźć żadnego przyzwoitego wyjaśnienia, które jest lepsze i w jakich okolicznościach należy je zastosować.

Doceniamy wskazówki dotyczące dokumentacji na ten temat oraz wskazówki dotyczące tego, co może się zepsuć, jeśli zostanie wybrany niewłaściwy.

Alnitak
źródło
2
Ten link może ci pomóc. Przejdź przez to ..
Aju

Odpowiedzi:

305

Zgadzam się, że dokumentacja w systemie Android jest niewielka, ale możesz zebrać kilka faktów z różnych źródeł.

Ten post na oficjalnym blogu Google Android dla programistów został napisany głównie po to, aby pomóc w usuwaniu wycieków pamięci, ale zawiera również dobre informacje o kontekstach:

W zwykłej aplikacji na Androida zazwyczaj masz dwa rodzaje kontekstu, działania i aplikacji.

Lektura tego artykułu mówi dalej o różnicy między nimi a tym, kiedy warto rozważyć użycie aplikacji Context ( Activity.getApplicationContext()) zamiast kontekstu Activity this). Zasadniczo kontekst aplikacji jest powiązany z aplikacją i zawsze będzie taki sam przez cały cykl życia aplikacji, ponieważ kontekst kontekstu jest związany z aktywnością i może zostać zniszczony wiele razy, ponieważ aktywność jest niszczona podczas zmian orientacji ekranu i taki.

Nie mogłem znaleźć naprawdę nic o tym, kiedy użyć getBaseContext () poza postem od Dianne Hackborn, jednego z inżynierów Google pracujących nad pakietem Android SDK:

Nie używaj getBaseContext (), po prostu użyj posiadanego kontekstu.

To było z postu w grupie dyskusyjnej dla programistów Androida , możesz też rozważyć zadanie pytania, ponieważ garstka osób pracujących na Androidzie monitoruje tę grupę i odpowiada na pytania.

Ogólnie rzecz biorąc, wydaje się, że lepiej jest używać globalnego kontekstu aplikacji, gdy jest to możliwe.

snctln
źródło
13
Kiedy mam działanie A, które może rozpocząć działanie B, które z kolei może ponownie uruchomić A z flagą CLEAR_TOP (i być może powtórzyć ten cykl wiele razy) - jakiego kontekstu powinienem użyć w tym przypadku, aby uniknąć tworzenia ogromnej ścieżki konteksty odniesienia? Diana mówi, że używa „tego” zamiast getBaseContext, ale potem ... przez większość czasu A będzie ponownie użyte, ale zdarzają się sytuacje, gdy zostanie utworzony nowy obiekt dla A, a następnie stary A przecieka. Wygląda więc na to, że getBaseContext jest najbardziej właściwym wyborem w większości przypadków. To nie jest jasne, dlaczego Don't use getBaseContext(). Czy ktoś może to wyjaśnić?
JBM
2
jak uzyskać dostęp do obiektu kontekstu wewnątrz klasy, która nie rozszerza działania?
Cole
1
@Cole, możesz stworzyć klasę, którą nazwiemy tutaj „ExampleClass”, której konstruktor pobiera obiekt Context i tworzy instancję zmiennej instancji klasy „appContext”. Następnie twoja klasa Activity (lub dowolna inna klasa w tym zakresie) może wywołać metodę ExampleClass, która korzysta ze zmiennej instancji „AppContext” przyklad.
Archie1986
54

Oto, co znalazłem na temat użycia context:

1). W obrębie Activitysamego siebie należy używać thisdo nadmuchiwania układów i menu, rejestrowania menu kontekstowych, tworzenia widgetów, inicjowania innych działań, tworzenia nowych Intentw obrębie Activitypreferencji, tworzenia instancji lub innych metod dostępnych w pliku Activity.

Nadmuchuj układ:

View mView = this.getLayoutInflater().inflate(R.layout.myLayout, myViewGroup);

Napompuj menu:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    this.getMenuInflater().inflate(R.menu.mymenu, menu);
    return true;
}

Zarejestruj menu kontekstowe:

this.registerForContextMenu(myView);

Utwórz widżet:

TextView myTextView = (TextView) this.findViewById(R.id.myTextView);

Rozpocznij Activity:

Intent mIntent = new Intent(this, MyActivity.class);
this.startActivity(mIntent);

Ustawienia preferencji:

SharedPreferences mSharedPreferences = this.getPreferenceManager().getSharedPreferences();

2). W przypadku klasy obejmującej całą aplikację należy użyć, getApplicationContext()ponieważ ten kontekst istnieje przez cały okres użytkowania aplikacji.

Pobierz nazwę bieżącego pakietu Androida:

public class MyApplication extends Application {    
    public static String getPackageName() {
        String packageName = null;
        try {
            PackageInfo mPackageInfo = getApplicationContext().getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), 0);
            packageName = mPackageInfo.packageName;
        } catch (NameNotFoundException e) {
            // Log error here.
        }
        return packageName;
    }
}

Powiąż klasę dla całej aplikacji:

Intent mIntent = new Intent(this, MyPersistent.class);
MyServiceConnection mServiceConnection = new MyServiceConnection();
if (mServiceConnection != null) {
    getApplicationContext().bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
}

3). W przypadku obiektów nasłuchujących i innych typów klas Androida (np. ContentObserver) użyj podstawienia kontekstu, takiego jak:

mContext = this;    // Example 1
mContext = context; // Example 2

gdzie thislub contextjest kontekstem klasy (działanie itp.).

Activity podstawienie kontekstu:

public class MyActivity extends Activity {
    private Context mContext;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        
        mContext = this;
    }
}

Podstawienie kontekstu słuchacza:

public class MyLocationListener implements LocationListener {
    private Context mContext;
    public MyLocationListener(Context context) {
        mContext = context;
    }
}

ContentObserver podstawienie kontekstu:

public class MyContentObserver extends ContentObserver {
    private Context mContext;
    public MyContentObserver(Handler handler, Context context) {
        super(handler);
        mContext = context;
    }
}

4). W przypadku BroadcastReceiver(w tym wbudowanego / osadzonego odbiornika) użyj własnego kontekstu odbiornika.

Zewnętrzne BroadcastReceiver:

public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (action.equals(Intent.ACTION_SCREEN_OFF)) {
            sendReceiverAction(context, true);
        }
        private static void sendReceiverAction(Context context, boolean state) {
            Intent mIntent = new Intent(context.getClass().getName() + "." + context.getString(R.string.receiver_action));
            mIntent.putExtra("extra", state);
            context.sendBroadcast(mIntent, null);
        }
    }
}

Wbudowany / osadzony BroadcastReceiver:

public class MyActivity extends Activity {
    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final boolean connected = intent.getBooleanExtra(context.getString(R.string.connected), false);
            if (connected) {
                // Do something.
            }
        }
    };
}

5). W przypadku Usług użyj własnego kontekstu usługi.

public class MyService extends Service {
    private BroadcastReceiver mBroadcastReceiver;
    @Override
    public void onCreate() {
        super.onCreate();
        registerReceiver();
    }
    private void registerReceiver() {
        IntentFilter mIntentFilter = new IntentFilter();
        mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
        this.mBroadcastReceiver = new MyBroadcastReceiver();
        this.registerReceiver(this.mBroadcastReceiver, mIntentFilter);
    } 
}

6). W przypadku Toastów zazwyczaj używaj getApplicationContext(), ale tam, gdzie to możliwe, używaj kontekstu przekazanego z działania, usługi itp.

Użyj kontekstu aplikacji:

Toast mToast = Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG);
mToast.show();

Użyj kontekstu przekazanego ze źródła:

public static void showLongToast(Context context, String message) {
    if (context != null && message != null) {
        Toast mToast = Toast.makeText(context, message, Toast.LENGTH_LONG);
        mToast.show();
    }
}

I na koniec, nie używaj getBaseContext()zgodnie z zaleceniami programistów platformy Android.

AKTUALIZACJA: Dodaj przykłady Contextużycia.

ChuongPham
źródło
1
Zamiast mContext można użyć OuterClass.this; zobacz komentarze w stackoverflow.com/questions/9605459/…
Paul Verest
3
+1 za tak pomocną odpowiedź! Zgadzam się, że zaakceptowana odpowiedź jest w porządku, jak odpowiedź zaakceptowana, ale cholera, ta odpowiedź była bardzo pouczająca! Dziękuję za te wszystkie przykłady, które pomogły mi lepiej zrozumieć użycie kontekstu jako całości. Skopiowałem nawet twoją odpowiedź do pliku tekstowego na moim komputerze jako odniesienie.
Ryan,
13

Przeczytałem ten wątek kilka dni temu, zadając sobie to samo pytanie. Moja decyzja po przeczytaniu tego była prosta: zawsze używaj applicationContext.

Jednak napotkałem z tym problem, spędziłem kilka godzin, aby go znaleźć, i kilka sekund, aby go rozwiązać ... (zmiana jednego słowa ...)

Używam LayoutInflater, aby nadmuchać widok zawierający Błystkę.

Oto dwie możliwości:

1)

    LayoutInflater layoutInflater = LayoutInflater.from(this.getApplicationContext());

2)

    LayoutInflater layoutInflater = LayoutInflater.from(this.getBaseContext());

Następnie robię coś takiego:

    // managing views part
    View view = ContactViewer.mLayoutInflater.inflate(R.layout.aViewContainingASpinner, theParentView, false);
    Spinner spinner = (Spinner) view.findViewById(R.id.theSpinnerId);
    String[] myStringArray = new String[] {"sweet","love"};

    // managing adapter part
    // The context used here don't have any importance -- both work.
    ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this.getApplicationContext(), myStringArray, android.R.layout.simple_spinner_item);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinner.setAdapter(adapter);

    theParentView.addView(view);

Co zauważyłem: jeśli utworzyłeś instancję liniową z aplikacjąContext, to kiedy klikniesz pokrętło w swojej działalności, otrzymasz nieprzechwycony wyjątek, pochodzący z maszyny wirtualnej dalvik (nie z twojego kodu, dlatego dużo wydałem czasu, aby dowiedzieć się, gdzie był mój błąd ...).

Jeśli użyjesz baseContext, to w porządku, otworzy się menu kontekstowe i będziesz mógł wybierać spośród wybranych opcji.

Oto mój wniosek: przypuszczam (nie testowałem tego dalej), że baseContext jest wymagany, gdy mamy do czynienia z kontekstowym menu w twojej aktywności ...

Test został zakończony kodowaniem za pomocą API 8 i przetestowany na HTC Desire, Android 2.3.3.

Mam nadzieję, że mój komentarz do tej pory cię nie nudził i życzę wszystkiego najlepszego. Miłego kodowania ;-)

Mav3656
źródło
Zawsze tego używałem, tworząc widoki w działaniu. Na tej podstawie, że jeśli działanie zostanie ponownie uruchomione, widoki zostaną przerobione i może powstanie nowy kontekst, z którego można ponownie utworzyć widoki. Wadą opublikowaną na blogu programistów jest to, że podczas gdy ImageView jest zblokowany, używana rysowalna / bitmapa może zawiesić się w tym kontekście. Niemniej jednak to właśnie robię w tej chwili. Jeśli chodzi o kod w innym miejscu aplikacji (zwykłe klasy), po prostu używam kontekstu aplikacji, ponieważ nie jest on specyficzny dla żadnej aktywności ani elementów interfejsu użytkownika.
JonWillis,
6

Po pierwsze, zgadzam się, że powinniśmy używać kontekstu aplikacji, gdy tylko jest to możliwe. następnie „to” w działaniu. nigdy nie potrzebowałem kontekstu podstawowego.

W moich testach w większości przypadków można je zamieniać. W większości przypadków chcesz uzyskać kontekst, aby uzyskać dostęp do plików, preferencji, bazy danych itp. Dane te są ostatecznie odzwierciedlane jako pliki w prywatnym folderze danych aplikacji (/ data / data /). Bez względu na to, z jakiego kontekstu korzystasz, zostaną zmapowane do tego samego folderu / plików, więc wszystko będzie w porządku.

Tak zaobserwowałem. Może są przypadki, w których powinieneś je rozróżnić.

samsonsu
źródło
Potrzebowałem kontekstu podstawowego, aby globalnie ustawić język aplikacji podczas uruchamiania (gdy nie jest zgodny z domyślnym językiem telefonu).
Tina
3

W niektórych przypadkach możesz użyć kontekstu działania zamiast kontekstu aplikacji podczas uruchamiania czegoś w wątku. Gdy wątek zakończy wykonywanie i musisz zwrócić wynik z powrotem do działania osoby wywołującej, potrzebujesz tego kontekstu za pomocą procedury obsługi.

((YourActivity) context).yourCallbackMethod(yourResultFromThread, ...);
Paweł
źródło
2

W prostych słowach

getApplicationContext()jak sugeruje nazwa metody, Twoja aplikacja będzie wiedzieć o szczegółach aplikacji, do których możesz uzyskać dostęp z dowolnego miejsca w aplikacji. Możesz więc skorzystać z tego w powiązaniu usługi, rejestracji transmisji itp. Application contextBędzie działać do momentu zamknięcia aplikacji.

getActivity()lub thissprawi, że Twoja aplikacja będzie wiedzieć o bieżącym ekranie, na którym widoczne są również szczegółowe informacje o poziomie aplikacji dostarczone przez application context. Wszystko, co chcesz wiedzieć o bieżącym ekranie Window ActionBar Fragementmanger, jest dostępne w tym kontekście. Zasadniczo i Activityrozszerz Context. Ten kontekst będzie żył, dopóki obecny składnik (działanie) nie będzie żywy

arjun
źródło
1

Zamieszanie wynika z faktu, że istnieje wiele sposobów dostępu do kontekstu, bez (na powierzchni) żadnych wyraźnych różnic. Poniżej znajdują się cztery najczęstsze sposoby uzyskania dostępu do kontekstu w działaniu.

getContext()
getBaseContext()
getApplicationContext()
getActionBar().getThemedContext() //new

Co to jest kontekst? Osobiście lubię myśleć o kontekście jako o stanie twojej aplikacji w dowolnym momencie. Kontekst aplikacji reprezentuje globalną lub podstawową konfigurację aplikacji, a Działanie lub Usługa może się na niej opierać i reprezentuje instancję konfiguracji aplikacji lub stan przechodni dla niej.

Jeśli spojrzysz na źródło dla android.content.Context, zobaczysz, że Kontekst jest klasą abstrakcyjną, a komentarze do klasy są następujące:

Interfejs do globalnych informacji o środowisku aplikacji. Jest to klasa abstrakcyjna, której implementację zapewnia system Android. Umożliwia dostęp do application-specificzasobów i klas, a także wezwania do application-levelwykonywania operacji, takich jak uruchamianie działań, nadawanie i przyjmowanie zamiarów itp. Biorę z tego to, że kontekst zapewnia wspólną implementację dostępu do poziomu aplikacji, a także poziomu systemu zasoby. Zasoby na poziomie aplikacji mogą uzyskiwać dostęp do takich zasobów jak zasoby [getResources()]lub zasoby String, a zasoby na [getAssets()]poziomie systemu to wszystko, do czego masz dostępContext.getSystemService().

W rzeczywistości spójrz na komentarze dotyczące metod i wydają się one potwierdzać to pojęcie:

getSystemService(): Zwróć uchwyt do system-levelusługi według nazwy. Klasa zwracanego obiektu różni się w zależności od żądanej nazwy. getResources(): Zwraca instancję zasobów dla pakietu aplikacji. getAssets(): Zwraca instancję zasobów dla pakietu aplikacji. Warto zauważyć, że w klasie abstrakcyjnej Context wszystkie powyższe metody są abstrakcyjne! Tylko jedna instancja getSystemService (Class) ma implementację, która wywołuje metodę abstrakcyjną. Oznacza to, że implementacja dla nich powinna być zapewniona głównie przez klasy implementacyjne, które obejmują:

ContextWrapper
Application
Activity
Service
IntentService

Patrząc na dokumentację API, hierarchia klas wygląda następująco:

Kontekst

| - ContextWrapper

| - - Zastosowanie

| - - ContextThemeWrapper

| - - - - Aktywność

| - - Usługa

| - - - IntentService

Ponieważ wiemy, że Contextsam w sobie nie daje wglądu, schodzimy w dół drzewa i przyglądamy się temu ContextWrapperi zdajemy sobie sprawę, że tam też nie ma wiele. Ponieważ aplikacja się rozszerza ContextWrapper, nie ma tam wiele do zobaczenia, ponieważ nie zastępuje ona implementacji dostarczonej przez ContextWrapper. Oznacza to, że implementacja kontekstu jest zapewniana przez system operacyjny i jest ukryta przed API. Możesz rzucić okiem na konkretną implementację Context, patrząc na źródło klasy ContextImpl.

Chanaka Weerasinghe
źródło
0

Użyłem tylko tego i getBaseContextpodczas opiekania z onClick(bardzo zielonego nooba na Javę i Androida). Używam tego, gdy mój pilot jest bezpośrednio w działaniu i muszę go użyć getBaseContextw anonimowym wewnętrznym pilocie. Zgaduję, że to w zasadzie podstęp getBaseContext, może przywraca kontekst działania, w którym ukrywa się klasa wewnętrzna.

Tony
źródło
1
To źle, zwraca podstawowy kontekst samego działania. Aby uzyskać działanie (to, którego chcesz użyć jako kontekst) od anonimowej klasy wewnętrznej, użyj czegoś takiego MyActivity.this. Używanie opisanego kontekstu podstawowego prawdopodobnie nie spowoduje problemów, ale jest złe.
nickmartens1980,