कैसे हटना कोड - डेक्स में 65k विधि सीमा


91

मेरे पास एक बड़ा एंड्रॉइड ऐप है जो कई लाइब्रेरी प्रोजेक्ट्स पर निर्भर करता है। एंड्रॉइड संकलक में प्रति .dex फ़ाइल की 65536 विधियों की सीमा है और मैं उस संख्या को पार कर रहा हूं।

जब आप विधि की सीमा से टकराते हैं तो मूल रूप से दो रास्ते चुन सकते हैं (कम से कम मुझे पता है)।

1) अपने कोड को सिकोड़ें

2) कई डीएक्स फाइलें बनाएँ ( यह ब्लॉग पोस्ट देखें )

मैंने दोनों को देखा और यह पता लगाने की कोशिश की कि मेरी विधि इतनी ऊंची जाने के लिए क्या कारण है। Google ड्राइव API 12,000 से अधिक पर अमरूद की निर्भरता के साथ सबसे बड़ा हिस्सा लेता है। ड्राइव एपीआई v2 के लिए कुल लाभ 23,000 से अधिक तक पहुँचते हैं!

मेरा प्रश्न मुझे लगता है कि आपको क्या लगता है कि मुझे क्या करना चाहिए? क्या मुझे अपने ऐप की एक विशेषता के रूप में Google डिस्क एकीकरण को हटा देना चाहिए? वहाँ एपीआई नीचे हटना करने के लिए एक रास्ता है (हाँ, मैं proguard का उपयोग करें)? क्या मुझे कई डीएक्स रूट पर जाना चाहिए (जो कि विशेष रूप से तीसरे पक्ष के एपीआई से निपटने के बजाय दर्दनाक दिखता है)?


2
मुझे आपके ऐप से प्यार हुआ। क्या आपने छद्म apkरूप में सभी अतिरिक्त कामों के लिए आवश्यक डाउनलोड करने के बारे में सोचा है ? मैं व्यक्तिगत रूप से ड्राइव एकीकरण देखना पसंद
करूंगा

8
फेसबुक ने हाल ही में अपने एंड्रॉइड ऐप में लगभग समान समस्या की तरह प्रतीत होने वाले वर्कअराउंड का दस्तावेजीकरण किया है। उपयोगी हो सकता है: facebook.com/notes/facebook-engineering/…
रूबेन स्क्रैटन

4
मल्टीपल डेक्स रूट को शुरू करना। मैंने Google ड्राइव के साथ काम करने के लिए सफलतापूर्वक एक सेकेंडरी डेक्स फ़ाइल बनाई। मुझे किसी के लिए बुरा लग रहा है कि एक निर्भरता के रूप में अमरूद की जरूरत है। : P यह अभी भी मेरे लिए एक बहुत बड़ा मुद्दा है
Jared Rummler

4
आप विधियों की गणना कैसे करते हैं?
Bri6ko

1
यहां कुछ अतिरिक्त नोट: stackoverflow.com/questions/21490382 (एक उपयोगिता के लिंक सहित, जो एपीके में विधि संदर्भों को सूचीबद्ध करेगा)। नोट 64K की सीमा कुछ मुद्दों से जुड़ी हुई फेसबुक समस्या से संबंधित नहीं है।
फादन

जवाबों:


69

ऐसा लगता है कि Google ने आखिरकार डेक्स फ़ाइलों की 65K विधि सीमा को पार करने के लिए वर्कअराउंड / फिक्स लागू कर दिया है।

65K संदर्भ सीमा के बारे में

एंड्रॉइड एप्लिकेशन (एपीके) फाइलों में डाल्विक एक्सेसेबल (डीईएक्स) फाइलों के रूप में निष्पादन योग्य बायटेकोड फाइलें होती हैं, जिसमें आपके ऐप को चलाने के लिए उपयोग किए गए संकलित कोड होते हैं। Dalvik निष्पादन योग्य विनिर्देश उन तरीकों की कुल संख्या को सीमित करता है जिन्हें किसी एकल DEX फ़ाइल में 65,536 में संदर्भित किया जा सकता है, जिसमें Android फ्रेमवर्क विधियाँ, लाइब्रेरी विधियाँ, और आपके अपने कोड में विधियाँ शामिल हैं। इस सीमा को पार करने के लिए आवश्यक है कि आप एक मल्टीडेक्स कॉन्फ़िगरेशन के रूप में जानी जाने वाली एक से अधिक डीएक्स फ़ाइल बनाने के लिए अपनी ऐप बिल्ड प्रक्रिया को कॉन्फ़िगर करें।

Android 5.0 से पहले मल्टीडेक्स समर्थन

