Wybieranie bibliotek

Aby włączyć optymalizację aplikacji, musisz używać bibliotek zgodnych z optymalizacją Androida. Jeśli biblioteka nie jest skonfigurowana pod kątem optymalizacji na Androida – na przykład jeśli używa odbicia bez dołączania powiązanych reguł zachowywania – może nie być odpowiednia dla aplikacji na Androida. Na tej stronie wyjaśniamy, dlaczego niektóre biblioteki lepiej nadają się do optymalizacji aplikacji, i podajemy ogólne wskazówki, które pomogą Ci w wyborze.

Ogólne wskazówki dotyczące wyboru bibliotek

Skorzystaj z tych wskazówek, aby mieć pewność, że biblioteki są zgodne z optymalizacją aplikacji.

Wybieraj generowanie kodu zamiast odbicia

Wybierz biblioteki, które zamiast odbicia używają generowania kodu (codegen). Dzięki generowaniu kodu optymalizator może łatwiej określić, który kod jest faktycznie używany w czasie działania, a który można usunąć. Trudno stwierdzić, czy biblioteka używa generowania kodu czy refleksji, ale istnieją pewne oznaki – pomocne mogą być wskazówki.

Więcej informacji o generowaniu kodu w porównaniu z odbiciem znajdziesz w artykule Optymalizacja dla autorów bibliotek.

Sprawdzanie, czy użyto odbicia (zaawansowane)

Aby sprawdzić, czy biblioteka korzysta z odbicia, możesz przejrzeć jej kod. Jeśli biblioteka używa odbicia, sprawdź, czy udostępnia powiązane reguły zachowywania. Biblioteka prawdopodobnie korzysta z odbicia, jeśli:

  • Używa klas lub metod z pakietów kotlin.reflect lub java.lang.reflect.
  • Używa funkcji Class.forName lub classLoader.getClass.
  • Odczytuje adnotacje w czasie działania, np. jeśli przechowuje wartość adnotacji za pomocą val value = myClass.getAnnotation() lub val value = myMethod.getAnnotation(), a następnie wykonuje działanie z użyciem value.
  • Wywołuje metody, używając nazwy metody jako ciągu znaków, jak w tym przykładzie:

    // Calls the private `processData` API with reflection
    myObject.javaClass.getMethod("processData", DataType::class.java)
    ?.invoke(myObject, data)
    

Sprawdzanie problemów z optymalizacją

Zanim zdecydujesz się na użycie nowej biblioteki, przejrzyj narzędzie do śledzenia problemów i dyskusje online, aby sprawdzić, czy nie ma problemów związanych z minimalizacją lub konfigurowaniem optymalizacji aplikacji. Jeśli tak, poszukaj alternatywnych bibliotek. Pamiętaj o tych kwestiach:

  • Biblioteki AndroidX i biblioteki takie jak Hilt dobrze współpracują z optymalizacją aplikacji, ponieważ w większości przypadków używają generowania kodu zamiast odbicia. Gdy korzystają z odbicia, podają minimalne reguły zachowywania, aby zachować tylko potrzebny kod.
  • Biblioteki serializacji często używają odbicia, aby uniknąć powtarzalnego kodu podczas tworzenia instancji lub serializacji obiektów. Zamiast podejść opartych na odbiciu (np. Gson w przypadku JSON) poszukaj bibliotek, które używają generowania kodu, aby uniknąć tych problemów, np. Kotlin Serialization lub Moshi z generowaniem kodu.
  • Jeśli to możliwe, unikaj bibliotek, które zawierają reguły zachowywania obejmujące cały pakiet. Reguły zachowywania obejmujące cały pakiet mogą pomóc w rozwiązaniu błędów, ale ogólne reguły zachowywania należy ostatecznie doprecyzować, aby zachowywać tylko potrzebny kod. Więcej informacji znajdziesz w artykule Stopniowe wdrażanie optymalizacji.
  • Biblioteki nie powinny wymagać kopiowania i wklejania reguł zachowywania z dokumentacji do pliku w projekcie, zwłaszcza reguł zachowywania dotyczących całego pakietu. W dłuższej perspektywie reguły te stają się dla dewelopera aplikacji obciążeniem związanym z konserwacją, a ich optymalizacja i zmiana z czasem są trudne.

