Bleoo

志同道合,一杯酒。

  • 主页
  • 随笔
  • Android

Bleoo

志同道合,一杯酒。

  • 主页
  • 随笔
  • Android

Android 5.0到Android 6.0+的so动态库的适配问题

2017-08-31

前言

先简单阐述下几个概念,这些不是重点。

  1. Android编译时候的 api 版本,指的是你要生成的这个 apk 所依赖的 sdk 版本,例如 api23 即是 Android 6.0。
  2. .so 动态链接库,为 Linux 下的库文件,Windows 是 .dll,Android 是基于 Linux 内核的,所以使用的是 .so,在安卓上面,一般由 C/C++ 语言进行 Jni 编程后,采用 NDK 工具编译后所生成的。
  3. .so的作用,主要是提供系统底层函数,供应用层使用。不用它行不?可以,在Android已经提供了的情况下,你不需要再自己添加,例如一个 View 的绘制,里面都有很多 Native 关键词的函数,这个就是底层函数,Android api 对应的是它已经提供了。

问题来了

发生的环境:

此类问题一般发生在 Android 6.0 及其以上的系统,具体也存在于其他的 api 版本,主要集中在 api >=23;

具体表现是:

同一个 APP 在 api <=22 的 sdk 情况下编译,可以运行正常,不存在闪退或者 .so 库加载失败的情况,当你采用 api >=23 的sdk 编译的时候,安装到 Android 6.0 及其以上的手机的时候,大范围出现崩溃 或者 .so 库加载失败,而在 6.0 以下的手机却正常。

Catch的信息:

1
dlopen failed: cannot locate symbol "XXXX" xxxx.so

此类崩溃信息,你完全可以对号入”座”。

为何如此

​ 如果只为解决问题,可以不看这部分。

  现在我用一句话说白它,就是:不同链接方式时,dlopen会打开指定的系统中(手机中)或提供的动态库,并使用 dlsym 获取符号地址,也就是说,如果,在此时的手机中如果找不到,那么就会出问题,一般和 API 有关系。

  人为因素就是,编译这个 .so 库的人,他在编译的时候没考虑到下面这些情况,导致提供给别人用的时候,或者自己用的时候在高 API 版本手机出现问题。   

NDK兼容性

​ 使用NDK时,你可能会倾向于使用最新的编译平台,但事实上这是错误的,因为NDK平台不是后向兼容(兼容过去的版本)的,而是前向兼容(兼容将来的版本)的。推荐使用app的minSdkVersion对应的编译平台。

这也意味着当你引入一个预编译好的.so文件时,你需要检查它被编译所用的平台版本。

程序运行时

​ 上面提到 .so 是运行在 Linux 环境下的,而且在 Android 里面一般由 NDK 编译,编译的时候,我们可以指明一种文件叫做 Application.mk,里面有一行指明库的链接方式

1
APP_STL := XXX

gradle中,在android.defaultConfig.ndk节点下 stl

默认是静态,STL的取值:

名称 说明 功能
libstdc++(system) 默认最小系统 C++ 运行时库。 默认的值,最危险方式,直接和手机系统版本挂钩,采用手机最小版本的.so库链接 不适用
gabi++_static GAbi++ 运行时(静态) C++ 异常和 RTTI
gabi++_shared GAbi++ 运行时(共享) C++ 异常和 RTTI
stlport_static STLport 运行时(静态) C++ 异常和 RTTI;标准库
stlport_shared STLport 运行时(共享) C++ 异常和 RTTI;标准库
gnustl_static GNU STL(静态) C++ 异常和 RTTI;标准库
gnustl_shared GNU STL(共享) C++ 异常和 RTTI;标准库
c++_static LLVM libc++ 运行时(静态) C++ 异常和 RTTI;标准库
c++_shared LLVM libc++ 运行时(共享) C++ 异常和 RTTI;标准库

​ 如果不特别定义的话,“system”运行时库是默认的值。除此之外,凡是后面带“_static”的,表示其是一个静态链接的运行时库(运行时库的代码包含在编译后的程序中);而凡是后面带“_shared”的,表示其是一个动态链接的运行时库(运行时库在程序运行时被动态加载进来)。如果去除动态或静态链接的因素,则除了默认的“system”运行时库之外,还有所谓的“gabi++”运行时库、“stlport”运行时库和“gunstl”运行时库。如果想支持C++异常的话,必须要使用gunstl运行时库。

主要是两种链接,静态链接,动态链接:

  • 动态链接,

    指在生成可执行文件时不将所有程序用到的函数链接到一个文件,因为有许多函数在操作系统带的dll文件中,当程序运行时直接从操作系统中找。

  • 静态链接

    把所有用到的函数全部链接到 .so 文件中。

​ 重点来了,上面说到了,静态链接是会把所需要的都搞到exe中,其实不然,这个说法是早期的了,对于现在的 Android 发展来说,为了使程序方便扩展,具备通用性,已经采用插件形式来链接动态库,编译时的静态和动态链接仅仅是程度问题。插件加载形式有:   

  • dlopen
  • dlsym
  • dlclose

dlopen打开指定的系统中(手机中)动态库。并使用 dlsym 获取符号地址,也就是说,如果,在此时的手机中如果找不到,那么就会出问题,一般和 API 有关系。

解决方案

主要有两种:

  1. 委曲求全,指标不治本,把你的 APK target API 先降低到 23以下,若不行再把 编译时 API 降低到 23 以下,还出问题就继续降低,这意味着,你很多 Android Sdk 的新控件用不了;
  2. 在 Application.mk 中修改 APP_STL,重新编译 .so ,如果,我说如果你没有源码,那么悲剧了,要么等他们解决,要么采用第一种,建议尝试,APP_STL := gnustl_shared。

这种方式,对于所需要的外部动态链接函数、符号,在 NDK 13b 中都会独立生成一份,全部引用就解决此类问题,例如

1
2
3
4
5
6
7
8
private void load() {
try {
System.loadLibrary("gnustl_shared");
System.loadLibrary("speex");
} catch (Throwable e) {
Log.d("zzzzz","加载语音库异常 :"+e.toString());
}
}
  • android

扫一扫,分享到微信

微信分享二维码
Butterknife的实现探究
Android 共享元素动画的简单实现
© 2017 Bleoo
Hexo Theme Yilia by Litten