एंड्रॉइड 5.0 से पहले प्लेटफॉर्म के संस्करण ऐप कोड को निष्पादित करने के लिए Dalvik रनटाइम का उपयोग करते हैं। डिफ़ॉल्ट रूप से, Dalvik एप्स को एक ही class.dex bytecode फाइल प्रति एपीके में सीमित करता है। इस सीमा के आसपास पाने के लिए, आप मल्टीडेक्स समर्थन पुस्तकालय का उपयोग कर सकते हैं , जो आपके ऐप की प्राथमिक DEX फ़ाइल का हिस्सा बन जाती है और फिर अतिरिक्त DEX फ़ाइलों और उनमें मौजूद कोड तक पहुँच का प्रबंधन करती है।

Android 5.0 और उच्चतर के लिए मल्टीडेक्स समर्थन

एंड्रॉइड 5.0 और उच्चतर एआरटी नामक रनटाइम का उपयोग करता है जो मूल रूप से एप्लिकेशन एपीके फ़ाइलों से कई डेक्स फ़ाइलों को लोड करने का समर्थन करता है। ART एप्लिकेशन इंस्टॉल समय पर पूर्व-संकलन करता है जो कक्षाओं के लिए स्कैन करता है (.. N) .dex फाइलें और उन्हें एंड्रॉइड डिवाइस द्वारा निष्पादन के लिए एकल .oat फ़ाइल में संकलित करता है। Android 5.0 रनटाइम पर अधिक जानकारी के लिए, देखें ART का परिचय दें

देखें: 65K मेथड के साथ बिल्डिंग ऐप्स


मल्टीडेक्स सपोर्ट लाइब्रेरी

यह लाइब्रेरी मल्टीपल Dalvik Executable (DEX) फाइलों के साथ ऐप्स बनाने के लिए सहायता प्रदान करती है। मल्टीडेक्स कॉन्फ़िगरेशन का उपयोग करने के लिए 65536 से अधिक तरीकों का संदर्भ देने वाले एप्लिकेशन आवश्यक हैं। मल्टीडेक्स का उपयोग करने के बारे में अधिक जानकारी के लिए, देखें 65K से अधिक तरीकों के साथ बिल्डिंग ऐप्स

आपके द्वारा Android समर्थन लाइब्रेरी डाउनलोड करने के बाद यह लाइब्रेरी / एक्स्ट्रा / एंड्रॉइड / सपोर्ट / मल्टीडेक्स / डायरेक्टरी में स्थित है। लायब्रेरी में उपयोगकर्ता इंटरफ़ेस संसाधन नहीं हैं। अपने एप्लिकेशन प्रोजेक्ट में इसे शामिल करने के लिए, संसाधनों के बिना पुस्तकालयों को जोड़ने के निर्देशों का पालन करें

इस लाइब्रेरी के लिए ग्रैड बिल्ड स्क्रिप्ट निर्भरता पहचानकर्ता निम्नानुसार है:

com.android.support:multidex:1.0.+ यह निर्भरता अंकन रिलीज़ संस्करण 1.0.0 या उच्चतर निर्दिष्ट करता है।


आपको अभी भी सक्रिय रूप से अपने रक्षक की समीक्षा करके और अपनी निर्भरता की समीक्षा करके 65K विधि की सीमा से टकराने से बचना चाहिए।


6
+1, जब लोग एक ही व्यक्ति द्वारा उत्तर दिए जाते हैं तो वे सही उत्तर क्यों नहीं देते हैं?
पचेरियर

न्यूनतम एपी स्तर 14 हो जाता है!
विहान वर्मा

5
हमने आपको प्रत्येक निर्माण पर अपनी वर्तमान विधि गणना देने के लिए एक छोटा सा ग्रेड प्लगइन लिखा था। पुस्तकालयों का प्रबंधन करने में हमारे लिए मददगार था - github.com/KeepSafe/dexcount-gradle-plugin
फिलिप

53

आपको लगता है कि के लिए multidex समर्थन लाइब्रेरी का उपयोग कर सकते हैं, सक्षम करने के लिए multidex

1) इसे निर्भरता में शामिल करें:

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

2) अपने ऐप में इसे सक्षम करें:

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

3) अगर आपके पास अपने ऐप के लिए एप्लिकेशन क्लास है तो ओवरराइड करें संलग्नक को इस तरह हटा दें :

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

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

4) यदि आपके पास अपने आवेदन के लिए एक आवेदन वर्ग नहीं है तो android.support.multidex.MultiDexApplication को अपने मैनिफ़ेस्ट फ़ाइल में अपने आवेदन के रूप में पंजीकृत करें। इस तरह:

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

