项目背景,公司要将完整APP打包成AAR包,供其它厂商内嵌。外部厂商提供壳工程和相应的Application调用我们提供的aar包
一路走来踩了一堆坑。。。
这里先简要解释一下相关概念
1 什么是AAR包? AAR包相比于jar包,区别在哪儿?
aar包含所有资源,class,xml布局文件以及res资源文件全部包含。注意是全部。
jar只包含了class文件与清单文件,不包含资源文件,如图片等所有res中的文件。
捎带解释一下so库,
2 什么是so库?什么是ABI?相关的处理器型号在构建APP时有什么区别?
android系统目前支持以下七种不同的CPU架构:ARMv5,ARMv7 (从2010年起),x86 (从2011年起),MIPS (从2012年起),ARMv8,MIPS64和x86_64 (从2014年起),每一种都关联着一个相应的ABI。
应用程序二进制接口ABI(Application Binary Interface)定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集,内存对齐到可用的系统函数库。
so库的好处:
- so机制让开发者最大化利用已有的C和C++代码,达到重用的效果,利用软件世界积累了几十年的优秀代码;
- so是二进制,没有解释编译的开消,用so实现的功能比纯java实现的功能要快;
- so内存分配不受Dalivik/ART的单个应用限制,减少OOM;
- 相对于java代码,二进制代码的反编译难度更大,一些核心代码可以考虑放在so中。
在buildType标签下声明
ndk{
abiFilters "armeabi","armeabi-v7a","x86"
}
以上代码可以指定在构建时,生成支持这三类CPU的so库。
完整项目改造生成aar包的过程:
1 将原先module下的Application改为Library,正常调用assembleDebug 或 assembleRelease时就会在该module下的build/output目录下生成aar文件。
1.1 项目代码中的switch语句需要改为if语句
1.2 修改Manifest.xml文件
2 原有项目所依赖的jar包会被正常打包进aar中,但原项目依赖的aar则不会打包进aar
2.1 以外部compile形式所依赖的包,也不会被打包进aar
2.2 记得不要重复引用,避免壳工程引用的jar与打包好的aar冲突
3 声明具体支持的so库类型
3.1 最好在构建过程中声明所支持的CPU类型。
Android系统的匹配过程为从高到低,向下兼容,例如:armeabi-v7a类型的CPU支持armeabi
3.2 如果不在BuildType中声明,则默认支持所有类型的so库文件,通过反编译在aar中的lib目录下可以看到所支持的SO库类型
3.3 部分so库在不声明的情况下,默认打在armeabi下,这样会导致armeabi-v7a类型的包找不到相应的so库文件。解决办法就是强制声明为armeabi类型
3.4 注意so库存放的路径
4 Application参数的传递问题。
每一个Android App都有一个application context,这个参数需要壳工程传递给我们,调试的时候可以在壳工程的Manifest.xml中指定默认的Application.
并在默认的Application中初始化aar,
5 混淆
aar包也可以指定混淆方式,在提供给对方时,我们需要将代码混淆,在厂商发布时,也需要混淆,这样就存在二次混淆后,AAR包找不到相应类的问题。
解决方法可以让厂商在二次混淆时,keep住我们aar相关代码
6 常见BUG
6.1 java.lang.NoClassDefFoundError
NoClassDefFoundError错误的发生,是因为Java虚拟机在编译时能找到合适的类,而在运行时不能找到合适的类导致的错误。例如在运行时我们想调用某个类的方法或者访问这个类的静态成员的时候,发现这个类不可用,此时Java虚拟机就会抛出NoClassDefFoundError错误。与ClassNotFoundException的不同在于,这个错误发生只在运行时需要加载对应的类不成功,而不是编译时发生。
6.2 jar包引用重复
6.3 contentProvider在注册时出现重名情况
6.4 注意不要在壳工程的Activity中传递Context,可能出现Context为NULL的情况,最好在 壳工程的Application来初始化Context
6.5 java.lang.ExceptionInInitializerError
原因如果你在别的类调用getInstance,就会报错ExceptionInInitializerError。这是因为类加载时不会为实例变量赋值,对象创建时不会为静态变量赋值。我们调用getInstance时,此类就开始加载,加载的时候不会为实例变量赋值,但是会按顺序给静态变量赋值。需要检查变量初始化过程。