Kod nasıl küçültülür - dex'te 65k yöntem sınırı


91

Pek çok kütüphane projesine dayanan oldukça büyük bir Android uygulamam var. Android derleyicisinin .dex dosyası başına 65536 yöntem sınırlaması var ve ben bu sayıyı aşıyorum.

Yöntem sınırına ulaştığınızda temel olarak seçebileceğiniz (en azından bildiğim kadarıyla) iki yol vardır.

1) Kodunuzu küçültün

2) Birden fazla dex dosyası oluşturun ( bu blog gönderisine bakın )

Her ikisine de baktım ve yöntem sayımın bu kadar yükselmesine neyin sebep olduğunu bulmaya çalıştım. Google Drive API, 12.000'in üzerinde Guava bağımlılığı ile en büyük parçayı alıyor. Drive API v2 için toplam kitaplık sayısı 23.000'in üzerine çıktı!

Sorum şu, sence ne yapmalıyım? Google Drive entegrasyonunu uygulamamın bir özelliği olarak kaldırmalı mıyım? API'yi küçültmenin bir yolu var mı (evet, proguard kullanıyorum)? Çoklu dex yoluna gitmeli miyim (özellikle üçüncü taraf API'leri ile uğraşmak oldukça acı verici görünüyor)?


2
Uygulamana bayılıyorum. Tüm ekstra kitaplıkları sözde bir apkbiçimde indirmeyi düşündünüz mü ? Kişisel olarak Drive entegrasyonunu görmek istiyorum
JBirdVegas

8
Facebook, geçtiğimiz günlerde Android uygulamalarında neredeyse aynı bir sorun gibi görünen geçici çözümlerini belgeledi. Yararlı olabilir: facebook.com/notes/facebook-engineering/…
Reuben Scratton

4
Çoklu dex rotasına doğru ilerlemeye başladım. Google Drive ile çalışmak için başarıyla ikincil bir dex dosyası oluşturdum. Bağımlılık olarak guavaya ihtiyaç duyan herkes için üzülüyorum. : P Yine de benim için oldukça büyük bir sorun
Jared Rummler

4
yöntemleri nasıl sayarsınız?
Bri6ko

1
Burada bazı ek notlar: stackoverflow.com/questions/21490382 (bir APK'deki yöntem referanslarını listeleyen bir yardımcı programa bağlantı dahil). 64K sınırının, birkaç yoruma bağlı Facebook sorunu ile ilgisi olmadığını unutmayın.
fadden

Yanıtlar:


69

Görünüşe göre Google, dex dosyalarının 65K yöntem sınırını aşmak için nihayet bir geçici çözüm / düzeltme uyguladı.

65K Referans Sınırı Hakkında

Android uygulama (APK) dosyaları, uygulamanızı çalıştırmak için kullanılan derlenmiş kodu içeren Dalvik Yürütülebilir (DEX) dosyaları biçiminde yürütülebilir bayt kodu dosyaları içerir. Dalvik Yürütülebilir özelliği, Android çerçeve yöntemleri, kitaplık yöntemleri ve kendi kodunuzdaki yöntemler dahil olmak üzere tek bir DEX dosyasında başvurulabilecek toplam yöntem sayısını 65.536 ile sınırlar. Bu sınırı aşmak, uygulama oluşturma sürecinizi multidex yapılandırması olarak bilinen birden fazla DEX dosyası oluşturacak şekilde yapılandırmanızı gerektirir.

Android 5.0'dan önceki multidex desteği

Platformun Android 5.0'dan önceki sürümleri, uygulama kodunu yürütmek için Dalvik çalışma zamanını kullanır. Varsayılan olarak Dalvik, uygulamaları APK başına tek bir classes.dex bayt kodu dosyasıyla sınırlar. Bu sınırlamayı aşmak için, uygulamanızın birincil DEX dosyasının bir parçası haline gelen ve ardından ek DEX dosyalarına ve içerdikleri koda erişimi yöneten multidex destek kitaplığını kullanabilirsiniz .

Android 5.0 ve üstü için multidex desteği

Android 5.0 ve sonraki sürümler, uygulama APK dosyalarından birden çok dex dosyasının yüklenmesini yerel olarak destekleyen ART adlı bir çalışma zamanı kullanır. ART, sınıfları (.. N) .dex dosyalarını tarayan ve bunları Android cihaz tarafından yürütülmesi için tek bir .oat dosyasında derleyen uygulama yükleme zamanında ön derleme gerçekleştirir. Android 5.0 çalışma zamanı hakkında daha fazla bilgi için ART'ye Giriş bölümüne bakın .

65.000'den Fazla Yöntemle Uygulama Oluşturma bölümüne bakın


Multidex Destek Kitaplığı

Bu kitaplık, birden çok Dalvik Yürütülebilir (DEX) dosyasıyla uygulama oluşturma desteği sağlar. 65536'dan fazla yönteme başvuran uygulamalar, multidex yapılandırmalarını kullanmak için gereklidir. Çoklu ekran kullanımı hakkında daha fazla bilgi için bkz. 65K'dan Fazla Yöntemle Uygulama Oluşturma .

Bu kitaplık, Android Destek Kitaplıklarını indirdikten sonra / extras / android / support / multidex / dizininde bulunur. Kitaplık, kullanıcı arabirimi kaynaklarını içermez. Bunu uygulama projenize dahil etmek için, kaynaklar olmadan kitaplıklar ekleme talimatlarını izleyin .

Bu kitaplık için Gradle derleme komut dosyası bağımlılık tanımlayıcısı aşağıdaki gibidir:

com.android.support:multidex:1.0.+ Bu bağımlılık notasyonu, 1.0.0 veya daha yüksek sürüm sürümünü belirtir.


Proguard'ı aktif olarak kullanarak ve bağımlılıklarınızı gözden geçirerek 65K yöntem sınırına ulaşmaktan yine de kaçınmalısınız.


6
+1, İnsanlar aynı kişi tarafından yanıtlandıklarında neden doğru yanıtlara oy vermiyor?
Pacerier

min api seviyesi 14 oldu!
Vihaan Verma

5
Size her derlemede mevcut yöntem sayınızı vermek için küçük bir Gradle eklentisi yazdık. Kitaplıkları yönetmemizde
philipp

53

bunun için multidex destek kitaplığını kullanabilirsiniz, multidex'i etkinleştirmek için

1) bağımlılıklara dahil edin:

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

