如何收缩代码 - 在DEX 65K方法限制(How to shrink code - 65k met

2019-07-21 20:40发布

我有一个相当大的Android应用程序,它依赖于许多库项目。 Android的编译器的每.DEX文件65536种方法的限制,我超越这个数字。

基本上有你可以选择两条路径(至少我所知道的),当你打的方法限制。

1)收缩您的代码

2)建立多个DEX文件( 见本博客文章 )

我看着这两个,并试图找出是什么造成我的方法算走得这么高。 该谷歌云端硬盘API需要与番石榴依赖最大的一块超过12000人。 用于驱动API V2总库达到23000!

我的问题我的猜测是,你觉得我该怎么办? 我应该删除谷歌驱动器集成为我的应用程序的功能? 有没有办法来缩小API下来(是的,我用proguard的)? 我应该去多DEX路线(这看起来相当痛苦的,尤其是与第三方的API处理)?

Answer 1:

它看起来像谷歌终于实现了超越的DEX文件的65K方法限制解决方法/修复。

关于65K的基准限制

Android应用(APK)文件包含的Dalvik执行文件(DEX)文件,其中包含用于运行你的应用程序的编译代码形式的可执行字节码文件。 Dalvik可执行规范限制了可以在一个DEX文件中被引用65536个,其中包括Android框架法,图书馆法,并在自己的代码方式方法的总数。 获取过去的这个限制,您需要配置您的应用程序的构建过程,生成多个文件DEX,被称为multidex配置。

Multidex支持为Android 5.0之前

之前的Android 5.0平台的版本使用的Dalvik运行时,用于执行应用程序的代码。 默认情况下,Dalvik的限制应用到每个APK一个classes.dex字节码文件。 为了绕过这个限制,可以使用multidex支持库 ,成为您的应用程序的主要DEX文件的一部分,然后设法获得了额外的DEX文件和它们所包含的代码。

针对Android 5.0及更高版本Multidex支持

Android 5.0以上使用称为ART运行时,其本身支持从应用APK文件加载多个DEX文件。 ART在应用执行预编译安装时,其扫描类(.. N).DEX文件,并将其编译成由Android装置用于执行的单个.oat文件。 对于在Android 5.0运行时的更多信息,请参见介绍ART 。

请参阅: 构建应用程序的使用在65K方法


Multidex支持库

该库提供了多个Dalvik执行文件(DEX)文件构建应用程序的支持。 引用超过65536点的方法的应用都需要使用multidex配置。 有关使用multidex的更多信息,请参阅构建应用程序与在65K方法 。

该库位于/演员/安卓/支持/ multidex /目录你下载Android支持库之后。 该库不包含用户界面资源。 把它列入你的应用程序项目,请按照指示添加库没有资源。

此库的摇篮构建脚本的依赖标识符如下:

com.android.support:multidex:1.0.+这种依赖性符号指定发行版本1.0.0或更高版本。


您仍然应该避免通过积极使用ProGuard和审查你的依赖击中65K方法限制。



Answer 2:

您可以使用multidex支持库的是,为了使multidex

1)它包括在依赖关系:

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

2)在您的应用程序启用它:

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

3)如果你有一个应用程序类为您的应用程序,然后重写这样的attachBaseContext方法:

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>

它应该很好地工作!



Answer 3:

Play Services 6.5+帮助: http://android-developers.blogspot.com/2014/12/google-play-services-and-dex-method.html

“6.5版本开始,谷歌的Play服务,你就可以从许多单个的API来接,你可以看到”

...

“这将传递地包括‘基地’库,其在所有API中。”

这是个好消息,对于一个简单的游戏,例如,你可能只需要basegames ,也许drive

