2013-08-11 13 views
18

Zgodnie z wprowadzeniem Custom Class Loading in Dalvik Fred Chung na blogu Android Developers:niestandardowej klasy Ładowanie w Dalvik z Gradle (Android New Build System)

The Dalvik VM provides facilities for developers to perform custom class loading. Instead of loading Dalvik executable (“dex”) files from the default location, an application can load them from alternative locations such as internal storage or over the network.

Jednak nie wielu deweloperów potrzebę zrobienia niestandardową klasę Ładuję. Ale ci, którzy postępują zgodnie z instrukcjami na tym blogu, mogą mieć pewne problemy z naśladowaniem tego samego zachowania z Gradle, nowym systemem budowania dla Androida wprowadzonym w Google I/O 2013.

Jak dokładnie można dostosować nową kompilację system do wykonywania tych samych kroków pośrednich, co w starym (opartym na Ant) systemie kompilacji?

Odpowiedz

27

Mój zespół i ja niedawno osiągnęliśmy referencje do metody 64K w naszej aplikacji, która jest maksymalną liczbą obsługiwanych w pliku dex. Aby obejść to ograniczenie, musimy podzielić część programu na wiele dodatkowych plików dex i załadować je w czasie wykonywania.

Obserwowaliśmy wpis na blogu wymieniony w pytaniu o stary, oparty na Antach system kompilacji i wszystko działało dobrze. Ale ostatnio poczuliśmy potrzebę przejścia do nowego systemu kompilacji opartego na systemie Gradle.

Ta odpowiedź nie ma na celu zastąpienia całego postu na blogu pełnym przykładem. Zamiast tego po prostu wyjaśni, jak używać Gradle do ulepszenia procesu budowania i osiągnięcia tego samego. Należy pamiętać, że jest to prawdopodobnie tylko jeden sposób, aby to zrobić i jak obecnie robimy to w naszym zespole. Nie musi to oznaczać, że jest to tylko metoda.

Nasz projekt jest trochę inny i ten przykład działa jako indywidualny projekt Java, który skompiluje cały kod źródłowy do plików .class, zmontuje je do pojedynczego pliku .dex i na koniec zapakuje ten plik .dex do pliku .jar.

Zacznijmy ...

w korzeniu build.gradle mamy następujący fragment kodu, aby zdefiniować kilka domyślnych:

ext.androidSdkDir = System.env.ANDROID_HOME 

if(androidSdkDir == null) { 
    Properties localProps = new Properties() 
    localProps.load(new FileInputStream(file('local.properties'))) 

    ext.androidSdkDir = localProps['sdk.dir'] 
} 

ext.buildToolsVersion = '18.0.1' 
ext.compileSdkVersion = 18 

Musimy powyższy kod bo chociaż przykładem jest indywidualny projekt Java, nadal musimy używać komponentów z zestawu SDK systemu Android. I będziemy także potrzebować niektórych innych właściwościach później ... Tak, na build.gradle głównego projektu, mamy tę zależność:

dependencies { 
    compile files("${androidSdkDir}/platforms/android-${compileSdkVersion}/android.jar") 
} 

Jesteśmy również uproszczenie zestawy źródłowe ten projekt, który może nie być konieczne dla danego projektu:

sourceSets { 
    main { 
     java.srcDirs = ['src'] 
    } 
} 

Dalej, możemy zmienić domyślną konfigurację Wbudowany jar zadanie, aby dołączyć plik zamiast wszystkich plików .class classes.dex:

configure(jar) { 
    include 'classes.dex' 
} 

Teraz musimy mieć nowe zadanie, które faktycznie zgromadzi wszystkie pliki .class do jednego pliku .dex. W naszym przypadku musimy również dołączyć plik JAR biblioteki Protobuf do pliku .dex.Więc jestem w tym, że w tym przykładzie:

task dexClasses << { 
    String protobufJarPath = '' 

    String cmdExt = Os.isFamily(Os.FAMILY_WINDOWS) ? '.bat' : '' 

    configurations.compile.files.find { 
     if(it.name.startsWith('protobuf-java')) { 
      protobufJarPath = it.path 
     } 
    } 

    exec { 
     commandLine "${androidSdkDir}/build-tools/${buildToolsVersion}/dx${cmdExt}", '--dex', 
        "--output=${buildDir}/classes/main/classes.dex", 
        "${buildDir}/classes/main", "${protobufJarPath}" 
    } 
} 

Ponadto, upewnij się, że masz następujące import gdzieś (zazwyczaj na górze, oczywiście) na build.gradle plik:

import org.apache.tools.ant.taskdefs.condition.Os 

Teraz musimy wykonać zadanie jar zależne od naszego zadania dexClasses, aby się upewnić, że nasze zadanie zostanie wykonane przed złożeniem ostatecznego pliku .jar. Robimy to za pomocą prostego wiersza kodu:

jar.dependsOn(dexClasses) 

I skończymy ... Wystarczy powołać Gradle ze zwykłymi assemble zadania i ostatecznego pliku .jar, ${buildDir}/libs/${archivesBaseName}.jar będzie zawierać pojedynczy classes.dex plik (oprócz pliku MANIFEST.MF). Po prostu skopiuj go do folderu aktywów swojej aplikacji (możesz zawsze zautomatyzować to za pomocą programu Gradle, ale to już jest poza zakresem tego pytania) i śledzić resztę posta na blogu.

Jeśli masz jakieś pytania, po prostu krzycz w komentarzach. Spróbuję pomóc jak najlepiej moim umiejętnościom.

