Naruszenie liczby instancji aktywności StrictMode (2 wystąpienia, 1 oczekiwane) przy rotacji całkowicie pustej aktywności

Istotne tylko w tym, że motywuje eliminowanie fałszywych alarmów ze ścisłego trybu, ponieważ ciągła obecność jakiegokolwiek sprawia, że kara śmierci jest niepraktyczna

Przez ostatnie kilka dni ścigałem i naprawiałem wycieki pamięci w aplikacji. Po dojściu do punktu, w którym wierzyłem, że naprawiłem je wszystkie, zaimplementowałem mechanizm głośności awarii podobny do tego opisanego w Android StrictMode i wysypiska sterty (Włącz śledzenie instancji z karą śmierci, przechwytywanie komunikat o błędzie zamknięcia, sterta zrzutu, Wyślij sygnał SOS przy następnym uruchomieniu aplikacji). Wszystko to oczywiście w kompilacjach debugowania.

Do punktu

Po przekonaniu, że naprawiłem wszystkie wycieki aktywności, niektóre działania nadal powodują ostrzeżenia o naruszeniu instancji trybu podczas obracania ekranu. Co ciekawe, robią to tylko niektóre, a nie wszystkie działania aplikacji.

Przejrzałem wysypiska zrobione, gdy takie naruszenia występują, i przejrzałem Kodeks działań w pytanie w poszukiwaniu przecieku, ale nie dostać nigdzie.

Więc w tym momencie starałem się zrobić najmniejszy możliwy przypadek testowy. Tworzę całkowicie puste działanie (nawet bez layoutu), wyglądające tak:
package com.example.app;

import android.app.Activity;
import android.os.Bundle;
import android.os.StrictMode;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        StrictMode.setVmPolicy(
                new StrictMode.VmPolicy.Builder()
                        .detectAll()
                        .penaltyLog()
                        .build());
        StrictMode.setThreadPolicy(
                new StrictMode.ThreadPolicy.Builder()
                        .detectAll()
                        .penaltyDeath()
                        .penaltyLog()
                        .build());
        super.onCreate(savedInstanceState);
    }

}

A dla kompletności manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.app" >

    <application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
        android:name="com.example.app.MainActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    </application>

</manifest>

Otwieram aktywność(urządzenie trzymane w portrecie). Obracam się do pejzażu, a z powrotem do portretu, w którym momencie widzę ze StrictMode to w LogCat:

01-15 17:24:23.248: E / StrictMode(13867): Klasa com.przykład.app.MainActivity; instances=2; limit=1 2011-01-15 17: 24: 23.248: E/StrictMode(13867): android.os.StrictMode$InstanceCountViolation: class com.przykład.app.MainActivity; instances=2; limit=1 / 2011-01-15 17: 24: 23os.StrictMode.setClassInstanceLimit (StrictMode.java: 1)

Zrzut sterty

W tym momencie ręcznie uzyskuję zrzut sterty za pomocą DDMS. Oto dwie instancje w MAT:

1

A oto ten, który był jest to pierwsza część ścieżki z niej do korzenia GC:

2

Zauważ, że bez względu na to, ile razy obracam między pionowym a poziomym, liczba rzeczywistych instancji nigdy nie przekracza dwóch, a liczba oczekiwanych instancji nigdy nie przekracza jednej.

Czy ktoś może zrozumieć przeciek? Czy to w ogóle prawdziwy przeciek? Jeśli tak, to prawdopodobnie musi to być błąd Androida. Jeśli nie, to jedyne, co widzę, że może to być błąd w StrictMode.

Dobrze odpowiedzi mogą zawierać

  1. jeśli jest to prawdziwy przeciek, wyjaśnienie, jak to się dzieje, i co, jeśli można podjąć jakiekolwiek działania w celu naprawienia go lub powstrzymania StrictMode od rozpoczęcia kary śmierci w takich przypadkach (przypomnijmy, że fałszywe alarmy/neutralne sprawiają, że kara śmierci jest niepraktyczna).
  2. jeśli to nie jest prawdziwy przeciek, wyjaśnienie, dlaczego StrictMode myśli inaczej, i co jeśli można podjąć jakiekolwiek działania, aby go naprawić lub powstrzymać StrictMode od zainicjowania kary śmierci za takie przypadki (przypomnijmy, że fałszywe alarmy / neutralne sprawiają, że kara śmierci jest niepraktyczna).
  3. w obu przypadkach hipotezy, dlaczego nie występuje to przy wszystkich działaniach (większość działań w aplikacji, nad którą pracuję, nie powoduje takich naruszeń na rotacji).

Mam tak daleko, jak patrząc na źródło StrictMode i nie widzę tam nic oczywiście złego - zgodnie z oczekiwaniami, wymusza to GC przed rozważeniem dodatkowej instancji jako naruszenia.

Dobry wpis punkty za odczyt Źródła StrictMode disclosure

