Android 项目开发填坑记 - 使用 MultiDex 解决 64K 限制

JewSkene 8年前
   <p>Android 的 classLoader 在加载 APK 的时候限制了 class.dex 包含的 Java 方法数,其总数不能超过65535(64K, <strong>不要再说成 65K</strong> 了,1K = 2^10 = 1024 , 64 * 1024 = 65535),Google 官方给出的解决方案是使用 <a href="/misc/goto?guid=4959676865080574261" rel="nofollow,noindex">Multidex</a> 。</p>    <h2>启用 Multidex</h2>    <p>基本要求:</p>    <ul>     <li>使用 Android Studio 开发工具</li>     <li>Android SDK Build Tools >= 21.1</li>     <li>更新 Android Support Repository 到最新版本</li>    </ul>    <p>配置步骤:</p>    <ol>     <li>配置 Gradle build 来开启 multidex</li>     <li>修改 manifest 来引用 MultiDexApplication 类</li>    </ol>    <p>修改 module 下的 build.gradle 文件,添加支持库并开启 multidex:</p>    <pre>  <code class="language-java">android {      compileSdkVersion 21      buildToolsVersion "21.1.0"        defaultConfig {          ...          minSdkVersion 14          targetSdkVersion 21          ...            // Enabling multidex support.          multiDexEnabled true      }      ...  }    dependencies {    compile 'com.android.support:multidex:1.0.0'  }  </code></pre>    <p>PS: compileSdkVersion、buildToolsVersion 根据实际项目配置,但是版本不能低于上述版本。</p>    <p>在 AndroidManifest.xml 中给 application 节点添加对 MultiDexApplication 类的引用:</p>    <pre>  <code class="language-java"><?xml version="1.0" encoding="utf-8"?>  <manifest xmlns:android="http://schemas.android.com/apk/res/android"   package="xxx">      <application   ...   android:name="android.support.multidex.MultiDexApplication">          ...      </application>  </manifest>  </code></pre>    <p>PS: manifest 节点的 package 属性值根据实际项目有所不同。</p>    <p>注意:如果你的 APP 使用了继承 Application 的类,你需要重写 attachBaseContext() 方法并调用 MultiDex.install(this) 来启用 multidex 。</p>    <pre>  <code class="language-java">public class XXX extends Application{   protected void attachBaseContext(Context base) {    super.attachBaseContext(base);    MultiDex.install(this);   }  }  </code></pre>    <p>网上搜到还有一个方法:不继承 Application ,而是直接继承 MultiDexApplication 即可,这样就不需要重写 attachBaseContext() 方法了。</p>    <p>参考资料: <a href="/misc/goto?guid=4959676865080574261" rel="nofollow,noindex">Configure Apps with Over 64K Methods</a></p>    <h2>可能遇到的问题</h2>    <h3>NoClassDefFoundError</h3>    <p>Android SDK Build Tools 21.1 或者更高版本中的 Gradle Android 插件有对 multidex 的支持。这个插件使用 Proguard 来分析你的项目并在 [buildDir]/intermediates/multi-dex/[buildType]/maindexlist.txt 文件中生成一个 app 启动 classes 的列表。但是这个列表并不是100%准确,可能会丢失一些app启动所需的 classes 。</p>    <p>如果你在本地的测试机上没有遇到这个问题,并不代表你的 APP 没有问题,我通过查看友盟的崩溃记录和使用一些真机测试平台来进行检查,通常情况下会有所发现。</p>    <p>解决方法:在 module 下创建 multidex.keep 文件,并在其中罗列出那些 class,以便让编译器知道在 main dex 文件中要保持哪些 class。</p>    <p>生成 multidex.keep 文件中的内容有多种:</p>    <p>方法一:修改 module 下的 build.gradle 文件</p>    <pre>  <code class="language-java">apply plugin: 'com.android.application'  android {    ...  }  dependencies {    ...  }  android.applicationVariants.all { variant ->      task "fix${variant.name.capitalize()}MainDexClassList" << {          logger.info "Fixing main dex keep file for $variant.name"          File keepFile = new File("$buildDir/intermediates/multi-dex/$variant.buildType.name/maindexlist.txt")          keepFile.withWriterAppend { w ->              // Get a reader for the input file              w.append('\n')              new File("${projectDir}/multidex.keep").withReader { r ->                  // And write data from the input into the output                  w << r << '\n'              }              logger.info "Updated main dex keep file for ${keepFile.getAbsolutePath()}\n$keepFile.text"          }      }  }  tasks.whenTaskAdded { task ->      android.applicationVariants.all { variant ->          if (task.name == "create${variant.name.capitalize()}MainDexClassList") {              task.finalizedBy "fix${variant.name.capitalize()}MainDexClassList"          }      }  }  </code></pre>    <p>方法二:修改 module 下的 build.gradle 文件</p>    <pre>  <code class="language-java">apply plugin: 'com.android.application'  android {    ...    afterEvaluate {          tasks.matching {              it.name.startsWith('dex')          }.each { dx ->              if (dx.additionalParameters == null) {                  dx.additionalParameters = []              }              dx.additionalParameters += '--multi-dex' // enable multidex                // optional              dx.additionalParameters += "--main-dex-list=$projectDir/class-list.txt".toString() // enable the main-dex-list              dx.additionalParameters += '--minimal-main-dex'          }      }  }  dependencies {    ...  }  </code></pre>    <p>使用上述任意方式配置完成后, clean 然后 rebuild 项目,完成之后在 module 下的 build/intermediates/multi-dex/xxx 里找到 maindexlist.txt 文件(如果找不到相关目录,可能需要你同步后 rebuild 项目才能生成),复制里面的内容到 module 根目录下 multidex.keep 文件中(没有则先创建此文件)。</p>    <p>然后,比较重要的一步就是:通过友盟、测试记录、Bug记录等获取到 NoClassDefFoundError 错误对应的类,按照 maindexlist.txt 文件的方式添加这些类到 multidex.keep 文件中就可解决了。</p>    <h3>其他错误和问题</h3>    <p>比如 <strong>首次安装启动时黑屏没有响应/ANR</strong> 、 <strong>安装时异常</strong> 等,你可以参考文末的一些文章,此外你还可以参考 <a href="/misc/goto?guid=4959676865174237786" rel="nofollow,noindex">Android 必知必会-Android Splash 页秒开之细节处理</a> 来优化启动体验。</p>    <p>参考资料和推荐阅读:</p>    <ul>     <li><a href="/misc/goto?guid=4959676865258883723" rel="nofollow,noindex">Android Multidex 遇到的问题</a> (推荐此文)</li>     <li><a href="/misc/goto?guid=4959676865351099470" rel="nofollow,noindex">Android的multidex带来的性能问题-减慢app启动速度</a></li>     <li><a href="/misc/goto?guid=4959676865419107712" rel="nofollow,noindex">ClassNotFoundException</a></li>    </ul>    <h2>总结</h2>    <p>这是一篇早就准备写的文章,但当时搜集的资料未及时保存或者丢失,就拖到了现在。因为一个比较旧的 APP 也遇到了相关的问题,所以重新搜集了下资料整理发布出来了,希望能帮到遇到相关问题的朋友们。</p>    <p>PS:你可以通过下面的方式和我联系</p>    <p> </p>    <p>来自:http://likfe.com/2016/08/17/android-multiDex/</p>    <p> </p>