Ostrzeżenie: nie umieszczaj klas kontekstu systemu Android w polach statycznych; to jest wyciek pamięci (a także przerywa Instant Run)

84

Android Studio:

Nie umieszczaj klas kontekstu systemu Android w polach statycznych; to jest wyciek pamięci (a także przerywa Instant Run)

A więc 2 pytania:

# 1 Jak wywołać a startServicez metody statycznej bez zmiennej statycznej dla kontekstu?
# 2 Jak wysłać transmisję lokalną metodą statyczną (taką samą)?

Przykłady:

public static void log(int iLogLevel, String sRequest, String sData) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(mContext, LogService.class);
        intent.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        mContext.startService(intent);
    }
}

lub

        Intent intent = new Intent(MAIN_ACTIVITY_RECEIVER_INTENT);
        intent.putExtra(MAIN_ACTIVITY_REQUEST_FOR_UPDATE, sRequest));
        intent.putExtra(MAIN_ACTIVITY_DATA_FOR_VIEW, sData);
        intent.putExtra(MAIN_ACTIVITY_LOG_LEVEL, iLogLevel);
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);

Jaki byłby właściwy sposób na zrobienie tego bez używania mContext?

UWAGA: Myślę, że moim głównym pytaniem może być przekazanie kontekstu do klasy, z której pochodzi metoda wywołująca.

John Smith
źródło
Nie możesz przekazać Context jako parametru w metodzie?
Juan Cruz Soler
Nazwałbym tę rutynę w miejscach, które również nie miałyby kontekstu.
John Smith,
# 1 przekaż to jako parametr # 2 to samo.
njzk2
Następnie musisz przekazać kontekst również do metody wywołującej. Problem polega na tym, że pola statyczne nie są zbierane jako śmieci, więc możesz wyciekać działanie ze wszystkimi jego widokami
Juan Cruz Soler
1
@JohnSmith Kaskaduj go od czynności inicjującej (poprzez parametry konstruktora lub parametry metody) aż do momentu, w którym tego potrzebujesz.
AndroidMechanic - Viral Patel

Odpowiedzi:

56

Po prostu przekaż go jako parametr do swojej metody. Nie ma sensu tworzenie statycznej instancji Contextwyłącznie w celu uruchomienia Intent.

Oto jak powinna wyglądać Twoja metoda:

public static void log(int iLogLevel, String sRequest, String sData, Context ctx) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(ctx, LogService.class);
        intent1.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        ctx.startService(intent);
    }
}

Aktualizacja po komentarzach do pytania: kaskadowo kontekst od czynności inicjującej (poprzez parametry konstruktora lub parametry metody) aż do momentu, w którym jest to potrzebne.

AndroidMechanic - Viral Patel
źródło
Czy możesz podać przykład konstruktora?
John Smith
jeśli nazwa Twojej klasy to MyClassdodaj konstruktora publicznego, tak jak metodę public MyClass(Context ctx) { // put this ctx somewhere to use later }(To jest twój konstruktor) Teraz utwórz nową instancję MyClassużywając tego konstruktora np.MyClass mc = new MyClass(ctx);
AndroidMechanic - Viral Patel
Nie sądzę, aby przekazywanie na żądanie było tak proste. Chociaż istnieją oczywiste korzyści, takie jak brak martwienia się o nieaktualny kontekst lub jak tutaj, statyczny. Powiedzmy, że potrzebujesz kontekstu [być może chcesz napisać do prefs] w wywołaniu zwrotnym odpowiedzi, które zostanie wywołane asynchronicznie. Więc czasami jesteś zmuszony umieścić go w polu członka. A teraz musisz pomyśleć, jak nie uczynić tego statycznym. stackoverflow.com/a/40235834/2695276 wydaje się działać.
Rajat Sharma
1
Czy można używać ApplicationContext jako pola statycznego? W przeciwieństwie do działań obiekt aplikacji nie ulega zniszczeniu, prawda?
NeoWang
50

Po prostu upewnij się, że przekazujesz context.getApplicationContext () lub wywołujesz getApplicationContext () w dowolnym kontekście, który jest przekazywany za pośrednictwem metod / konstruktora do Twojego singletona, jeśli zdecydujesz się przechowywać go w dowolnym polu składowym.

idiotyczny przykład (nawet jeśli ktoś przejdzie w działaniu, pobierze kontekst aplikacji i użyje go do utworzenia instancji singletona):

public static synchronized RestClient getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new RestClient(context.getApplicationContext());
    }
    return mInstance;
}