“API名称的完整列表如下。更多细节可以在Android开发者网站上找到:

  • com.google.android.gms:播放服务基地:87年6月5日
  • com.google.android.gms:播放服务的广告:87年6月5日
  • com.google.android.gms:播放服务,appindexing:87年6月5日
  • com.google.android.gms:播放服务,地图:87年6月5日
  • com.google.android.gms:播放服务地点:87年6月5日
  • com.google.android.gms:播放服务的健身:87年6月5日
  • com.google.android.gms:播放服务全景:87年6月5日
  • com.google.android.gms:播放服务驱动的:87年6月5日
  • com.google.android.gms:游戏服务游戏:87年6月5日
  • com.google.android.gms:播放服务钱包:87年6月5日
  • com.google.android.gms:播放服务身份:87年6月5日
  • com.google.android.gms:播放服务铸:87年6月5日
  • com.google.android.gms:播放服务加:87年6月5日
  • com.google.android.gms:播放服务,APPSTATE:87年6月5日
  • com.google.android.gms:播放服务耐磨:87年6月5日
  • com.google.android.gms:播放服务,所有磨损:87年6月5日


Answer 4:

在谷歌之前的6.5播放服务的版本,你必须的API的整个包编译到您的应用程序。 在某些情况下,这样做变得更加困难,以保持在您的应用程序(包括框架API,库方法,以及你自己的代码)下65536极限的方法数。

从6.5版本,你可以选择,而不是谷歌编译播放服务API为您的应用程序。 例如,只包括谷歌飞度和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'

更多的参考,您可以点击这里



Answer 5:

搭配使用ProGuard以减轻你的apk为未使用的方法,不会在你的最终版本。 仔细检查你已经在你的ProGuard配置文件来使用ProGuard和番石榴以下(我的道歉,如果你已经有了这个,这不是在写本新闻时知道):

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

此外,如果您使用的是ActionbarSherlock,切换到V7程序兼容性支持库也将大量减少你的方法计数(根据个人经验)。 说明位于:

  • http://developer.android.com/tools/support-library/features.html#v7-appcompat动作条
  • http://developer.android.com/tools/support-library/setup.html#libs-with-res


Answer 6:

你可以使用瓶瓶链接缩小巨大的外部库像谷歌播放服务(16K方法!)

在你的情况你只会撕裂从谷歌Play服务的罐子除了一切common internaldrive子包。



Answer 7:

对于Eclipse用户不使用摇篮,有将打破谷歌播放服务罐子,只用你想要的部分重建它的工具。

我用的dextorer strip_play_services.sh 。

它可以是很难确切地知道哪些服务包括因为有一些内部的依赖关系,但你可以从小处着手,并添加到配置,如果事实证明,需要的东西不见了。



Answer 8:

我认为,从长远来看,打破你的应用程序在多个DE​​X将是最好的方式。



Answer 9:

多DEX支持将是对这一问题的官方解决方案。 见我的答案在这里的细节。



Answer 10:

如果不使用multidex其制作的构建过程非常缓慢。 你可以做到以下几点。 由于yahska提到,在使用特定的谷歌游戏服务库。 在大多数情况下只有这是必要的。

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

这里是所有可用的软件包选择性编译API整合到你的可执行文件

如果这将是不够的,你可以使用脚本的gradle。 把这个代码在文件“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'


Answer 11:

如果使用谷歌播放服务,你可能知道,它增加了20K +方法。 如前所述,Android的工作室拥有模块化纳入具体的服务选项,但用户坚持使用Eclipse必须采取模块化掌握在自己手中:(

幸运的是有一个shell脚本 ,使工作相当容易。 只需提取到谷歌播放服务jar目录,根据需要编辑.conf文件所提供的并执行shell脚本。

其使用的一个例子是在这里 。



Answer 12:

如果使用谷歌播放服务,你可能知道,它增加了20K +方法。 如前所述,Android的工作室拥有模块化纳入具体的服务选项,但用户坚持使用Eclipse必须采取模块化掌握在自己手中:(

幸运的是有一个shell脚本,使工作相当容易。 只需提取到谷歌播放服务jar目录,根据需要编辑.conf文件所提供的并执行shell脚本。

其使用的一个例子是在这里。

就像他说的,我代替compile 'com.google.android.gms:play-services:9.0.0'只是我需要和它的工作库。



文章来源: How to shrink code - 65k method limit in dex