Android添加自定义公共so库
Android对应用应用的系统库限制越来越严格,上层应用包括(apk、jar包)不能直接引用系统的一些so库了。如果需要使用,只能使用,系统申明的公共库。如果使用非系统申明的公共库,apk运行后调用该so库时,app会直接挂掉。
1、错误信息
具体报错形式如下:
01-01 02:17:24.222 7475 7475 E linker : library "/system/lib/libhalloworld.so" ("/system/lib/libhalloworld.so") needed or dlopened by "/system/lib/libnativeloader.so" is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="", default_library_paths="/system/fake-libs:/data/app/com.example.halloworld-1/base.apk!/lib/", permitted_paths="/data:/mnt/expand:/data/data/com.example.halloworld"]01-01 02:17:24.223 7475 7475 E System : java.lang.UnsatisfiedLinkError: dlopen failed: library "/system/lib/libhalloworld.so" needed or dlopened by "/system/lib/libnativeloader.so" is not accessible for the namespace "classloader-namespace"
这个报错的意思是说,apk通过dlopen打开一个native库时,Nativeloader打不开libhalloworld.so。并且提示了apk能够访问的具体路径:
- ld_library_paths="",
- default_library_paths="/system/fake-libs64:/data/app/com.example.haha-1/base.apk!/lib/",
- permitted_paths="/data:/mnt/expand:/data/data/com.example.haha
上面这些路径是apk可以访问的路径,除之之外的其它路径是不能访问的,由于我们要访问的so库位于/system/lib,因此apk不能访问。
2、升级后apk奔溃问题
存在出厂预制的apk能正常使用,但是升级之后运行奔溃的问题。先来看看源码的处理:
final boolean isBundledApp = mApplicationInfo.isSystemApp() //如果是系统apk && !mApplicationInfo.isUpdatedSystemApp(); //apk没有升级过String libraryPermittedPath = mDataDir;if (isBundledApp) { //permitted_paths就增加system/lib// This is necessary to grant bundled apps access to// libraries located in subdirectories of /system/liblibraryPermittedPath += File.pathSeparator +System.getProperty("java.library.path");}
因此,如果是系统apk并且没有升级过,so库的搜索路径就会增加一个system/lib。而apk升级过之后,就不满足条件了。
3、解决方法
3.1、符合google要求的改法
具体介绍和处理查看Google官方的说明(https://source.android.google.cn/devices/tech/config/namespaces_libraries#adding-additional-native-libraries)添加其他原生库。
除了标准的公共原生库之外,芯片供应商(从 Android 7.0 开始)和设备制造商(从 Android 9 开始)还可以选择提供可供应用访问的其他原生库,方法是将它们放在相应的库文件夹中,并在 .txt 文件中明确列出它们。
库文件夹是:
/vendor/lib
(对于芯片供应商的 32 位库)和/vendor/lib64
(对于芯片供应商的 64 位库)/system/lib
(对于设备制造商的 32 位库)和/system/lib64
(对于设备制造商的 64 位库)
.txt 文件是:
/vendor/etc/public.libraries.txt
(对于芯片供应商的库)/system/etc/public.libraries-COMPANYNAME.txt
(对于设备制造商的库),其中COMPANYNAME
指的是制造商的名称(例如awesome.company
)。COMPANYNAME
应符合[A-Za-z0-9_.-]+
格式(字母数字字符、_、.(点)和 -)。如果某些库来自外部解决方案提供商,则可以在设备中包含多个此类 .txt 文件。
必须将 system
分区内由设备制造商公开提供的原生库命名为 lib*COMPANYNAME.so
,例如 libFoo.awesome.company.so
。换句话说,没有公司名称后缀的 libFoo.so
不得公开。库文件名中的 COMPANYNAME
必须与列出库名称的 txt 文件名称中的 COMPANYNAME
匹配。
作为 AOSP 一部分的原生库不得公开(默认情况下公开的标准公共原生库除外)。只有芯片供应商或设备制造商添加的其他库可供应用访问。
从 Android 8.0 开始,供应商的公共库需要遵循以下额外限制,并且需要进行相应的设置:
-
供应商的原生库必须添加适当的标签,这样才能供应用访问。如有任何应用(包括第三方应用)要求访问原生库,则该库必须在供应商专用的file_contexts文件中标记为 same_process_hal_file,具体如下所示:
/vendor/lib(64)?/libnative.so u:object_r:same_process_hal_file:s0
其中,libnative.so为原生库的名称。
-
该库不得依赖(无论是直接依赖,还是通过其依赖关系间接依赖)VNDK-SP 库和 LLNDK 库之外的任何系统库。您可以在以下位置找到 VNDK-SP 库和 LLNDK 库的列表:
development/vndk/tools/definition/tool/datasets/eligible-list-
。-release.csv
对于system so上面的内容主要有三点:
1、需要添加的so库文件名格式要求:如 libhalloworld.google.so
2、public.libraries.txt文件命名:public.libraries-google.txt
3、public.libraries-google.txt文件内容必须是libhalloworld.google.so,且放在/system/etc下
以上三点有一点不对应,apk同样会奔溃。
3.2 偷懒改法
谷歌原生提供的公共库是放在/system/etc/public.libraries.txt里面,引用这里的库不会存在上述的问题。但是引用这个文件可能存在cts测试的问题(暂时没有验证过),因此我们可以在在device或者vendor目录下,在编译时覆盖掉该文件,或者直接修改该文件来达到添加库的目的。参考修改可以看看(https://android-review.googlesource.com/#/c/209029/)
4、相关源码解读
public.libraries.txt相关的处理逻辑如下,在LibraryNamespaces类的Initialize()会读取这个文件,将so库设置为公共so库。
//system/core/libnativeloader/native_loader.cppstatic constexpr const char* kPublicNativeLibrariesSystemConfigPathFromRoot = "/etc/public.libraries.txt";static constexpr const char* kPublicNativeLibrariesVendorConfig = "/vendor/etc/public.libraries.txt"; void Initialize() { ... std::vector<std::string> sonames; ReadConfig(public_native_libraries_system_config, &sonames, &error_msg), ReadConfig(kPublicNativeLibrariesVendorConfig, &sonames); public_libraries_ = base::Join(sonames, ':'); ... }
该方法的调用流程,首先在创建一个虚拟机的时候,初始化NativeLoader
//art/runtime/java_vm_ext.ccextern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { ................ // Initialize native loader. This step makes sure we have // everything set up before we start using JNI. android::InitializeNativeLoader(); ...}
然后进入native_loader,进行初始化
//system/core/libnativeloader/native_loader.cppstatic LibraryNamespaces* g_namespaces = new LibraryNamespaces;void InitializeNativeLoader() { g_namespaces->Initialize();}
初始化是调用LibraryNamespaces类的Initialize完成公共so库的赋值.
void Initialize() { .................. std::vector<std::string> sonames; ReadConfig(public_native_libraries_system_config, &sonames, &error_msg), ReadConfig(kPublicNativeLibrariesVendorConfig, &sonames); public_libraries_ = base::Join(sonames, ':'); ............. }
5、总结
1、Android N 之后,Google对调用公共系统库的限制越来越严格,只有public.libraries.txt相关的文件申明后的so才能使用。
2、如果声明的public.libraries.txt和so库在vendor目录下,那么system下的apk和jar是不能使用的,同样会造成apk奔溃。
3、使用以上的解决方法,必须严格按照规定来使用,有对不上的地方apk就会奔溃。
6、参考文献
库的命名空间: https://source.android.google.cn/devices/tech/config/namespaces_libraries#adding-additional-native-libraries
链接器命名空间: https://source.android.google.cn/devices/architecture/vndk/linker-namespace
VNDK 构建系统支持: https://source.android.google.cn/devices/architecture/vndk/build-system?hl=zh-cn
Platform APIs: https://android.googlesource.com/platform/ndk/+/master/docs/PlatformApis.md
Namespace based Dynamic Linking:https://jackwish.net/2017/namespace-based-dynamic-linking.html
Framework基础:Android N 公共so库怎么定义:https://www.jianshu.com/p/4be3d1dafbec
更多相关文章
- android之ContentProvider详解
- Android文件操作总结
- android webview 访问https页面 SslError 处理
- android 获取文件大小
- Android(安卓)瘦身之道 ---- so文件
- AndroidManifest.xml文件讲解
- Android之更换皮肤
- 手动从Eclipse移植Android项目到Android(安卓)Studio记录
- Android(安卓)二:root 手机的原理