2) Uygulamanızda etkinleştirin:

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

3) uygulamanız için bir uygulama sınıfınız varsa , aşağıdaki gibi attachBaseContext yöntemini geçersiz kılın :

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

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

4) Eğer varsa yok bir sahip uygulama daha sonra kayıt uygulamanız için sınıf android.support.multidex.MultiDexApplication Manifest'inizde dosyasında uygulama olarak. bunun gibi:

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

ve iyi çalışmalı!


32

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

"Google Play hizmetlerinin 6.5 sürümünden başlayarak, bir dizi bağımsız API arasından seçim yapabilecek ve görebilirsiniz"

...

"bu, geçişli olarak tüm API'lerde kullanılan 'temel' kitaplıkları içerecektir."

Bu muhtemelen sadece ihtiyaç örneğin basit bir oyun için iyi bir haber base, gamesbelki ve drive.

"API adlarının tam listesi aşağıdadır. Daha fazla ayrıntıyı Android Geliştirici sitesinde bulabilirsiniz .:

  • com.google.android.gms: play-services-base: 6.5.87
  • com.google.android.gms: oyun hizmetleri-reklamlar: 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: oyun hizmetleri-fitness: 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: oyun hizmetleri-oyunlar: 6.5.87
  • com.google.android.gms: play-services-wallet: 6.5.87
  • com.google.android.gms: oyun hizmetleri kimliği: 6.5.87
  • com.google.android.gms: play-services-cast: 6.5.87
  • com.google.android.gms: play-services-plus: 6.5.87
  • com.google.android.gms: play-services-appstate: 6.5.87
  • com.google.android.gms: play-services-giyilebilir: 6.5.87
  • com.google.android.gms: play-services-all-wear: 6.5.87

Eclipse projesinde bunun nasıl yapılacağına dair herhangi bir bilgi var mı?
Brian White

Henüz bu sürüme yükseltme yapabilecek durumda değilim. Ama projeniz Maven temelliyse, umarım bunu kendi içinde çözmeniz gerekir maven pom.
Csaba Toth

@ webo80 Bu yalnızca 6.5.87 sürümüne kadar iseniz yardımcı olur. Petey'in cevabını merak ediyorum, bu proguard kullanılmayan işlevleri ortadan kaldırıyor. Acaba bu 2. parti kitaplarını da içeriyor mu, yoksa sadece kendi şeyleriniz mi? Proguard hakkında daha fazla okumalıyım.
Csaba Toth

@BrianWhite Şimdilik tek çözüm .jar dosyasını bazı harici
araçlarla

1
@CsabaToth, beni kurtardın! Tam 'com.google.android.gms: play-services' yerine yukarıdaki listeden yalnızca birkaçını ekledim ve bu fark yarattı!
EZDsIt

9

Google Play hizmetlerinin 6.5'ten önceki sürümlerinde, API paketinin tamamını uygulamanızda derlemeniz gerekiyordu. Bazı durumlarda, bunu yapmak, uygulamanızdaki yöntemlerin sayısını (çerçeve API'leri, kitaplık yöntemleri ve kendi kodunuz dahil) 65.536 sınırının altında tutmayı zorlaştırdı.

