最近我们看到很多开发者对在app中使用Realm对于apk文件的大小影响特别关注。在今天发布的0.79版本的realm中,我们对此作出了重大改进。
一个正常的啥都没有且没有使用任何其他类库,运行在ARM架构上的Android app一般最小为907kb(当然,在某些情况下,最低可以达到50kb,具体看这儿
https://realm.io/news/reducing-apk-size-native-libraries/#feb-17-update)。当使用0.78版本的realm时,app大概是3.9Mb,但是在使用0.79版本的realm时,apk的大小可以减小到1.6Mb,减小了70%。
下面来看下为什么以及如何实现
为什么你应该关心apk的大小
节省几Mb可能对现代设备的来说可能看起来不太多,但是在Android手机中有大量的旧的或低端设备并没有太大的剩余空间来使用。事实上,由于Android在新兴经济体尤其受欢迎,大多数运行Android app的设备看起来不像您的iPhones或Nexus - 有些只有几十兆字节的空间用来于所有的app,这意味着安装一个几兆的app 通常需要删除其他app。通常,app是否多几Mb对于那些空间有限的用户来说意味着是否去选择安装。
Native Libraries的解决办法
如果你下载了0.78版本的realm并且深入了解了你可以看到:
$ du -h -d1
28K ./com
468K ./io
9.1M ./lib <--- Native libraries
16K ./META-INF
9.6M
哎呀! Native libraries总共9MB,超过我们的jar大小的94%。 反过来,这将是用的apk文件增大超过3MB(压缩后)。
如果你读了FAQhttp://realm.io/docs/java#faq,你应该知道下载下来的realm jar包大小和添加realm到你的app中增加的apk文件大小并不相同。这是因为realm必须为手机的每个架构(例如arm,armv7-a,arm64)包含一个相同的本地库的副本。现在,你通常需要为ARM,ARMv7-A,ARM64,MIPS&x86,这5中架构的手机做适配,这也就意味着平均每个字节的native代码都要乘以5。
现在Android安装过程非常聪明,可以丢弃非相关CPU的native libraries。 例如,如果在MIPS设备上运行,它将只保留库的MIPS副本,并删除其他。 所以一旦你的app安装在设备上,Realm实际上会占用比你的APK看起来少很多的空间。你的app在应用商店显示的大小很大,进而会造成下载困难。
优化Native Libraries
假设我们忽略jar包中的Native code有多个副本的事实,仍然值得尝试看看可以做什么来优化每个编译版本的本机库的大小。
看看我们jar的lib文件夹中的文件,你可以看到我们支持的每个独立架构so库的大小。 (ps:在0.78中还不支持ARM64)
$ ls -lh lib/
lib/armeabi:
total 3776
-rwxr-xr-x@ 1 emanuelez staff 1.8M Jan 22 14:30 libtightdb-jni.so
lib/armeabi-v7a:
total 3672
-rwxr-xr-x@ 1 emanuelez staff 1.8M Jan 22 14:31 libtightdb-jni.so
lib/mips:
total 6376
-rwxr-xr-x@ 1 emanuelez staff 3.1M Jan 22 14:32 libtightdb-jni.so
lib/x86:
total 4888
-rwxr-xr-x@ 1 emanuelez staff 2.4M Jan 22 14:31 libtightdb-jni.so
(libtightdb是我们的C++核心库的名字,源于我们公司的以前名称“TightDB”;该项目现在正在重命名为realm-core。)
你可以看到ARM库要小得多。 这是因为他们可以利用Thumb指令集,而最棒的是,大多数Android设备运行在ARM处理器。(ps:thumb指令集是arm指令集的一个子集,运行在arm处理器上)
在这一点上,我们的核心(libtightdb)和JNI代码都是使用带有-Os标志的GCC编译的,它尝试在保持良好性能的同时优化库的大小。 我们还启用了-visibility=hidden来隐藏大部分不需要的ELF符号。
工具链技巧
那么能做些什么来改进呢? 我们尝试了几个设置并取得了很好的效果。 现在使用GCC工具链的两个额外的特性:
这让我们得到如下结果:
realm/build/intermediates/bundles/release/jni/armeabi:
total 1624
-rwxr-xr-x 1 emanuelez staff 809K Feb 11 09:30 libtightdb-jni.so
realm/build/intermediates/bundles/release/jni/armeabi-v7a:
total 1592
-rwxr-xr-x 1 emanuelez staff 793K Feb 11 09:30 libtightdb-jni.so
realm/build/intermediates/bundles/release/jni/mips:
total 3448
-rwxr-xr-x 1 emanuelez staff 1.7M Feb 11 09:30 libtightdb-jni.so
realm/build/intermediates/bundles/release/jni/x86:
total 2640
-rwxr-xr-x 1 emanuelez staff 1.3M Feb 11 09:30 libtightdb-jni.so
我们将库的大小减半了! 这使jar文件的总大小从3.1 MB到2.1 MB。 相当大的改进!
APK拆分
但还有一个事情,Android开发可以做到更好:APK拆分。 这是添加到Android Gradle插件中一个相对较新的功能,它保证为每个CPU提供一个APK,只包含相关的本机库。
不幸的是,当native libraries捆绑在jar文件中时,此功能不起作用,但是有一个可以使用的快速解决方法。
我们的发行包(网站目录:Download->Java)包含一个名为“eclipse”的文件夹。 此文件夹包含Realm库的拆分版本。 所有你需要做的是将jar文件复制到app的libs文件夹中,并复制src / main / jniLibs目录中的四个文件夹。 现在您可以在build.gradle文件中启用ABI拆分,如下所示:
android {
// Some other configuration here...
splits {
abi {
enable true
reset()
include 'x86', 'armeabi', 'armeabi-v7a', 'mips'
universalApk false
}
}
}
现在Gradle将生成一个APK每个CPU减小的尺寸更进一步!
总结回顾:
把这一切放在一起,对于大多数(基于ARM)的设备来说,较小的编译大小和APK拆分意味着Realm现在只会让你的apk增加约730KB。 这比以前的3MB领域的76%少了会添加到你的应用程序过去!
我们将不断尝试将这个数字降低,我们期待帮助您保持您的应用程序精益和干净,无论你部署在哪种架构的设备上。
所以综上得出结论:
第一步:
defaultConfig {
applicationId "xxx"
minSdkVersion 19
targetSdkVersion 24
versionCode 1
versionName "1.0"
ndk {
//设置支持的cpu
abiFilters 'armeabi' //'armeabi-v7a', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
}
第二步:
splits {
abi {
enable true
reset()
include 'armeabi'
universalApk false
}
}
完毕!
pps:realm 2.x版本对于‘armeabi’不支持,所以在build.gradle文件中要做如下修改
ndk { abiFilters 'armeabi' ,'armeabi-v7a' }