引论
如果你是做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个): Google Drive: Youtube:
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/ABI12345678910├── AndroidManifest.xml└── jniLibs├── armeabi│ └── libsnappydb-native.so├── armeabi-v7a│ └── libsnappydb-native.so├── mips│ └── libsnappydb-native.so└── x86└── libsnappydb-native.so
或者使用jniLibs.srcDir属性指定:
- 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是如何做的。
用户下载的安装包大小是不固定的,也就是根据用户设备的ABI自动选择相应的安装包。这是Google Play开发者和用户提供的一大功能。
做为开发者,可以很方便的生成ABI对应的APK,只需在Module/build.gralde中添加:
生成的APKs:
上传Google Play Developer Console时使用Advnaced Mode:
总结
Android开发给我们开发者提供了so这样一种机制,掌握它,利用它,让你的开发工作变得更轻松,让你写出的应用运行更流畅。
引用
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/