Cara menyusutkan kode - batas metode 65k di dex

91

Saya memiliki aplikasi Android yang agak besar yang bergantung pada banyak proyek perpustakaan. Kompiler Android memiliki batasan 65536 metode per file .dex dan saya melampaui angka itu.

Pada dasarnya ada dua jalur yang dapat Anda pilih (setidaknya yang saya tahu) ketika Anda mencapai batas metode.

1) Kecilkan kode Anda

2) Bangun beberapa file dex ( lihat posting blog ini )

Saya melihat keduanya dan mencoba mencari tahu apa yang menyebabkan jumlah metode saya menjadi begitu tinggi. Google Drive API mengambil bagian terbesar dengan ketergantungan Guava di lebih dari 12.000. Total libs untuk Drive API v2 mencapai lebih dari 23.000!

Pertanyaan saya, menurut Anda, apa yang harus saya lakukan? Haruskah saya menghapus integrasi Google Drive sebagai fitur aplikasi saya? Apakah ada cara untuk mengecilkan API (ya, saya menggunakan proguard)? Haruskah saya menggunakan beberapa rute dex (yang terlihat agak menyakitkan, terutama berurusan dengan API pihak ketiga)?

Jared Rummler
sumber
2
Saya kebetulan menyukai aplikasi Anda. Pernahkah Anda berpikir untuk mengunduh semua lib ekstra dalam apkbentuk semu ? Saya pribadi ingin melihat integrasi Drive
JBirdVegas
8
Facebook baru-baru ini mendokumentasikan solusi mereka untuk masalah yang tampaknya hampir sama di aplikasi Android mereka. Semoga bermanfaat: facebook.com/notes/facebook-engineering/…
Reuben Scratton
4
Mulai menuju ke beberapa rute dex. Saya berhasil membuat file dex sekunder untuk bekerja dengan Google Drive. Saya merasa kasihan pada siapa pun yang membutuhkan jambu biji sebagai ketergantungan. : P Ini masih menjadi masalah yang cukup besar bagi saya
Jared Rummler
4
bagaimana Anda menghitung metode?
Bri6ko
1
Beberapa catatan tambahan di sini: stackoverflow.com/questions/21490382 (termasuk tautan ke utilitas yang akan mencantumkan referensi metode dalam APK). Perhatikan batas 64K tidak terkait dengan masalah Facebook terkait beberapa komentar.
fadden

Jawaban:

69

Sepertinya Google akhirnya menerapkan solusi / perbaikan untuk melampaui batas metode 65K file dex.

Tentang Batas Referensi 65K

File aplikasi Android (APK) berisi file bytecode yang dapat dieksekusi dalam bentuk file Dalvik Executable (DEX), yang berisi kode terkompilasi yang digunakan untuk menjalankan aplikasi Anda. Spesifikasi Dalvik Executable membatasi jumlah total metode yang dapat dirujuk dalam satu file DEX menjadi 65.536, termasuk metode framework Android, metode library, dan metode dalam kode Anda sendiri. Untuk melewati batas ini, Anda harus mengonfigurasi proses build aplikasi untuk menghasilkan lebih dari satu file DEX, yang dikenal sebagai konfigurasi multidex.

Dukungan multidex sebelum Android 5.0

Versi platform sebelum Android 5.0 menggunakan waktu proses Dalvik untuk menjalankan kode aplikasi. Secara default, Dalvik membatasi aplikasi ke satu file bytecode class.dex per APK. Untuk mengatasi batasan ini, Anda dapat menggunakan pustaka dukungan multidex , yang menjadi bagian dari file DEX utama aplikasi Anda dan kemudian mengelola akses ke file DEX tambahan dan kode yang dikandungnya.

Dukungan multidex untuk Android 5.0 dan lebih tinggi

