引论

如果你是做Android系统开发,对于so文件应该不会默认;如果是做应用开发,可能更了解jar或aar文件,但你也会不经意的在开发中或是要用到的第三库/SDK中看到so的身影,那你应该往下看,全面了解Android应用在运行时的另一个重要文件类型so。

什么是so文件

so是shared object的缩写,见名思义就是共享的对象,机器可以直接运行的二进制代码。大到操作系统,小到一个专用软件,都离不开so。参见https://en.wikipedia.org/wiki/Library_(computing)
so主要存在于Unix和Linux系统中。

.so vs .a

.a:archive

存档的含义,是unix系统中对于静态库的文件后缀,在软件打包时和主程序表态链接在一起,表现形式是在链接成同一个文件。go lang即广泛采用这一形式,对于软件分发只有一个文件。对于打包好的软件来讲,这是专属库,所有都在出厂前打包在一起了,好处是不受外界影响,坏处是任何改动要全部分发。对于安装应用的系统来讲,当然是共享的越多越好,既省内存又省硬盘。

.so:shared object

共享库,用过Windows的同学应该都或多或少碰到过找不到DLL或DLL错误之类的问题,其中最为著名的问题就是DLL Hell(某个著名的库,软件a使用1.0,新装的软件b使用1.0.1,导致软件a运行异常),DLL即Dynamic Link Library的缩写,和shared object表示同样的事物,只是名字不同而已。运行时按需加载,不论是系统提供的共享库还是自带的共享库,最大化利用软件分治的原理,修Bug也是更新所在so文件,不需全部更新。

Android中的so

so是与平台相关的二进制机器码,与ABI(Application Binary Interface)相对应,一个ABI表示相应的CPU的指令集与内存页管理,也对应于不同的C运行环境,所以so是有不同的系统版本的。
随着Android系统的快速发展,搭载Android的硬件平台也早已多样化了(对比WinTel联盟,直到2012年才新发展了Windows RT来适配ARM平台,2015年的Win10才进入 Raspberry Pi 2这类基于ARM的新型设备中),现在已经运行在7个ABI:armeabi,armeabi-v7a (armeabi-v7a-hard),arm64-v8a,x86,x86_64,mips 和 mips64。

为什么使用

上面主要从软件开发的角度说明了为什么设计so以及开发者为什么使用so,由于Android基于Linux Kernl的,也继承了Linux中所有so相关的设计。除了系统方面的原因,Android开发者还要知道以下几点:

  • so机制让开发者最大化利用已有的C和C++代码,达到重用的效果,利用软件世界积累了几十年的优秀代码
  • so是二进制,没有解释编译的开消,用so实现的功能比纯java实现的功能要快
  • so内存分配不受Dalivik/ART的单个应用限制,减少OOM
    基于以上的各种好处,so在Android开发中大量使用,利用Native Lib Monitor可以看到:
    微信(将近30个):
    so
    Google Drive:
    so
    Youtube:
    so

PS1:从Youtube中的so文件可以看出安装后的应用混合使用arm和arm64两种ABI,体现了硬件的兼容性。ARM64和X86可以运行ARMv7,X86对ARMv7采用软件兼容,在CPU层面多一个指令集翻译环节,所以如果你是so文件的提供者,就算是增加工作量,也最好提供覆盖全渐ABI,让最终APP的性能不因为你而打折,这一点非常重要;作为应用开发者,在有选择的情况下,尽量选用覆盖全部ABI的库,体现为背后的技术实力。
PS2:一个APK包中可以包含多个ABI的Library,而Android在安装APK时,会选择与自己最匹配的ABI安装到应用中。
PS3:由PS1引发的一个问题:深爱广大Android开发者喜欢的Genymotion模拟器是基于virtualbox的x86架构,如果使用了不含x86 ABI的so库,就不能安装到Genymtion中。另外,如果Genymotion没有提供Play Service,如果开发需要的话,要安装Googel Apps x86。可以到根据你的模拟器系统版本下载合适的安装包,直接把下载好的zip包拖到Genymtion模拟器中,赚慢的话,也可以直接从我的百度网盘下载。

