Weryfikowanie działania aplikacji w środowisku wykonawczym Androida (ART)

Środowisko wykonawcze Androida (ART) to domyślne środowisko wykonawcze na urządzeniach z Androidem 5.0 (poziom interfejsu API 21) lub nowszym. To środowisko wykonawcze oferuje wiele funkcji, które zwiększają wydajność oraz płynność działania platformy i aplikacji Androida. Więcej informacji o nowych funkcjach ART znajdziesz w artykule Przedstawiamy ART.

Jednak niektóre techniki, które działają w Dalviku, nie działają w ART. Z tego dokumentu dowiesz się, na co zwracać uwagę przy migracji istniejącej aplikacji w celu zapewnienia zgodności z ART. Większość aplikacji powinna działać po prostu pod warunkiem użycia ART.

Rozwiązywanie problemów z czyszczeniem pamięci

W sekcji Dalvik aplikacje często używają funkcji System.gc(), aby prosić o wyczyszczenie pamięci (GC). Powinno to być znacznie mniej potrzebne w przypadku ART, zwłaszcza jeśli wywołujesz czyszczenie pamięci, aby zapobiegać występowaniu błędów typu GC_FOR_ALLOC lub ograniczyć fragmentację. Aby sprawdzić, które środowisko wykonawcze jest używane, wywołaj System.getProperty("java.vm.version"). Jeśli używana jest ART, wartość właściwości to "2.0.0" lub więcej.

ART korzysta z kolektora kopiowania równoczesnego (CC), który równocześnie kompaktuje stertę Java. Z tego powodu nie należy używać technik, które są niezgodne z kompaktowaniem GC (takich jak zapisywanie wskaźników do danych instancji obiektów). Jest to szczególnie ważne w przypadku aplikacji korzystających z natywnego interfejsu Java (JNI). Aby dowiedzieć się więcej, zobacz Zapobieganie problemom JNI.

Zapobieganie występowaniu problemów związanych z JNI

JNI ART jest nieco bardziej rygorystyczne niż Dalvik. Szczególnie warto używać trybu CheckJNI, aby wychwytywać typowe problemy. Jeśli Twoja aplikacja wykorzystuje kod C/C++, przeczytaj ten artykuł:

Debugowanie Android JNI przy użyciu CheckJNI

Sprawdzanie kodu JNI pod kątem problemów z czyszczeniem pamięci

Kolektor kopiowania równoległego (CC) może przenosić obiekty w pamięci na potrzeby kompresowania. Jeśli używasz kodu C/C++, nie wykonuj operacji, które są niezgodne z kompaktowaniem GC. Ulepszyliśmy narzędzie CheckJNI, aby wykrywać niektóre potencjalne problemy (jak opisano w artykule o zmianach w lokalnych plikach referencyjnych JNI w ICS).

Jednym z obszarów, na które należy zwrócić uwagę, jest użycie funkcji Get...ArrayElements() i Release...ArrayElements(). W środowiskach wykonawczych z niekompaktowym GC funkcje Get...ArrayElements() zwykle zwracają odwołanie do rzeczywistej pamięci będącej podstawą obiektu tablicy. Jeśli zmienisz jeden ze zwróconych elementów tablicy, sam obiekt tablicy też się zmienia (a argumenty Release...ArrayElements() są zwykle ignorowane). Jeśli jednak używasz kompaktowania GC, funkcje Get...ArrayElements() mogą zwracać kopię pamięci. Jeśli nadużyjesz pliku referencyjnego podczas kompaktowania GC, może to doprowadzić do uszkodzenia pamięci lub innych problemów. Na przykład:

  • Jeśli wprowadzisz zmiany w zwróconych elementach tablicy, musisz po zakończeniu wywołać odpowiednią funkcję Release...ArrayElements(), aby mieć pewność, że wprowadzone zmiany zostaną prawidłowo skopiowane do bazowego obiektu tablicy.
  • Gdy zwalniasz elementy tablicy pamięci, musisz użyć odpowiedniego trybu w zależności od wprowadzonych zmian:
    • Jeśli nie wprowadzono żadnych zmian w elementach tablicy, użyj trybu JNI_ABORT, który zwolni pamięć bez kopiowania zmian z powrotem do bazowego obiektu tablicy.
    • Jeśli wprowadzono zmiany w tablicy i nie potrzebujesz już odwołania, użyj kodu 0 (spowoduje to zaktualizowanie obiektu tablicy i zwolnienie kopii pamięci).
    • Jeśli w tablicy wprowadzono zmiany, które chcesz zatwierdzić, i chcesz zachować jej kopię, użyj funkcji JNI_COMMIT (która aktualizuje bazowy obiekt tablicy i zachowuje kopię).
  • Gdy wywołujesz funkcję Release...ArrayElements(), zwracaj ten sam wskaźnik, który został pierwotnie zwrócony przez usługę Get...ArrayElements(). Nie można na przykład zwiększać wartości oryginalnego wskaźnika (aby przeskanować zwrócone elementy tablicy), a następnie przekazywać go do funkcji Release...ArrayElements(). Przekazywanie tego zmodyfikowanego wskaźnika może spowodować zwolnienie niewłaściwej pamięci, co doprowadzi do jej uszkodzenia.