और यह ठीक काम करना चाहिए!


31

Play Services6.5+ मदद करता है: http://android-developers.blogspot.com/2014/12/google-play-services-and-dex-method.html

"Google Play सेवाओं के संस्करण 6.5 से शुरू होकर, आप कई व्यक्तिगत API से चुन सकेंगे, और आप देख सकते हैं"

...

"यह सकारात्‍मक रूप से 'आधार' पुस्‍तकालयों को सम्‍मिलित करेगा, जिसका उपयोग सभी API में किया जाता है।"

यह एक सरल खेल उदाहरण के लिए आप शायद ही जरूरत के लिए अच्छी खबर है, base, gamesऔर हो सकता है drive

"एपीआई नामों की पूरी सूची नीचे है। अधिक जानकारी Android डेवलपर साइट पर देखी जा सकती है।:

  • com.google.android.gms: प्ले-सेवाओं आधार: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं-विज्ञापन: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं-appindexing: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं-नक्शे: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं-स्थान: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं-फिटनेस: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं-चित्रमाला: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं ड्राइव: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं-खेल: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं-वॉलेट: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं-पहचान: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं कास्ट: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं से अधिक: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं-AppState: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं-पहनने योग्य: 6.5.87
  • com.google.android.gms: प्ले-सेवाओं के सभी पहनने: 6.5.87

कैसे एक ग्रहण परियोजना के भीतर करने के लिए पर कोई जानकारी?
ब्रायन व्हाइट

मैं अभी तक उस संस्करण में अपग्रेड करने की स्थिति में नहीं हूं। लेकिन अगर आपकी परियोजना मावेन आधारित है, तो उम्मीद है कि आपको बस उस मावेन पोम में हल करना होगा।
सेसाबा तोथ

@ webo80 ठीक है, यह केवल तभी मदद करता है जब आप 6.5.87 संस्करण तक होते हैं। मुझे आश्चर्य है कि पेटी के जवाब के बारे में, जो अप्रयुक्त कार्यों को बंद कर देता है। मुझे आश्चर्य होता है कि इसमें 2 वाँ पार्टी लिब भी शामिल है, या सिर्फ आपका अपना सामान। मुझे प्रोगार्ड के बारे में अधिक पढ़ना चाहिए।
सेसाबा तोथ

@BrianWhite अब के लिए एकमात्र समाधान कुछ बाहरी उपकरण के साथ .jar फ़ाइल को पट्टी करने के लिए लगता है ..
milosmns

मैंने इस टूल का उपयोग करते हुए समाप्त किया: gist.github.com/dextorer/a32cad7819b7f272239b
ब्रायन व्हाइट

9

6.5 से पहले Google Play सेवाओं के संस्करणों में, आपको अपने ऐप में एपीआई के पूरे पैकेज को संकलित करना था। कुछ मामलों में, ऐसा करने से 65,536 की सीमा के तहत अपने ऐप में तरीकों की संख्या (फ्रेमवर्क एपीआई, लाइब्रेरी के तरीके, और अपने खुद के कोड सहित) को बनाए रखना अधिक कठिन हो गया।

संस्करण 6.5 से, आप इसके बजाय चुनिंदा Google Play सेवा API को अपने ऐप में संकलित कर सकते हैं। उदाहरण के लिए, केवल Google फ़िट और Android Wear API को शामिल करने के लिए, अपनी build.gradle फ़ाइल में निम्न पंक्ति प्रतिस्थापित करें:

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

इन पंक्तियों के साथ:

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

अधिक संदर्भ के लिए, आप यहां क्लिक कर सकते हैं


इसे ग्रहण में कैसे करें?
हार्दिक 9850

7

अपने एपीके को हल्का करने के लिए प्रोगार्ड का उपयोग करें क्योंकि अप्रयुक्त तरीके आपके अंतिम बिल्ड में नहीं होंगे। अमरूद के साथ प्रोगार्ड का उपयोग करने के लिए आप अपने रक्षक की फाइल में निम्नलिखित दोहरी जांच (मेरे माफी मांगने पर यदि आपके पास पहले से ही यह है, तो यह लिखने के समय ज्ञात नहीं था):

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

इसके अलावा, यदि आप एक्शनबारलॉक का उपयोग कर रहे हैं, तो v7 एप्कोम्पैट सपोर्ट लाइब्रेरी पर स्विच करने से आपकी विधि गणना बहुत कम हो जाएगी (व्यक्तिगत अनुभव के आधार पर)। निर्देश स्थित हैं:


यह Warning: butterknife.internal.ButterKnifeProcessor: can't find superclass or interface javax.annotation.processing.AbstractProcessor./gradlew :myapp:proguardDevDebug
प्रॉमिसिंग