如何使用

  • Android Studio,将得到的ABI放到jniLibs/ABI
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ├── AndroidManifest.xml
    └── jniLibs
    ├── armeabi
    │ └── libsnappydb-native.so
    ├── armeabi-v7a
    │ └── libsnappydb-native.so
    ├── mips
    │ └── libsnappydb-native.so
    └── x86
    └── libsnappydb-native.so

或者使用jniLibs.srcDir属性指定:

1
2
3
4
5
6
7
8
android {
sourceSets {
main {
jni.srcDirs = [] //disable automatic ndk-build call
jniLibs.srcDir 'main/libs'
}
}
}

  • eclipse中直接放到libs/ABI目录。
  • aar文件中,so处于jni/ABI目录中,对于库开发者和应用开发者都不必关注,全部自动处理。
  • 在生成的APK中,所有so文件对应于lib/ABI中。
  • 当APK安装到Android系统中时,so文件位置:
    Android<5.0,/data/data/PACKAGE_NAME/lib
    Android>=5.0,/data/app/PACKAGE_NAME/lib/CPU_ARCH/和/data/data/PACKAGE_NAME/lib

可能会出现的问题

使用了so本地库后,开发者可能会遇到“UnsatisfiedLinkError”,“dlopen: failed”等等能看见的问题,还会引发应用崩溃或性能低下等更隐蔽的问题。

避免问题的途径

主要针对so文件的提供者:

  • 请尽量覆盖全部ABI
  • Android NDK向前但不向后兼容,可以适当忽略“compile against the latest platform”这类优化提示。简单说,利用NDK针对android-17生成的so文件可以在android-22上运行,反之却不行。这点与Android SDK的兼容性不一样,在SDK14上编译的应用,在API23上也是可以运行的;在SDk23上的编译的应用,只要minSdkVersion小于14,同样在API14上可以运行。.

对于采用so的应用开发者

尽量保证你所使用的so文件编译编译的一致性,要了解你使用的库,在选择新库时要全面考虑。

针对Google Play进行分ABI上传

上文中提到尽量使用覆盖全部ABI的库,其直接结果是让APK变大了好多,显然增加上用户下载负担,因为最终只会用到一个ABI,而下载的其它ABI就白白浪费了。
我们来看Google的明星产品Maps是如何做的。

gmaps_apk_size_info

用户下载的安装包大小是不固定的,也就是根据用户设备的ABI自动选择相应的安装包。这是Google Play开发者和用户提供的一大功能。
做为开发者,可以很方便的生成ABI对应的APK,只需在Module/build.gralde中添加:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
android{
...
splits {
abi {
enable true
reset()
include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' //select ABIs to build APKs for
//universalApk true //generate an additional APK that contains all the ABIs
}
}
// map for the version code
project.ext.versionCodes = ['armeabi': 1, 'armeabi-v7a': 2, 'arm64-v8a': 3, 'mips': 5, 'mips64': 6, 'x86': 8, 'x86_64': 9]
android.applicationVariants.all { variant ->
// assign different version code for each output
variant.outputs.each { output ->
output.versionCodeOverride =
project.ext.versionCodes.get(output.getFilter(
com.android.build.OutputFile.ABI), 0) * 1000000 + android.defaultConfig.versionCode
}
}
}

生成的APKs:

multi_abi_apks.png

上传Google Play Developer Console时使用Advnaced Mode
upload_apks_adv_mode.png

总结

Android开发给我们开发者提供了so这样一种机制,掌握它,利用它,让你的开发工作变得更轻松,让你写出的应用运行更流畅。

we_are_just_begining.png

引用

http://stackoverflow.com/questions/9688200/difference-between-shared-objects-so-static-libraries-a-and-dlls-so
http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html
https://developer.android.com/ndk/guides/abis.html
https://software.intel.com/en-us/blogs/2012/11/12/how-to-publish-your-apps-on-google-play-for-x86-based-android-devices-using
http://stackoverflow.com/questions/31666412/how-to-order-multiple-apks-with-native-libraries-in-google-play-store
http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
https://androidbycode.wordpress.com/2015/07/07/android-ndk-a-guide-to-deploying-apps-with-native-libraries/