我有一个相当大的Android应用程序,它依赖于许多库项目。 Android编译器对每个.dex文件有65536个方法的限制,我超过了这个数字。
当您达到方法限制时,基本上可以选择两条路径(至少我知道)。
1)缩小你的代码
2)构建多个dex文件(see this blog post)
我调查了两者,并试图找出导致我的方法计数过高的原因。 Google Drive API占据了Guava依赖的最大块,超过12,000。 Drive API v2的总库存超过23,000!
我的问题是,您认为我应该怎么做?我应该删除Google云端硬盘集成作为我应用的功能吗?有没有办法缩小API(是的,我使用proguard)?我应该采用多重dex路由(看起来相当痛苦,尤其是处理第三方API)?
答案 0 :(得分:66)
看起来谷歌最终实施了一种解决方法/修复,超越了dex文件的65K方法限制。
关于65K参考限值
Android应用程序(APK)文件包含 Dalvik可执行文件(DEX)形式的可执行字节码文件 文件,其中包含用于运行应用程序的已编译代码。该 Dalvik可执行规范限制了方法的总数 可以在单个DEX文件中引用到65,536,包括 您自己的Android框架方法,库方法和方法 码。超过此限制要求您配置应用程序 构建进程以生成多个DEX文件,称为multidex 配置。
Android 5.0之前的Multidex支持
Android 5.0之前的平台版本使用Dalvik运行时 用于执行应用程序代码默认情况下,Dalvik将应用程序限制为单个 每个APK的classes.dex字节码文件。为了解决这个问题 限制,你可以使用multidex support library,它变成了 应用程序的主要DEX文件的一部分,然后管理对其的访问 附加的DEX文件及其包含的代码。
针对Android 5.0及更高版本的Multidex支持
Android 5.0及更高版本本身使用名为ART的运行时 支持从应用程序APK文件加载多个dex文件。艺术 在扫描的应用程序安装时执行预编译 classes(.. N).dex文件并将它们编译成单个.oat文件 由Android设备执行。有关Android的更多信息 5.0运行时,请参阅Introducing ART。
请参阅:Building Apps with Over 65K Methods
Multidex支持库
此库为构建提供支持 具有多个Dalvik可执行文件(DEX)文件的应用程序。引用的应用 使用multidex配置需要超过65536种方法。 有关使用multidex的更多信息,请参阅Building Apps with Over 65K Methods。
这个库位于/ extras / android / support / multidex / 下载Android支持库后的目录。该 库不包含用户界面资源。把它包括在内 您的应用项目,请按照Adding libraries without resources.
的说明进行操作此库的Gradle构建脚本依赖项标识符为 如下:
com.android.support:multidex:1.0.+此依赖项表示法指定 1.0.0或更高版本。
您应该通过积极使用proguard并查看依赖项来避免达到65K方法限制。
答案 1 :(得分:52)
您可以使用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>
它应该可以正常工作!
答案 2 :(得分:31)
Play Services
6.5+有助于:
http://android-developers.blogspot.com/2014/12/google-play-services-and-dex-method.html
&#34;从6.5版本的Google Play服务开始,您将能够 从众多单独的API中挑选,您可以看到&#34;
...
&#34;这将过渡性地包括使用的“基础”库 跨所有API。&#34;
这是一个好消息,例如,对于一个简单的游戏,您可能只需要base
,games
和drive
。
&#34;完整的API名称列表如下。更多细节可以在上找到 Android开发者网站。:
答案 3 :(得分:9)
在6.5之前的Google Play服务版本中,您必须将整个API包编译到您的应用中。在某些情况下,这样做会使您的应用程序中的方法数量(包括框架API,库方法和您自己的代码)更难以保持65,536的限制。
从版本6.5开始,您可以选择性地将Google Play服务API编译到您的应用中。例如,要仅包含Google Fit和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'
如需更多参考,请点击here
答案 4 :(得分:7)
使用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 appcompat支持库也会减少您的方法数量(基于个人经验)。说明位于:
答案 5 :(得分:7)
您可以使用Jar Jar Links缩小巨大的外部库,例如Google Play服务(16K方法!)
在您的情况下,除了common
internal
和drive
子包之外,您只需从Google Play服务jar中删除所有内容。
答案 6 :(得分:4)
对于不使用Gradle的Eclipse用户,有些工具会分解Google Play Services jar并仅使用您想要的部分重建它。
我使用strip_play_services.sh by dextorer。
可能很难确切地知道要包含哪些服务,因为存在一些内部依赖关系,但如果事实证明缺少所需的东西,则可以从小规模开始并添加到配置中。
答案 7 :(得分:3)
我认为从长远来看,以多种dex打破你的应用程序将是最好的方式。
答案 8 :(得分:2)
多语言支持将是此问题的官方解决方案。有关详细信息,请参阅my answer here。
答案 9 :(得分:2)
如果不使用使构建过程非常慢的multidex。 您可以执行以下操作。 提到的yahska使用特定的Google Play服务库。 对于大多数情况,只需要这样做。
compile 'com.google.android.gms:play-services-base:6.5.+'
以下是所有可用的包Selectively compiling APIs into your executable
如果这还不够,您可以使用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'
答案 10 :(得分:1)
如果使用Google Play服务,您可能知道它增加了20k +方法。如前所述,Android Studio可以选择模块化包含特定服务,但是使用Eclipse的用户必须自己动手进行模块化:(
幸运的是there's a shell script使这项工作相当容易。只需解压缩到google play services jar目录,根据需要编辑提供的.conf文件并执行shell脚本。
使用它的一个例子是here。
答案 11 :(得分:1)
如果使用Google Play服务,您可能知道它增加了20k +方法。如前所述,Android Studio可以选择模块化包含特定服务,但是使用Eclipse的用户必须自己动手进行模块化:(
幸运的是,有一个shell脚本可以让这项工作变得相当容易。只需解压缩到google play services jar目录,根据需要编辑提供的.conf文件并执行shell脚本。
这里有一个使用的例子。
就像他说的那样,我只用我需要的库替换compile 'com.google.android.gms:play-services:9.0.0'
并且它有效。