Włączanie optymalizacji po dodaniu nowej biblioteki

Po dodaniu nowej biblioteki włącz optymalizację i sprawdź, czy nie występują błędy. Jeśli występują błędy, poszukaj alternatywnych bibliotek lub napisz reguły keep. Jeśli biblioteka nie jest zgodna z optymalizacją, zgłoś błąd dotyczący tej biblioteki.

Filtrowanie nieprawidłowych reguł przechowywania (zaawansowane)

Reguły przechowywania się sumują. Oznacza to, że niektórych reguł zawartych w zależności biblioteki nie można usunąć i mogą one wpływać na kompilację innych części aplikacji. Jeśli na przykład biblioteka zawiera regułę wyłączającą optymalizacje kodu, wyłącza ona optymalizacje w całym projekcie.

Unikaj bibliotek z regułami zachowywania, które zatrzymują kod, który powinien zostać usunięty. Jeśli jednak musisz ich użyć, możesz odfiltrować reguły, jak pokazano w tym kodzie:

// If you're using AGP 8.4 and higher
buildTypes {
    release {
        optimization.keepRules {
          it.ignoreFrom("com.somelibrary:somelibrary")
        }
    }
}

// If you're using AGP 7.3-8.3
buildTypes {
    release {
        optimization.keepRules {
          it.ignoreExternalDependencies("com.somelibrary:somelibrary")
        }
    }
}

Studium przypadku: dlaczego Gson nie działa z optymalizacjami

Gson to biblioteka serializacji, która często powoduje problemy z optymalizacją aplikacji, ponieważ w dużym stopniu wykorzystuje odbicie. Poniższy fragment kodu pokazuje, jak zwykle używa się biblioteki Gson, co może łatwo spowodować awarię w czasie działania. Zwróć uwagę, że gdy używasz biblioteki Gson do pobierania listy obiektów User, nie wywołujesz konstruktora ani nie przekazujesz fabryki do funkcji fromJson(). Tworzenie lub używanie klas zdefiniowanych przez aplikację bez spełnienia jednego z tych warunków wskazuje, że biblioteka może używać refleksji otwartej:

  • Klasa aplikacji implementująca bibliotekę, standardowy interfejs lub klasę
  • wtyczka do generowania kodu, np. KSP;
class User(val name: String)
class UserList(val users: List<User>)

// This code runs in debug mode, but crashes when optimizations are enabled
Gson().fromJson("""[{"name":"myname"}]""", User::class.java).toString()

Aby dowiedzieć się, jak R8 działa w przypadku Gson, zapoznaj się z regułami konsumenta Gson. Gdy R8 analizuje ten kod i nie widzi nigdzie instancji UserList ani User, może zmienić nazwy pól lub usunąć konstruktory, które wydają się nieużywane, co może spowodować awarię aplikacji. Jeśli używasz innych bibliotek w podobny sposób, sprawdź, czy nie będą one zakłócać optymalizacji aplikacji. Jeśli tak się stanie, unikaj ich.

Aby zdefiniować klasy w sposób zgodny z regułami konsumenta Gson, użyj tego fragmentu kodu jako odniesienia:

class User(@com.google.gson.annotations.SerializedName("name") val name: String)
class UserList(@com.google.gson.annotations.SerializedName("users") val users: List<User>)

Pamiętaj, że biblioteki Room, HiltMoshi z generowaniem kodu tworzą typy zdefiniowane przez aplikację, ale używają generowania kodu, aby uniknąć konieczności stosowania refleksji.