Zrobiłem te testy tylko na jednym urządzeniu, Galaxy S4 z CyanogenMod 11 migawka m2 (http://download.cyanogenmod.org/get/jenkins/53833/cm-11-20140104-SNAPSHOT-M2-jfltexx.zip)ale nie wyobrażam sobie, żeby CyanogenMod odbiegał od KitKat pod względem zarządzania aktywnością.

Dodatkowe źródła StrictMode:

  1. instancetracker activity jest tylko polem ostatniej instancji: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/Activity.java#Activity.0mInstanceTracker
  2. gdzie oczekiwane wystąpienie aktywności jest zwiększane: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/ActivityThread.java#2095
  3. gdzie liczba oczekiwanych instancji aktywności jest zmniejszana: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/app/ActivityThread.java#3485
Author: Community, 2014-01-15

3 answers

Wybieram drzwi #2: to nie jest prawdziwy przeciek.

Dokładniej, to jest związane tylko ze śmieciarstwem. Trzy wskazówki:
  1. Ścieżka do korzenia GC kończy się na FinalizerReference, który jest ściśle związany z GC. Zasadniczo obsługuje wywoływanie metod finalize() na obiektach kwalifikujących się do GC -- dla których istnieje jedna z nich, a mianowicie instancja ViewRootImpl.WindowInputEventReceiver, która rozszerza InputEventReceiver, która ma metodę finalize(). Jeśli był to "prawdziwy" wyciek pamięci, to obiekt nie kwalifikuje się do GC i powinna być co najmniej jedna inna ścieżka do katalogu głównego GC.

  2. Przynajmniej w moim przypadku testowym, jeśli wymuszę GC przed wykonaniem migawki sterty, to jest tylko jeden odniesienie do MainActivity (podczas gdy są dwa, jeśli wykonam migawkę bez tego). Wygląda na to, że wymuszanie GC z DDMS faktycznie obejmuje wywołanie wszystkich finalizerów (najprawdopodobniej przez wywołanie FinalizerReference.finalizeAllEnqueued(), które powinno zwolnić wszystkie te referencje.

  3. Mógłbym odtworzyć go w urządzeniu z Androidem 4.4.4, ale nie w Androidzie L, który ma nowy algorytm GC (co prawda jest to co najwyżej poszlaki, ale jest to zgodne z innymi).

Dlaczego tak się dzieje dla niektórych czynności, a nie dla innych? Chociaż nie mogę powiedzieć na pewno, jest prawdopodobne, że konstruowanie" bardziej skomplikowanej " aktywności uruchamia GC (po prostu dlatego, że musi przydzielić więcej pamięci), podczas gdy prosta taka jak ta "ogólnie" nie. Ale to powinno być zmienne.

Dlaczego StrictMode myśli inaczej?

Są obszerne komentarze w StrictMode na temat tego przypadku, sprawdź kod źródłowy decrementExpectedActivityCount(). Niemniej jednak wygląda na to, że nie działa dokładnie tak, jak zamierzali.

    // Note: adding 1 here to give some breathing room during
    // orientation changes.  (shouldn't be necessary, though?)
    limit = newExpected + 1;

    ...

    // Quick check.
    int actual = InstanceTracker.getInstanceCount(klass);
    if (actual <= limit) {
        return;
    }

    // Do a GC and explicit count to double-check.
    // This is the work that we are trying to avoid by tracking the object instances
    // explicity.  Running an explicit GC can be expensive (80ms) and so can walking
    // the heap to count instance (30ms).  This extra work can make the system feel
    // noticeably less responsive during orientation changes when activities are
    // being restarted.  Granted, it is only a problem when StrictMode is enabled
    // but it is annoying.
    Runtime.getRuntime().gc();

    long instances = VMDebug.countInstancesOfClass(klass, false);
    if (instances > limit) {
        Throwable tr = new InstanceCountViolation(klass, instances, limit);
        onVmPolicyViolation(tr.getMessage(), tr);
    }

Update

Właściwie wykonałem więcej testów, wywołując StrictMode.incrementExpectedActivityCount() za pomocą reflection, i znalazłem bardzo ciekawy wynik, który nie zmienia odpowiedzi (nadal jest #2), ale Myślę, że to dodatkowa wskazówka. Jeśli zwiększysz liczbę "oczekiwanych" wystąpień aktywności (powiedzmy do 4), to nadal będzie miało miejsce ścisłe naruszenie trybu (twierdząc, że występuje 5 wystąpień), przy każdej czwartej rotacji .

Z tego wnioskuję, że wywołanie Runtime.getRuntime().gc() jest tym, co faktycznie uwalnia te finalizowalne obiekty, a kod działa dopiero po przekroczeniu ustawionego limitu.

Co jeśli można podjąć jakiekolwiek działania, aby naprawić to?

Chociaż nie jest to w 100% niezawodne, wywołanie System.gc() w aktywności onCreate() prawdopodobnie sprawi, że ten problem zniknie (i tak było w moich testach). Jednak Specyfikacja Javy wyraźnie mówi, że wywóz śmieci nie może być wymuszony (a to tylko podpowiedź), więc nie jestem pewien, czy zaufałbym pójściu z karą śmierci, nawet z tą "poprawką"...

Możesz połączyć to z ręcznym zwiększeniem limitu liczby instancji aktywności przez wywołanie odbicie. Ale wygląda na to, że to naprawdę prymitywny hack:

Method m = StrictMode.class.getMethod("incrementExpectedActivityCount", Class.class);
m.invoke(null, MainActivity.class);

(Uwaga: Należy to zrobić tylko raz podczas uruchamiania aplikacji).

 20
Author: matiash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-07 19:11:33

Chcę podsumować i dostarczyć ostateczną odpowiedź na Bezpieczny czas dla innych programistów.

Jest Androidem.os.StrictMode$InstanceCountViolation problem czy nie

To może być prawdziwy problem. Aby zidentyfikować, czy jest to problem, mogę polecić następujący post: wykrywanie wycieków aktywności w Androidzie. Jeśli zobaczysz, że istnieją obiekty zawierające odniesienie do tej aktywności, które nie są związane z Android Framework to masz problem, który powinien być naprawione przez Ciebie.

W przypadku, gdy nie ma obiektów zawierających odniesienie do tej aktywności, które nie są związane z Android Framework, oznacza to, że napotkałeś problem związany z tym, jak detectactivityleaks sprawdzić jest realizowane. Również jak wspomniano, wywóz śmieci powinien pomóc w tym przypadku

Dlaczego detectActivityLeaks działa nieprawidłowo i nie odtwarza na wszystkich urządzeniach

Jeśli przyjrzymy się źródłom detectActivityLeaks:

// Note: adding 1 here to give some breathing room during
// orientation changes.  (shouldn't be necessary, though?)
limit = newExpected + 1;

...

// Quick check.
int actual = InstanceTracker.getInstanceCount(klass);
if (actual <= limit) {
    return;
}

// Do a GC and explicit count to double-check.
// This is the work that we are trying to avoid by tracking the object instances
// explicity.  Running an explicit GC can be expensive (80ms) and so can walking
// the heap to count instance (30ms).  This extra work can make the system feel
// noticeably less responsive during orientation changes when activities are
// being restarted.  Granted, it is only a problem when StrictMode is enabled
// but it is annoying.
Runtime.getRuntime().gc();

long instances = VMDebug.countInstancesOfClass(klass, false);
if (instances > limit) {
    Throwable tr = new InstanceCountViolation(klass, instances, limit);
    onVmPolicyViolation(tr.getMessage(), tr);
}

The problem polega na tym, że aby poprawnie policzyć instancje, wszystkie poprzednie powinny być zbierane (przynajmniej logika jest na tym przekazywana). I to jest główny problem tej implementacji. Powodem jest to, że jedna podróż w obie strony GC nie może być wystarczająca w Javie, jeśli chcesz mieć pewność, że wszystkie obiekty, które są gotowe do wydania, są zbierane przez GC. W większości przypadków wystarczy wywołać System.GC () dwa razy lub użyć czegoś podobnego do metod zaimplementowanych w tym jlibs biblioteka.

Dlatego ten problem odtwarza się na niektórych urządzeniach (wersje systemu operacyjnego) i nie odtwarza się na innych. Wszystko zależy od tego, jak GC jest zaimplementowane i czasami wystarczy tylko jedno wywołanie, aby mieć pewność, że obiekt zostanie odebrany przez GC.

Jak uniknąć tego problemu w przypadku, gdy nie ma wycieku aktywności Po prostu uruchamiam System.gc () przed rozpoczęciem aktywności w konfiguracji debugowania, jak w poniższym przykładzie. Pomoże to uniknąć tego problemu i daje możesz kontynuować korzystanie z detectactivityleaks check.

 if (BuildConfig.DEBUG)
 {         
     System.gc();
 }
 Intent intent = new Intent(context, SomeActivity.class);
 this.startActivity(intent);

Zapewnia to również, że w release build Garbage Collection nie jest wymuszane, ponieważ nie jest to zalecane.

 4
Author: Maxim Kornilov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-18 07:42:47

Nie jestem programistą Androida, więc jest to trochę strzał w ciemno.

Przypuszczalnie ponieważ zachowanie nie jest spójne dla innych działań, ale mimo to jest wywoływane przez całkowicie trywialne, problem sprowadza się do czasu potrzebnego na ukończenie onCreate.

Jeśli onCreate może zakończyć się przed zakończeniem sekwencji pause/stop/destroy (i ewentualnym zbieraniem śmieci, choć nie musi dojść do tego punktu afaics) dla starej aktywności, to naprawdę będzie występują dwa przypadki.

Proste rozwiązanie, aby temu zapobiec, polega na tym, że aktywność ma statyczne atomicboolean "ready" (początkowo false), które sprawdza przed zrobieniem czegokolwiek innego w onCreate, w którym chcesz zapętlić

while(!ready.compareAndSet(false, true)) {
    //sleep a bit
}

Następnie nadpisuje wywołanie zwrotne cyklu życia ondestroy i wywołuje ready.compareAndSet (true, false)

Przepraszam, jeśli takie podejście jest całkowicie naiwne.
 0
Author: user467257,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-07 16:32:56