Obsługa błędów

JNI ART zgłasza błędy w wielu przypadkach, gdy Dalvik tego nie robi. Możesz wychwycić wiele takich przypadków, testując ją za pomocą CheckJNI.

Jeśli na przykład funkcja RegisterNatives zostanie wywołana przy użyciu nieistniejącej metody (np. dlatego, że metoda została usunięta przez narzędzie takie jak ProGuard), ART prawidłowo zwraca NoSuchMethodError:

08-12 17:09:41.082 13823 13823 E AndroidRuntime: FATAL EXCEPTION: main
08-12 17:09:41.082 13823 13823 E AndroidRuntime: java.lang.NoSuchMethodError:
    no static or non-static method
    "Lcom/foo/Bar;.native_frob(Ljava/lang/String;)I"
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.nativeLoad(Native Method)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.doLoad(Runtime.java:421)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.Runtime.loadLibrary(Runtime.java:362)
08-12 17:09:41.082 13823 13823 E AndroidRuntime:
    at java.lang.System.loadLibrary(System.java:526)

ART zarejestruje też błąd (widoczny w logcat), jeśli RegisterNatives zostanie wywołany bez żadnych metod:

W/art     ( 1234): JNI RegisterNativeMethods: attempt to register 0 native
methods for <classname>

Dodatkowo funkcje JNI GetFieldID() i GetStaticFieldID() teraz prawidłowo zgłaszają NoSuchFieldError, a nie po prostu wartość null. I podobnie, GetMethodID() i GetStaticMethodID() prawidłowo zgłaszają NoSuchMethodError. Może to prowadzić do błędów CheckJNI spowodowanych nieobsługiwanymi wyjątkami lub wyjątkami zgłaszanymi do elementów wywołujących kod Java w kodzie natywnym. Z tego powodu szczególnie ważne jest testowanie aplikacji zgodnych z ART przy użyciu trybu CheckJNI.

ART wymaga, aby użytkownicy metod CallNonvirtual...Method() JNI (takich jak CallNonvirtualVoidMethod()) używali klasy deklarującej metody, a nie podklasy, zgodnie ze specyfikacją JNI.

Zapobieganie problemom z rozmiarem stosu

W firmie Dalvik znajdowały się osobne stosy dla kodu natywnego i Java. Domyślny rozmiar stosu Java wynosił 32 KB, a domyślny rozmiar stosu natywnego to 1 MB. ART ma ujednolicony stos dla lepszej lokalizacji. Zwykle rozmiar stosu ART Thread powinien być mniej więcej taki sam jak w przypadku Dalvik. Jeśli jednak wyraźnie określisz rozmiary stosu, może być konieczne ponowne sprawdzenie tych wartości w przypadku aplikacji działających w ART.

  • W Javie przejrzyj wywołania konstruktora Thread, które określają jawny rozmiar stosu. Na przykład musisz zwiększyć rozmiar, jeśli występuje StackOverflowError.
  • W języku C/C++ sprawdź użycie znaczników pthread_attr_setstack() i pthread_attr_setstacksize() w wątkach, w których jest również uruchamiany kod Java za pomocą JNI. Oto przykład błędu rejestrowanego, gdy aplikacja próbuje wywołać JNI AttachCurrentThread(), gdy rozmiar wątku jest zbyt mały:
    F/art: art/runtime/thread.cc:435]
        Attempt to attach a thread with a too-small stack (16384 bytes)