1
हालांकि, विकास के दौरान, आम तौर पर प्राउड रन नहीं होता है (अंतिम बार ग्रहण के साथ नहीं) ताकि आप रिलीज़ बिल्ड बनाने तक संकोचन से लाभ न उठा सकें।
ब्रायन व्हाइट

7

आप जार जार लिंक का उपयोग कर सकते हैंGoogle Play Services (16K मेथड्स) जैसी विशाल बाहरी लाइब्रेरी को सिकोड़ने के लिए का !

आपके मामले में, आप Google Play Services के जार common internalऔर driveसब-पैकेजों को छोड़कर सब कुछ चीर देंगे ।


4

ग्रहण उपयोगकर्ताओं के लिए ग्रैडल का उपयोग नहीं करने के लिए, ऐसे उपकरण हैं जो Google Play Services के जार को तोड़ देंगे और इसे केवल उन हिस्सों के साथ फिर से बनाना चाहेंगे जो आप चाहते हैं।

मैं dextorer द्वारा strip_play_services.sh का उपयोग करता हूं

यह जानना मुश्किल हो सकता है कि किन सेवाओं को शामिल करना है क्योंकि कुछ आंतरिक निर्भरताएं हैं, लेकिन आप छोटे से शुरू कर सकते हैं और कॉन्फ़िगरेशन में जोड़ सकते हैं यदि यह पता चलता है कि आवश्यक चीजें गायब हैं।


3

मुझे लगता है कि लंबे समय में कई डेक्स में आपके ऐप को तोड़ना सबसे अच्छा तरीका होगा।


2
मैं ग्रेडेल के साथ ऐसा करने का एक उचित तरीका ढूंढ रहा हूं: - / कोई संकेत?
इवान मोर्गिलो


2

यदि मल्टीडेक्स का उपयोग नहीं करना है जो निर्माण प्रक्रिया को बहुत धीमा करता है। आप निम्नलिखित कर सकते हैं। जैसा कि yahska ने विशिष्ट Google Play सेवा लाइब्रेरी का उपयोग करने का उल्लेख किया है। ज्यादातर मामलों के लिए केवल इसकी जरूरत होती है।

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

यहाँ सभी उपलब्ध संकुल चुनिंदा एपीआई को आपके निष्पादन योग्य है

यदि यह पर्याप्त नहीं होगा, तो आप वर्गीकृत स्क्रिप्ट का उपयोग कर सकते हैं। इस कोड को फाइल '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
}

}

फिर इस लिपि को अपने build.gradle में इस तरह लागू करें

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

1

यदि Google Play Services का उपयोग कर रहे हैं, तो आप जान सकते हैं कि इसमें 20k + विधियाँ शामिल हैं। जैसा कि पहले ही उल्लेख किया गया है, एंड्रॉइड स्टूडियो के पास विशिष्ट सेवाओं के मॉड्यूलर समावेश का विकल्प है, लेकिन एक्लिप्स से चिपके हुए उपयोगकर्ताओं को अपने हाथों में संशोधन करना होगा :(

सौभाग्य से एक शेल स्क्रिप्ट है जो काम को काफी आसान बनाती है। बस Google Play सेवाएं जार निर्देशिका में निकालें, आपूर्ति की गई .conf फ़ाइल को आवश्यकतानुसार संपादित करें और शेल स्क्रिप्ट निष्पादित करें।

इसके उपयोग का एक उदाहरण यहाँ है


1

यदि Google Play Services का उपयोग कर रहे हैं, तो आप जान सकते हैं कि इसमें 20k + विधियाँ शामिल हैं। जैसा कि पहले ही उल्लेख किया गया है, एंड्रॉइड स्टूडियो के पास विशिष्ट सेवाओं के मॉड्यूलर समावेश का विकल्प है, लेकिन एक्लिप्स से चिपके हुए उपयोगकर्ताओं को अपने हाथों में संशोधन करना होगा :(

सौभाग्य से एक शेल स्क्रिप्ट है जो काम को काफी आसान बनाती है। बस Google play Services जार डायरेक्टरी को एक्सट्रैक्ट करें, सप्लाई की गई .conf फाइल को आवश्यकतानुसार एडिट करें और शेल स्क्रिप्ट को निष्पादित करें।

इसके उपयोग का एक उदाहरण यहाँ है।

जैसे उन्होंने कहा, मैं compile 'com.google.android.gms:play-services:9.0.0'सिर्फ उन पुस्तकालयों की जगह लेता हूं जिनकी मुझे जरूरत थी और यह काम किया।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.