Android 5.0 dan lebih tinggi menggunakan runtime yang disebut ART yang secara native mendukung pemuatan beberapa file dex dari file APK aplikasi. ART melakukan pra-kompilasi pada waktu penginstalan aplikasi yang memindai file kelas (.. N) .dex dan mengompilasinya menjadi satu file .oat untuk dieksekusi oleh perangkat Android. Untuk informasi selengkapnya tentang waktu proses Android 5.0, lihat Memperkenalkan ART .

Lihat: Membangun Aplikasi dengan Lebih dari 65K Metode


Pustaka Dukungan Multidex

Library ini memberikan dukungan untuk membuat aplikasi dengan beberapa file Dalvik Executable (DEX). Aplikasi yang mereferensikan lebih dari 65536 metode diperlukan untuk menggunakan konfigurasi multidex. Untuk informasi selengkapnya tentang menggunakan multidex, lihat Membangun Aplikasi dengan Metode 65 Ribu Lebih .

Library ini terletak di direktori / extras / android / support / multidex / setelah Anda mendownload Library Dukungan Android. Pustaka tidak berisi sumber daya antarmuka pengguna. Untuk memasukkannya ke dalam proyek aplikasi Anda, ikuti petunjuk untuk Menambahkan perpustakaan tanpa sumber daya.

Pengenal dependensi skrip build Gradle untuk pustaka ini adalah sebagai berikut:

com.android.support:multidex:1.0.+ Notasi ketergantungan ini menentukan versi rilis 1.0.0 atau yang lebih tinggi.


Anda tetap harus menghindari mencapai batas metode 65K dengan secara aktif menggunakan proguard dan meninjau dependensi Anda.

Jared Rummler
sumber
6
+1, Mengapa orang-orang tidak memberikan suara positif pada jawaban yang benar ketika mereka dijawab oleh orang yang sama?
Pacerier
tingkat api min menjadi 14!
Vihaan Verma
5
Kami menulis plugin Gradle kecil untuk memberi Anda jumlah metode Anda saat ini pada setiap build. Sangat membantu kami untuk mengelola perpustakaan - github.com/KeepSafe/dexcount-gradle-plugin
philipp
53

Anda dapat menggunakan pustaka dukungan multidex untuk itu, untuk mengaktifkan multidex

1) sertakan dalam dependensi:

dependencies {
  ...
  compile 'com.android.support:multidex:1.0.0'
}

2) Aktifkan di aplikasi Anda:

defaultConfig {
    ...
    minSdkVersion 14
    targetSdkVersion 21
    ....
    multiDexEnabled true
}

3) jika Anda memiliki kelas aplikasi untuk aplikasi Anda, maka Ganti metode attachBaseContext seperti ini:

package ....;
...
import android.support.multidex.MultiDex;

public class MyApplication extends Application {
  ....
   @Override
   protected void attachBaseContext(Context context) {
    super.attachBaseContext(context);
    MultiDex.install(this);
   }
}

4) jika Anda tidak memiliki kelas aplikasi untuk aplikasi Anda, maka daftarkan android.support.multidex.MultiDexApplication sebagai aplikasi Anda dalam file manifes. seperti ini:

<application
    ...
    android:name="android.support.multidex.MultiDexApplication">
    ...
</application>

dan itu akan bekerja dengan baik!

Prakhar
sumber
32

Play Services6.5+ membantu: http://android-developers.blogspot.com/2014/12/google-play-services-and-dex-method.html

"Mulai dengan versi 6.5, layanan Google Play, Anda akan dapat memilih dari sejumlah API individu, dan Anda dapat melihat"

...

"ini secara transitif akan menyertakan pustaka 'dasar', yang digunakan di semua API."

Ini adalah kabar baik, untuk game sederhana misalnya Anda mungkin hanya membutuhkan base, gamesdan mungkin drive.

