Mam dość dużą aplikację na Androida, która opiera się na wielu projektach bibliotecznych. Kompilator Androida ma ograniczenie 65536 metod na plik .dex i przekraczam tę liczbę.
Istnieją zasadniczo dwie ścieżki, które możesz wybrać (przynajmniej o których wiem), gdy osiągniesz limit metody.
1) Zmniejsz swój kod
2) Zbuduj wiele plików dex ( zobacz ten wpis na blogu )
Przyjrzałem się obu i próbowałem dowiedzieć się, co powoduje, że liczba moich metod rośnie tak wysoko. Interfejs API Dysku Google zajmuje największą część z zależnością od guawy na poziomie ponad 12 000. Łączna liczba bibliotek dla interfejsu Drive API v2 przekracza 23 000!
Wydaje mi się, że moje pytanie brzmi, jak myślisz, co powinienem zrobić? Czy powinienem usunąć integrację Dysku Google jako funkcję mojej aplikacji? Czy jest sposób na zmniejszenie API (tak, używam proguard)? Czy powinienem wybrać trasę wielokrotnego dex (co wygląda raczej bolesne, szczególnie w przypadku interfejsów API innych firm)?
źródło
apk
formie? Osobiście chciałbym zobaczyć integrację z DyskiemOdpowiedzi:
Wygląda na to, że Google w końcu wdrożył obejście / poprawkę do przekroczenia limitu metody 65K dla plików dex.
Zobacz: Tworzenie aplikacji przy użyciu ponad 65 tys. Metod
Nadal powinieneś unikać przekroczenia limitu metod 65K poprzez aktywne używanie proguard i przeglądanie swoich zależności.
źródło
możesz użyć do tego biblioteki obsługi multidex, Aby włączyć multidex
1) uwzględnij go w zależnościach:
dependencies { ... compile 'com.android.support:multidex:1.0.0' }
2) Włącz to w swojej aplikacji:
defaultConfig { ... minSdkVersion 14 targetSdkVersion 21 .... multiDexEnabled true }
3) jeśli masz klasę aplikacji dla swojej aplikacji, Zastąp metodę attachBaseContext w następujący sposób:
package ....; ... import android.support.multidex.MultiDex; public class MyApplication extends Application { .... @Override protected void attachBaseContext(Context context) { super.attachBaseContext(context); MultiDex.install(this); } }
4) Jeśli nie masz klasy aplikacji dla swojej aplikacji, zarejestruj android.support.multidex.MultiDexApplication jako aplikację w pliku manifestu. lubię to:
<application ... android:name="android.support.multidex.MultiDexApplication"> ... </application>
i powinno działać dobrze!
źródło
Play Services
Pomoc w wersji 6.5+: http://android-developers.blogspot.com/2014/12/google-play-services-and-dex-method.html...
To dobra wiadomość, na przykład w przypadku prostej gry prawdopodobnie potrzebujesz tylko
base
,games
a możedrive
.źródło
W wersjach usług Google Play starszych niż 6.5 trzeba było skompilować cały pakiet interfejsów API w swojej aplikacji. W niektórych przypadkach utrudniło to utrzymanie liczby metod w aplikacji (w tym interfejsów API platformy, metod bibliotek i własnego kodu) poniżej 65 536 limitu.
Od wersji 6.5 możesz zamiast tego selektywnie kompilować interfejsy API usług Google Play do swojej aplikacji. Na przykład, aby uwzględnić tylko interfejsy API Google Fit i Android Wear, zamień następujący wiersz w pliku build.gradle:
compile 'com.google.android.gms:play-services:6.5.87'
z tymi wierszami:
compile 'com.google.android.gms:play-services-fitness:6.5.87' compile 'com.google.android.gms:play-services-wearable:6.5.87'
Aby uzyskać więcej informacji, kliknij tutaj
źródło
Użyj proguard, aby rozjaśnić swój apk, ponieważ nieużywane metody nie będą dostępne w ostatecznej wersji. Dokładnie sprawdź, czy w swoim pliku konfiguracyjnym proguard masz następujące dane, aby używać proguard z guawą (przepraszam, jeśli już to masz, nie było to znane w momencie pisania):
# Guava exclusions (http://code.google.com/p/guava-libraries/wiki/UsingProGuardWithGuava) -dontwarn sun.misc.Unsafe -dontwarn com.google.common.collect.MinMaxPriorityQueue -keepclasseswithmembers public class * { public static void main(java.lang.String[]); } # Guava depends on the annotation and inject packages for its annotations, keep them both -keep public class javax.annotation.** -keep public class javax.inject.**
Ponadto, jeśli używasz ActionbarSherlock, przełączenie się na bibliotekę obsługującą zgodność aplikacji z wersją 7 również znacznie zmniejszy liczbę metod (w oparciu o osobiste doświadczenia). Instrukcje znajdują się:
źródło
Warning: butterknife.internal.ButterKnifeProcessor: can't find superclass or interface javax.annotation.processing.AbstractProcessor
podczas biegu./gradlew :myapp:proguardDevDebug
Możesz użyć Jar Jar Links, aby zmniejszyć ogromne biblioteki zewnętrzne, takie jak usługi Google Play (metody 16K!)
W twoim przypadku po prostu zgrywasz wszystko z słoika usług Google Play z wyjątkiem
common
internal
idrive
pod-pakietów.źródło
Dla użytkowników Eclipse, którzy nie używają Gradle, istnieją narzędzia, które rozbiją słoik usług Google Play i odbudują go tylko z potrzebnych części.
Używam strip_play_services.sh od dextorer .
Dokładne określenie, które usługi należy uwzględnić, może być trudne, ponieważ istnieją pewne wewnętrzne zależności, ale możesz zacząć od małych elementów i dodać je do konfiguracji, jeśli okaże się, że brakuje potrzebnych elementów.
źródło
Myślę, że na dłuższą metę najlepszym sposobem byłoby rozbicie aplikacji na wiele sposobów.
źródło
Obsługa Multi-dex
będzieoficjalnym rozwiązaniem tego problemu. Zobacz moją odpowiedź tutaj, aby poznać szczegóły.źródło
Jeśli nie, użyj multidex, co sprawia, że proces budowania jest bardzo powolny. Możesz wykonać następujące czynności. Jak wspomniała Yahska, użyj określonej biblioteki usług Google Play. W większości przypadków tylko to jest potrzebne.
compile 'com.google.android.gms:play-services-base:6.5.+'
Oto wszystkie dostępne pakiety Selektywne kompilowanie interfejsów API do pliku wykonywalnego
Jeśli to nie wystarczy, możesz użyć skryptu gradle. Umieść ten kod w pliku „strip_play_services.gradle”
def toCamelCase(String string) { String result = "" string.findAll("[^\\W]+") { String word -> result += word.capitalize() } return result } afterEvaluate { project -> Configuration runtimeConfiguration = project.configurations.getByName('compile') println runtimeConfiguration ResolutionResult resolution = runtimeConfiguration.incoming.resolutionResult // Forces resolve of configuration ModuleVersionIdentifier module = resolution.getAllComponents().find { it.moduleVersion.name.equals("play-services") }.moduleVersion def playServicesLibName = toCamelCase("${module.group} ${module.name} ${module.version}") String prepareTaskName = "prepare${playServicesLibName}Library" File playServiceRootFolder = project.tasks.find { it.name.equals(prepareTaskName) }.explodedDir def tmpDir = new File(project.buildDir, 'intermediates/tmp') tmpDir.mkdirs() def libFile = new File(tmpDir, "${playServicesLibName}.marker") def strippedClassFileName = "${playServicesLibName}.jar" def classesStrippedJar = new File(tmpDir, strippedClassFileName) def packageToExclude = ["com/google/ads/**", "com/google/android/gms/actions/**", "com/google/android/gms/ads/**", // "com/google/android/gms/analytics/**", "com/google/android/gms/appindexing/**", "com/google/android/gms/appstate/**", "com/google/android/gms/auth/**", "com/google/android/gms/cast/**", "com/google/android/gms/drive/**", "com/google/android/gms/fitness/**", "com/google/android/gms/games/**", "com/google/android/gms/gcm/**", "com/google/android/gms/identity/**", "com/google/android/gms/location/**", "com/google/android/gms/maps/**", "com/google/android/gms/panorama/**", "com/google/android/gms/plus/**", "com/google/android/gms/security/**", "com/google/android/gms/tagmanager/**", "com/google/android/gms/wallet/**", "com/google/android/gms/wearable/**"] Task stripPlayServices = project.tasks.create(name: 'stripPlayServices', group: "Strip") { inputs.files new File(playServiceRootFolder, "classes.jar") outputs.dir playServiceRootFolder description 'Strip useless packages from Google Play Services library to avoid reaching dex limit' doLast { def packageExcludesAsString = packageToExclude.join(",") if (libFile.exists() && libFile.text == packageExcludesAsString && classesStrippedJar.exists()) { println "Play services already stripped" copy { from(file(classesStrippedJar)) into(file(playServiceRootFolder)) rename { fileName -> fileName = "classes.jar" } } } else { copy { from(file(new File(playServiceRootFolder, "classes.jar"))) into(file(playServiceRootFolder)) rename { fileName -> fileName = "classes_orig.jar" } } tasks.create(name: "stripPlayServices" + module.version, type: Jar) { destinationDir = playServiceRootFolder archiveName = "classes.jar" from(zipTree(new File(playServiceRootFolder, "classes_orig.jar"))) { exclude packageToExclude } }.execute() delete file(new File(playServiceRootFolder, "classes_orig.jar")) copy { from(file(new File(playServiceRootFolder, "classes.jar"))) into(file(tmpDir)) rename { fileName -> fileName = strippedClassFileName } } libFile.text = packageExcludesAsString } } } project.tasks.findAll { it.name.startsWith('prepare') && it.name.endsWith('Dependencies') }.each { Task task -> task.dependsOn stripPlayServices } project.tasks.findAll { it.name.contains(prepareTaskName) }.each { Task task -> stripPlayServices.mustRunAfter task }
}
Następnie zastosuj ten skrypt w pliku build.gradle, w ten sposób
apply plugin: 'com.android.application' apply from: 'strip_play_services.gradle'
źródło
Jeśli korzystasz z usług Google Play, możesz wiedzieć, że dodaje 20k + metod. Jak już wspomniano, Android Studio ma opcję modułowego włączania określonych usług, ale użytkownicy, którzy utknęli w Eclipse, muszą wziąć modularyzację w swoje ręce :(
Na szczęście istnieje skrypt powłoki, który ułatwia zadanie. Po prostu wypakuj do katalogu jar usług Google Play, edytuj dostarczony plik .conf zgodnie z potrzebami i wykonaj skrypt powłoki.
Przykład jego użycia znajduje się tutaj .
źródło
Tak jak powiedział, zastępuję
compile 'com.google.android.gms:play-services:9.0.0'
tylko biblioteki, których potrzebowałem i zadziałało.źródło