+0

Czy chcesz mieć jakiś wkład w http://stackoverflow.com/questions/18629021/safe-way-to-migrate-fegr- existing-eclipse-built-android-project-to-ant-build? Dzięki. –

+0

Kompleksowe i bardzo przydatne. Oczywiście, jesteś zajęty, ale byłby to znaczący wkład jako publiczne repozytorium github :) Dzięki za szczegółowe informacje. –

+0

configure (jar) { zawiera "classes.dex" } nie działa dla mnie - pojawia się błąd: "Wystąpił problem przy ocenie projektu> Nie można znaleźć właściwości 'jar' w projekcie." – goRGon

0

Zobacz moją odpowiedź over here. Kluczowe punkty to:

  • Użyj właściwości additionalParameters na dynamicznie tworzonych dexCamelCase zadań przekazać --multi-dex do dx i tworzyć wielu plików Dex.
  • Użyj programu ładującego klasy , aby użyć wielu plików dex.
2

Android Studio Gradle plugin dostarcza obecnie natywną multidex support, który skutecznie rozwiązuje limitu metoda Android 65k bez konieczności ręcznego ładowania klas z pliku jar, a tym samym sprawia, że ​​blog Fred Chung nieaktualne do tego celu. Jednak ładowanie niestandardowych klas z pliku JAR w środowisku wykonawczym w systemie Android jest nadal przydatne w celu zwiększenia elastyczności (np. Tworzenie plugin framework for your app), więc opiszę ten scenariusz użycia poniżej:

Utworzono port oryginału przykładowa aplikacja na blogu Freda Chunga do Android Studio na moim github page over here przy użyciu wtyczki biblioteki Android zamiast wtyczki Java. Zamiast próbować modyfikować istniejący proces dekodujący, aby podzielić go na dwa moduły, takie jak na blogu, umieściłem kod, który chcemy umieścić w pliku jar, w jego własnym module i dodaliśmy niestandardowe zadanie assembleExternalJar, które usuwa niezbędne pliki klas po zakończeniu zadania głównego assemble.

Oto odpowiednia część pliku build.gradle dla biblioteki. Jeśli twój moduł biblioteczny ma jakieś zależności, które nie są w głównym projekcie, prawdopodobnie będziesz musiał zmodyfikować ten skrypt, aby je dodać.

apply plugin: 'com.android.library' 
// ... see github project for the full build.gradle file 

// Define some tasks which are used in the build process 
task copyClasses(type: Copy) { // Copy the assembled *.class files for only the current namespace into a new directory 
    // get directory for current namespace (PLUGIN_NAMESPACE = 'com.example.toastlib') 
    def namespacePath = PLUGIN_NAMESPACE.replaceAll("\\.","/") 
    // set source and destination directories 
    from "build/intermediates/classes/release/${namespacePath}/" 
    into "build/intermediates/dex/${namespacePath}/" 

    // exclude classes which don't have a corresponding .java entry in the source directory 
    def remExt = { name -> name.lastIndexOf('.').with {it != -1 ? name[0..<it] : name} } 
    eachFile {details -> 
     def thisFile = new File("${projectDir}/src/main/java/${namespacePath}/", remExt(details.name)+".java") 
     if (!(thisFile.exists())) { 
      details.exclude() 
     } 
    } 
} 

task assembleExternalJar << { 
    // Get the location of the Android SDK 
    ext.androidSdkDir = System.env.ANDROID_HOME 
    if(androidSdkDir == null) { 
     Properties localProps = new Properties() 
     localProps.load(new FileInputStream(file('local.properties'))) 
     ext.androidSdkDir = localProps['sdk.dir'] 
    } 
    // Make sure no existing jar file exists as this will cause dx to fail 
    new File("${buildDir}/intermediates/dex/${PLUGIN_NAMESPACE}.jar").delete(); 
    // Use command line dx utility to convert *.class files into classes.dex inside jar archive 
    String cmdExt = Os.isFamily(Os.FAMILY_WINDOWS) ? '.bat' : '' 
    exec { 
     commandLine "${androidSdkDir}/build-tools/${BUILD_TOOLS_VERSION}/dx${cmdExt}", '--dex', 
        "--output=${buildDir}/intermediates/dex/${PLUGIN_NAMESPACE}.jar", 
        "${buildDir}/intermediates/dex/" 
    } 
    copyJarToOutputs.execute() 
} 

task copyJarToOutputs(type: Copy) { 
    // Copy the built jar archive to the outputs folder 
    from 'build/intermediates/dex/' 
    into 'build/outputs/' 
    include '*.jar' 
} 


// Set the dependencies of the build tasks so that assembleExternalJar does a complete build 
copyClasses.dependsOn(assemble) 
assembleExternalJar.dependsOn(copyClasses) 

Bardziej szczegółowe informacje można znaleźć w pełnym kodzie źródłowym przykładowej aplikacji na moim githubie.

+0

Dodałem [żądanie funkcji] (kod https: // .google.com/p/android/issues/detail? id = 81420), aby wtyczka Gradle obsługiwała użycie składni stylu Ant < aby zmodyfikować zadanie Dex, co sprawiłoby, że byłoby to dużo łatwiejsze i mniej delikatne . –

+0

Mam kilka pytań dotyczących podobnego pytania. Czy możesz proszę sprawdzić moje pytanie tutaj - http://stackoverflow.com/questions/32003870/too-many-method-references-in-android-library- project – Scorpion

Powiązane problemy