getApplicationContext () zgodnie z dokumentacją: "Zwróć kontekst pojedynczego, globalnego obiektu Application bieżącego procesu."

Oznacza to, że kontekst zwrócony przez "getApplicationContext ()" przejdzie przez cały proces i dlatego nie ma znaczenia, czy przechowujesz do niego statyczne odniesienie w dowolnym miejscu, ponieważ zawsze będzie tam w czasie wykonywania Twojej aplikacji (i przeżyje wszystkie obiekty / singletony utworzone przez to).

Porównaj to z kontekstem wewnątrz widoków / działań zawierających duże ilości danych, jeśli wyciekniesz kontekst utrzymywany przez działanie, system nie będzie w stanie zwolnić tego zasobu, co oczywiście nie jest dobre.

Odwołanie do działania poprzez jego kontekst powinno przeżywać ten sam cykl życia, co samo działanie, w przeciwnym razie będzie utrzymywać kontekst jako zakładnika powodując wyciek pamięci (co jest przyczyną ostrzeżenia o kłaczkach).

EDYCJA: Dla faceta, który bije przykład z powyższych dokumentów, w kodzie jest nawet sekcja komentarzy o tym, o czym właśnie napisałem:

    // getApplicationContext() is key, it keeps you from leaking the
    // Activity or BroadcastReceiver if someone passes one in.
Marcus Gruneau
źródło
8
do faceta, który uderzył w faceta, który uderzył w powyższy przykład: celem tego wątku jest ostrzeżenie Lint w konflikcie z zalecanym przez Google wzorcem tworzenia singletona.
Raphael C
7
Przeczytaj: „Nie umieszczaj klas kontekstu systemu Android w polach statycznych; jest to wyciek pamięci (a także przerywa działanie funkcji Instant Run)”. Czy wiesz, jakie są klasy kontekstu? Aktywność jest jedną z nich i nie powinieneś przechowywać Aktywności jako pola statycznego, jak sam siebie opisałeś (w przeciwnym razie wycieknie pamięć). Możesz jednak przechowywać Context (o ile jest to kontekst aplikacji) jako pole statyczne, ponieważ przeżywa wszystko. (I w ten sposób zignoruj ​​ostrzeżenie). Jestem pewien, że możemy się zgodzić co do tego prostego faktu, prawda?
Marcus Gruneau
jako weterynarz iOS, w moim pierwszym tygodniu z Androidem ... Wyjaśnienia takie jak te pomagają mi zrozumieć ten nonsens w kontekście ... Więc to ostrzeżenie o kłaczkach (och, jak nie lubię żadnych ostrzeżeń) będzie się kręcić, ale Twoja odpowiedź rozwiązuje prawdziwy problem .
eric
@Marcus, jeśli twoja klasa podrzędna nie jest świadoma tego, kto tworzy jej instancję z jakim kontekstem, to po prostu złą praktyką jest przechowywanie jej jako statycznego elementu członkowskiego. ponadto kontekst aplikacji żyje jako część obiektu aplikacji aplikacji, obiekt aplikacji nie pozostanie w pamięci na zawsze, zostanie zabity. wbrew powszechnemu przekonaniu aplikacja nie zostanie uruchomiona ponownie od zera. Android utworzy nowy obiekt Application i rozpocznie działanie, na którym wcześniej znajdował się użytkownik, dając złudzenie, że aplikacja nigdy nie została zabita.
Raphael C
@RaphaelC czy masz taką dokumentację? Wygląda na to, że jest to całkowicie błędne, ponieważ Android zapewnia tylko jeden kontekst aplikacji na przebieg każdego procesu.
HaydenKai
6

To tylko ostrzeżenie. Nie martw się. Jeśli chcesz użyć kontekstu aplikacji, możesz zapisać go w klasie „pojedynczej”, która jest używana do zapisywania całej klasy pojedynczej w Twoim projekcie.

Licat Julius
źródło
2

W twoim przypadku nie ma sensu mieć go jako pola statycznego, ale nie sądzę, że jest to złe we wszystkich przypadkach. Jeśli teraz, co robisz, możesz mieć pole statyczne, które ma kontekst i wyzerować je później. Tworzę instancję statyczną dla mojej głównej klasy modelu, która ma kontekst wewnątrz, kontekst aplikacji, a nie kontekst działania, a także mam pole wystąpienia statycznego klasy zawierające aktywność, które null in przy niszczeniu. Nie widzę, że mam wyciek pamięci. Więc jeśli jakiś sprytny facet uważa, że ​​się mylę, śmiało skomentuj ...