Zmiany modeli obiektowych

Dalvik nieprawidłowo zezwolił na zastępowanie metod dotyczących pakietów prywatnych. ART wysyła ostrzeżenie w takich przypadkach:

Before Android 4.1, method void com.foo.Bar.quux()
would have incorrectly overridden the package-private method in
com.quux.Quux

Jeśli chcesz zastąpić metodę klasy w innym pakiecie, zadeklaruj ją jako public lub protected.

Object ma teraz prywatne pola. W przypadku aplikacji, które odzwierciedlają pola w hierarchii klas, uważaj, aby nie próbować analizować pól Object. Jeśli np. iterujecie hierarchię klas w ramach platformy serializacji,

Class.getSuperclass() == java.lang.Object.class

zamiast kontynuować, dopóki metoda nie zwróci wartości null.

Serwer proxy InvocationHandler.invoke() otrzymuje teraz wartość null, jeśli nie ma argumentów zamiast pustej tablicy. To zachowanie było wcześniej udokumentowane, ale nie zostało poprawnie obsługiwane w Dalvik. W poprzednich wersjach Mockito występują z tym problemy, dlatego do testowania przy użyciu ART należy używać zaktualizowanej wersji Mockito.

Rozwiązywanie problemów z kompilacją AOT

Kompilacja kodu Java Ahead-Of-Time (AOT) w formacie ART powinna działać w przypadku wszystkich standardowych kodów w języku Java. Kompilacja wykonuje narzędzie dex2oat ART. Jeśli podczas instalacji napotkasz jakiekolwiek problemy związane z elementem dex2oat, daj nam znać (patrz Zgłaszanie problemów), abyśmy mogli jak najszybciej je rozwiązać. Pamiętaj o kilku kwestiach:

  • ART przeprowadza dokładniejszą weryfikację kodu bajtowego podczas instalacji niż Dalvik. Kod utworzony za pomocą narzędzi do kompilacji Androida powinien działać prawidłowo. Niektóre narzędzia do przetwarzania końcowego (zwłaszcza narzędzia do zaciemniania kodu) mogą jednak generować nieprawidłowe pliki, które są tolerowane przez Dalvik, ale są odrzucane przez ART. Współpracujemy z dostawcami narzędzi, aby wykryć i rozwiązać takie problemy. W wielu przypadkach pobranie najnowszych wersji narzędzi i ponowne wygenerowanie plików DEX może rozwiązać te problemy.
  • Weryfikator ART zgłasza niektóre typowe problemy:
    • nieprawidłowy przepływ sterowania
    • niezrównoważone monitorenter/monitorexit
    • Rozmiar listy typów parametrów o długości 0
  • Niektóre aplikacje zależą od zainstalowanego formatu pliku .odex w /system/framework, /data/dalvik-cache lub w zoptymalizowanym katalogu wyjściowym DexClassLoader. Są to teraz pliki ELF, a nie rozszerzone wersje plików DEX. Chociaż ART stara się przestrzegać tych samych reguł nazewnictwa i blokowania co Dalvik, aplikacje nie powinny zależeć od formatu pliku – format może ulec zmianie bez powiadomienia.

    Uwaga: w Androidzie 8.0 (poziom interfejsu API 26) i nowszych katalog zoptymalizowanego katalogu wyjściowego DexClassLoader został wycofany. Więcej informacji znajdziesz w dokumentacji konstruktora DexClassLoader().

Problemy z raportowaniem

Jeśli napotkasz problemy, które nie są związane z problemami z JNI, zgłoś je za pomocą narzędzia Android Open Source Project Issue Tracker na stronie https://code.google.com/p/android/issues/list. Dołącz "adb bugreport" i link do aplikacji w Sklepie Google Play, jeśli jest dostępny. Jeśli to możliwe, dołącz plik APK, który odtwarza problem. Pamiętaj, że problemy (w tym załączniki) są widoczne publicznie.