"Daftar lengkap nama API ada di bawah. Detail lebih lanjut dapat ditemukan di situs Pengembang Android .:

  • com.google.android.gms: play-services-base: 6.5.87
  • com.google.android.gms: iklan-layanan-bermain: 6.5.87
  • com.google.android.gms: play-services-appindexing: 6.5.87
  • com.google.android.gms: play-services-maps: 6.5.87
  • com.google.android.gms: play-services-location: 6.5.87
  • com.google.android.gms: layanan-bermain-kebugaran: 6.5.87
  • com.google.android.gms: play-services-panorama: 6.5.87
  • com.google.android.gms: play-services-drive: 6.5.87
  • com.google.android.gms: permainan-layanan-permainan: 6.5.87
  • com.google.android.gms: play-services-wallet: 6.5.87
  • com.google.android.gms: play-services-identity: 6.5.87
  • com.google.android.gms: play-services-cast: 6.5.87
  • com.google.android.gms: layanan-bermain-plus: 6.5.87
  • com.google.android.gms: play-services-appstate: 6.5.87
  • com.google.android.gms: play-services-wearable: 6.5.87
  • com.google.android.gms: play-services-all-wear: 6.5.87
Csaba Toth
sumber
Ada informasi tentang bagaimana melakukannya dalam proyek Eclipse?
Brian White
Saya belum dalam posisi untuk meningkatkan ke versi itu. Tetapi jika proyek Anda berbasis Maven, maka mudah-mudahan Anda hanya perlu menyelesaikannya di maven pom Anda.
Csaba Toth
@ webo80 Nah, ini hanya membantu jika Anda menggunakan versi 6.5.87. Saya bertanya-tanya tentang jawaban Petey, bahwa pengawal itu menghapus fungsi yang tidak digunakan. Saya ingin tahu apakah itu melibatkan libs pihak ke-2 juga, atau hanya barang Anda sendiri. Saya harus membaca lebih lanjut tentang proguard.
Csaba Toth
@BrianWhite Satu-satunya solusi untuk saat ini tampaknya menghapus file .jar dengan beberapa alat eksternal ..
milosmns
1
@CsabaToth, Anda menyelamatkan saya! Saya hanya menambahkan beberapa dari daftar di atas, bukan 'com.google.android.gms: play-services' lengkap, dan itu membuat perbedaan!
EZDsIt
9

Dalam versi layanan Google Play sebelum 6.5, Anda harus mengompilasi seluruh paket API ke dalam aplikasi Anda. Dalam beberapa kasus, hal itu membuat lebih sulit untuk mempertahankan jumlah metode dalam aplikasi Anda (termasuk API kerangka kerja, metode pustaka, dan kode Anda sendiri) di bawah batas 65.536.

Dari versi 6.5, sebagai gantinya Anda dapat secara selektif menyusun API layanan Google Play ke dalam aplikasi Anda. Misalnya, untuk hanya menyertakan Google Fit dan Android Wear API, ganti baris berikut dalam file build.gradle Anda:

compile 'com.google.android.gms:play-services:6.5.87'

dengan garis-garis ini:

compile 'com.google.android.gms:play-services-fitness:6.5.87'
compile 'com.google.android.gms:play-services-wearable:6.5.87'

untuk referensi lebih lanjut, Anda bisa klik di sini

akshay
sumber
Bagaimana cara melakukannya di gerhana?
Hardik9850
7

Gunakan proguard untuk meringankan apk Anda karena metode yang tidak digunakan tidak akan ada dalam versi final Anda. Periksa kembali apakah Anda telah mengikuti di file konfigurasi proguard Anda untuk menggunakan proguard dengan jambu biji (maaf jika Anda sudah memiliki ini, tidak diketahui pada saat penulisan):

# 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.**

Selain itu, jika Anda menggunakan ActionbarSherlock, beralih ke pustaka dukungan appcompat v7 juga akan banyak mengurangi jumlah metode Anda (berdasarkan pengalaman pribadi). Instruksi berada:

petey
sumber
ini terlihat menjanjikan tetapi saya mendapatkannya Warning: butterknife.internal.ButterKnifeProcessor: can't find superclass or interface javax.annotation.processing.AbstractProcessorsaat berlari./gradlew :myapp:proguardDevDebug
ericn
1
Namun, selama pengembangan, proguard umumnya tidak berjalan (akhirnya tidak dengan Eclipse) sehingga Anda tidak dapat memanfaatkan penyusutan hingga melakukan build rilis.
Brian White
7

Anda dapat menggunakan Jar Jar Links untuk mengecilkan perpustakaan eksternal yang besar seperti Google Play Services (metode 16K!)

Dalam kasus Anda, Anda hanya akan merobek semuanya dari jar Layanan Google Play kecuali common internaldan drivesub-paket.

piksel
sumber
4

Untuk pengguna Eclipse yang tidak menggunakan Gradle, ada alat yang akan memecah tabung Layanan Google Play dan membuatnya kembali hanya dengan bagian yang Anda inginkan.

Saya menggunakan strip_play_services.sh oleh dextorer .

Mungkin sulit untuk mengetahui dengan tepat layanan mana yang akan disertakan karena ada beberapa dependensi internal tetapi Anda dapat memulai dari yang kecil dan menambahkan ke konfigurasi jika ternyata ada hal-hal yang diperlukan hilang.

Brian White
sumber
3

Menurut saya, dalam jangka panjang, memecah aplikasi Anda dalam beberapa dex akan menjadi cara terbaik.

prmottajr.dll
sumber
2
Saya mencari cara yang tepat untuk melakukan ini dengan Gradle: - / Ada petunjuk?
Ivan Morgillo
2

Jika tidak menggunakan multidex yang membuat proses build sangat lambat. Anda dapat melakukan hal berikut. Seperti yang disebutkan yahska gunakan pustaka layanan google play khusus. Untuk kebanyakan kasus, hanya ini yang diperlukan.

compile 'com.google.android.gms:play-services-base:6.5.+'

Ini semua paket yang tersedia Mengompilasi API secara selektif ke dalam file yang dapat dieksekusi

Jika ini tidak cukup, Anda dapat menggunakan skrip gradle. Letakkan kode ini di file '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
}

}

Kemudian terapkan skrip ini di build.gradle Anda, seperti ini

apply plugin: 'com.android.application'
apply from: 'strip_play_services.gradle'
Roman Nazarevych
sumber
1

Jika menggunakan Layanan Google Play, Anda mungkin tahu bahwa itu menambahkan metode 20k +. Seperti yang telah disebutkan, Android Studio memiliki opsi untuk penyertaan modular layanan tertentu, tetapi pengguna yang terjebak dengan Eclipse harus mengambil modularisasi ke tangan mereka sendiri :(

Untungnya ada skrip shell yang membuat pekerjaan ini cukup mudah. Cukup ekstrak ke direktori jar layanan google play, edit file .conf yang disediakan sesuai kebutuhan dan jalankan skrip shell.

Contoh penggunaannya ada di sini .

Tom
sumber
1

Jika menggunakan Layanan Google Play, Anda mungkin tahu bahwa itu menambahkan metode 20k +. Seperti yang telah disebutkan, Android Studio memiliki opsi untuk penyertaan modular layanan tertentu, tetapi pengguna yang terjebak dengan Eclipse harus mengambil modularisasi ke tangan mereka sendiri :(

Untungnya ada skrip shell yang membuat pekerjaan ini cukup mudah. Cukup ekstrak ke direktori jar layanan google play, edit file .conf yang disediakan sesuai kebutuhan dan jalankan skrip shell.

Contoh penggunaannya ada di sini.

Seperti yang dia katakan, saya mengganti compile 'com.google.android.gms:play-services:9.0.0'hanya dengan perpustakaan yang saya butuhkan dan itu berhasil.

Tamir Gilany
sumber