Również Instant Run działa tutaj dobrze ...

Renetik
źródło
Nie sądzę, że mylisz się co do zasady, ale musisz uważać, aby działanie, o którym mówisz, miało maksymalnie jedną instancję w danym momencie, zanim będzie mogło używać pól statycznych. Jeśli Twoja aplikacja kończy się z więcej niż jednym stosem wstecznym, ponieważ można ją uruchomić z różnych miejsc (powiadomienie, głębokie linkowanie, ...), coś pójdzie nie tak, chyba że użyjesz w manifeście flagi, takiej jak singleInstance. Dlatego zawsze łatwiej jest unikać pól statycznych z działań.
BladeCoder
android: launchMode = "singleTask" powinno wystarczyć, więc przechodzę na to, użyłem singleTop, ale nie wiedziałem, że to za mało, ponieważ chcę zawsze mieć tylko pojedyncze wystąpienia moich głównych działań, tak są projektowane moje aplikacje.
Renetik
2
„singleTask” gwarantuje tylko jedno wystąpienie na zadanie. Jeśli Twoja aplikacja ma wiele punktów wejścia, takich jak głębokie linki lub uruchamianie jej z powiadomienia, możesz skończyć z wieloma zadaniami.
BladeCoder
1

Ogólnie unikaj definiowania pól kontekstu jako statycznych. Samo ostrzeżenie wyjaśnia, dlaczego: to wyciek pamięci. Jednak zerwanie natychmiastowego biegu może nie być największym problemem na naszej planecie.

Istnieją dwa scenariusze, w których otrzymasz to ostrzeżenie. Na przykład (najbardziej oczywisty):

public static Context ctx;

A potem jest nieco trudniejszy, w którym kontekst jest zawarty w klasie:

public class Example{
    public Context ctx;
    //Constructor omitted for brievety 
}

Ta klasa jest gdzieś zdefiniowana jako statyczna:

public static Example example;

Otrzymasz ostrzeżenie.

Samo rozwiązanie jest dość proste: nie umieszczaj pól kontekstu w instancjach statycznych , czy to w klasie opakowującej, czy też deklarując ją bezpośrednio jako statyczną.

Rozwiązanie ostrzeżenia jest proste: nie umieszczaj pola statycznie. W twoim przypadku przekaż kontekst jako instancję do metody. W przypadku klas, w których wykonywanych jest wiele wywołań Context, użyj konstruktora, aby przekazać kontekst (lub działanie w tym przypadku) do klasy.

Zauważ, że jest to ostrzeżenie, a nie błąd. Jeśli z jakiegoś powodu potrzebujesz statycznego kontekstu, możesz to zrobić. Chociaż tworzysz wyciek pamięci, kiedy to robisz.

Zoe
źródło
jak możemy to zrobić bez tworzenia wycieku pamięci?
Julian00,
1
Nie możesz. Jeśli absolutnie musisz omijać konteksty, możesz zajrzeć do autobusu imprezowego
Zoe,
ok to był problem, który miałem, gdybyś mógł rzucić okiem na to, może jest inny sposób na zrobienie tego, przy okazji metoda musi być statyczna, ponieważ wywołuję ją z kodu c ++ stackoverflow.com/questions/54683863/ ...
isJulian00,
0

Jeśli upewnisz się, że jest to kontekst aplikacji. To ma znaczenie. Dodaj

@SuppressLint("StaticFieldLeak")
Victor Choy
źródło
1
I tak nie polecałbym tego robić. Jeśli potrzebujesz kontekstu, możesz użyć metody requireContext (), jeśli używasz bibliotek AndroidX. Lub możesz przekazać Context bezpośrednio do metody, która go potrzebuje. Możesz też po prostu pobrać odwołanie do klasy aplikacji, ale wolałbym raczej nie używać takiej sugestii SuppressLint.
Oleksandr Nos
0

Służy WeakReferencedo przechowywania kontekstu w klasach Singleton, a ostrzeżenie zniknie

private WeakReference<Context> context;

//Private contructor
private WidgetManager(Context context) {
    this.context = new WeakReference<>(context);
}

//Singleton
public static WidgetManager getInstance(Context context) {
    if (null == widgetManager) {
        widgetManager = new WidgetManager(context);
    }
    return widgetManager;
}

Teraz możesz uzyskać dostęp do kontekstu, takiego jak

  if (context.get() instanceof MainActivity) {
            ((MainActivity) context.get()).startActivityForResult(pickIntent, CODE_REQUEST_PICK_APPWIDGET);
        }
Hitesh Sahu
źródło