6.5 sürümünden itibaren, bunun yerine Google Play hizmeti API'lerini seçmeli olarak uygulamanızda derleyebilirsiniz. Örneğin, yalnızca Google Fit ve Android Wear API'lerini dahil etmek için build.gradle dosyanızda aşağıdaki satırı değiştirin:

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

şu satırlarla:

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

daha fazla referans için buraya tıklayabilirsiniz


Tutulma sırasında nasıl yapılır?
Hardik9850

7

Kullanılmayan yöntemler son yapınızda olmayacağından, apk'nizi hafifletmek için proguard kullanın. Proguard'ı guava ile kullanmak için proguard yapılandırma dosyanızda aşağıdaki hususları iki kez kontrol edin (eğer buna zaten sahipseniz özür dilerim, yazarken bilinmiyordu):

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

Ek olarak, ActionbarSherlock kullanıyorsanız, v7 appcompat destek kitaplığına geçmek de yöntem sayınızı önemli ölçüde azaltacaktır (kişisel deneyime bağlı olarak). Talimatlar bulunur:


bu Warning: butterknife.internal.ButterKnifeProcessor: can't find superclass or interface javax.annotation.processing.AbstractProcessor./gradlew :myapp:proguardDevDebug
umut verici

1
Ancak geliştirme sırasında proguard genellikle çalıştırılmaz (sonunda Eclipse ile değil), bu nedenle bir sürüm derlemesi yapana kadar küçülmeden yararlanamazsınız.
Brian White

7

Google Play Hizmetleri gibi büyük harici kitaplıkları küçültmek için Jar Jar Bağlantılarını kullanabilirsiniz (16K yöntemleri!)

Senin durumunda Google'dan sadece soygun herşey hariç Hizmetler kavanoz, Play common internalve drivealt paketler.


4

Gradle kullanmayan Eclipse kullanıcıları için, Google Play Hizmetleri kavanozunu parçalayacak ve onu yalnızca istediğiniz parçalarla yeniden oluşturacak araçlar vardır.

Kullandığım dextorer tarafından strip_play_services.sh .

Bazı dahili bağımlılıklar olduğundan hangi hizmetlerin dahil edileceğini tam olarak bilmek zor olabilir, ancak küçük başlayabilir ve gerekli şeylerin eksik olduğu ortaya çıkarsa yapılandırmaya ekleyebilirsiniz.


3

Uzun vadede uygulamanızı birden fazla dex'te kırmanın en iyi yol olacağını düşünüyorum.


2
Bunu Gradle ile yapmanın uygun bir yolunu arıyorum: - / Herhangi bir ipucu var mı?
Ivan Morgillo


2

Multidex kullanmıyorsanız, bu da yapım sürecini çok yavaşlatır. Aşağıdakileri yapabilirsiniz. Yahska'nın bahsettiği gibi, belirli bir google play hizmeti kitaplığı kullanın. Çoğu durumda yalnızca bu gereklidir.

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

İşte tüm mevcut paketler Seçilerek API'leri yürütülebilir dosyanıza derliyor

Bu yeterli olmazsa, gradle komut dosyasını kullanabilirsiniz. Bu kodu 'strip_play_services.gradle' dosyasına koyun

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
}

}

Ardından bu komut dosyasını build.gradle dosyanıza şu şekilde uygulayın

apply plugin: 'com.android.application'
apply from: 'strip_play_services.gradle'

1

Google Play Hizmetlerini kullanıyorsanız, 20k + yöntemlerini eklediğini biliyor olabilirsiniz. Daha önce de belirtildiği gibi, Android Studio belirli hizmetlerin modüler olarak dahil edilmesi seçeneğine sahiptir, ancak Eclipse ile takılan kullanıcıların modülerleştirmeyi kendi ellerine alması gerekir :(

Neyse ki işi oldukça kolaylaştıran bir kabuk betiği var. Sadece google play hizmetleri jar dizinine çıkartın, sağlanan .conf dosyasını gerektiği gibi düzenleyin ve kabuk komut dosyasını çalıştırın.

Kullanımının bir örneği burada .


1

Google Play Hizmetlerini kullanıyorsanız, 20k + yöntemlerini eklediğini biliyor olabilirsiniz. Daha önce de belirtildiği gibi, Android Studio belirli hizmetlerin modüler olarak dahil edilmesi seçeneğine sahiptir, ancak Eclipse ile takılan kullanıcıların modülerleştirmeyi kendi ellerine alması gerekir :(

Neyse ki işi oldukça kolaylaştıran bir kabuk betiği var. Sadece google play hizmetleri jar dizinine çıkartın, sağlanan .conf dosyasını gerektiği gibi düzenleyin ve kabuk komut dosyasını çalıştırın.

Kullanımının bir örneği burada.

Aynen söylediği gibi, compile 'com.google.android.gms:play-services:9.0.0'sadece ihtiyacım olan kütüphanelerle değiştiriyorum ve işe yaradı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.