问题起因
我曾经在开发Android Application的过程中遇到过那个有名的65k方法数的问题。如果你开发的应用程序变得非常庞大,你八成会遇到这个问题。
这个问题实际上体现为两个方面:
一、65k方法数
Android的APK安装包将编译后的字节码放在dex格式的文件中,供Android的JVM加载执行。不幸的是,单个dex文件的方法数被限制在了65536
之内,这其中除了我们自己实现的方法之外,还包括了我们用到的Android Framework方法、其他library包含的方法。如果我们的方法总数超过了这个限制,那么我们在尝试打包时,会抛出如下异常:
Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536
在比较新的Android构建工具下可能是如下异常:
trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.
二、APK安装失败
Android官方推荐了一个叫做MultiDex的工具,用来在打包时将方法分散放到多个dex内,以此来解决65K方法数的问题。但是,除此之外,方法数过多还会带来dex文件过大的问题。
在安装APK时,系统会运行一个叫做dexopt
的程序,dexopt会使用Dalvik LinearAlloc
缓冲区来存储应用的方法信息。在Android 2.x的系统中,该缓冲区大小仅为5M,当我们的dex文件过大超过该缓冲区大小时,就会遇到APK安装失败的问题。
思路
对于如上的两个问题,有个非常有名的方案,就是采用动态加载插件化APK的方法。
插件化APK的思路为:将部分代码分离出来放在另外的APK中,做成插件APK的形式,在我们的应用程序启动后,在使用时动态加载该插件APK中的内容。
该思路简单来说便是将部分代码放在了另外一个独立的APK中,而不是放在我们自己的dex中。这样一方面减少了我们自己dex中方法总数,另一方面也减小了dex文件的大小,因此可以解决如上两个方面的问题。对于这个插件APK包含的类,我们可以在使用到的时候再加载进来,这便是动态加载的思路。
要实现插件化APK,我们只需要解决如下3个问题:
-
如何生成插件APK
-
如何加载插件APK
-
如何使用插件APK中的内容
类加载器
在实现插件化APK之前,我们需要先了解一下Android中的类加载机制,作为实现动态加载的基础。
在Android中,我们通过ClassLoader
来加载应用程序运行需要的类。ClassLoader
是一个抽象类,我们需要继承该类来实现具体的类加载器的行为。在Android中,ClassLoader
的实现类采用了代理模型(Delegation Model)
来执行类的加载。每一个ClassLoader
类都有一个与之相关联的父加载器,当一个ClassLoader
类尝试加载某个类时,首先会委托其父加载器加载该类。如果父加载器成功加载了该类,则不会再由该子加载器进行加载;如果父加载器未能加载成功,则再由子加载器进行类加载的动作。
在Android中,我们一般使用DexClassLoader
和PathClassLoader
进行类的加载。
-
DexClassLoader
: 可以从.jar或者.apk文件中加载类; -
PathClassLoader
: 只能从系统内存中已安装的内容中加载类。
对于我们的插件化APK,显然需要使用DexClassLoader
进行自定义类加载。我们看一下DexClassLoader
的构造方法:
/**
* Create DexClassLoader
* @param dexPath String: the list of jar/apk files containing classes and resources, delimited by File.pathSeparator, which defaults to ":" on Android
* @param optimizedDirectory String: directory where optimized dex files should be written; must not be null
* @param librarySearchPath String: the list of directories containing native libraries, delimited by File.pathSeparator; may be null
* @param parent ClassLoader: the parent class loader
*/