【Android N兼容问题】Android N上系统预置应用调用第三方库初始化失败

本文主要是介绍【Android N兼容问题】Android N上系统预置应用调用第三方库初始化失败,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【Android N兼容问题】Android N上系统预置应用调用第三方库初始化失败

一、问题描述

  开发系统ROM需要预置一款内部应用,预置位置在”system/priv-app下”,该应用使用到了百度地图的so库; 在调用到该so库的地方,报初始化失败,查看log发现以下出错log:

05-22 10:02:17.963 11659 11659 E linker  : library "/data/user/0/com.xxx.xxx.voc/lib/libgnustl_shared.so" ("/system/priv-app/xxx/lib/arm/libgnustl_shared.so") needed or dlopened by "/system/lib/libnativeloader.so" 
is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="", default_library_paths="", permitted_paths="/data:/mnt/expand"]
...
05-22 10:02:17.965 11659 11659 W System.err: Caused by: java.lang.UnsatisfiedLinkError: dlopen failed: library "/data/user/0/com.xxx.xxx.voc/lib/libgnustl_shared.so" 
needed or dlopened by "/system/lib/libnativeloader.so" is not accessible for the namespace "classloader-namespace"
05-22 10:02:17.965 11659 11659 W System.err:    at java.lang.Runtime.load0(Runtime.java:908)
05-22 10:02:17.965 11659 11659 W System.err:    at java.lang.System.load(System.java:1505)
05-22 10:02:17.965 11659 11659 W System.err:    at com.baidu.navisdk.adapter.impl.BaiduNaviManager.loadNativeLibrary(BaiduNaviManager.java:311)
05-22 10:02:17.965 11659 11659 W System.err:    at com.baidu.navisdk.adapter.impl.BaiduNaviManager.loadBaiduNaviNativeLibrary(BaiduNaviManager.java:294)
05-22 10:02:17.965 11659 11659 W System.err:    at com.baidu.navisdk.adapter.impl.BaiduNaviManager.init(BaiduNaviManager.java:693)
05-22 10:02:17.965 11659 11659 W System.err:    at com.baidu.navisdk.adapter.impl.BaiduNaviSDKStub.init(BaiduNaviSDKStub.java:399)
05-22 10:02:17.965 11659 11659 W System.err:    ... 17 more

  log显示应该是百度sdk的so库is not accessible for the namespace,因此load so库时失败,导致最终百度地图sdk初始化失败;
  该应用开发人员告知使用adb install -r 方式可以成功初始化,但是push的方式无法成功初始化,同样编到版本里的该应用同样无法初始化成功。

二、问题分析:

1.找到打印出错的代码位置

  打印这句异常log的地方,是在 /bionic/linker/linker.cpp中的static bool load_library()函数中:

/bionic/linker/linker.cpp
static bool load_library(android_namespace_t* ns,LoadTask* task,LoadTaskList* load_tasks,int rtld_flags,const std::string& realpath) { 
...if (!ns->is_accessible(realpath)) {// TODO(dimitry): workaround for http://b/26394120 - the grey-listconst soinfo* needed_by = task->is_dt_needed() ? task->get_needed_by() : nullptr;if (is_greylisted(name, needed_by)) {// print warning only if needed by non-system libraryif (needed_by == nullptr || !is_system_library(needed_by->get_realpath())) {const soinfo* needed_or_dlopened_by = task->get_needed_by();const char* sopath = needed_or_dlopened_by == nullptr ? "(unknown)" :needed_or_dlopened_by->get_realpath();DL_WARN("library \"%s\" (\"%s\") needed or dlopened by \"%s\" is not accessible for the namespace \"%s\""" - the access is temporarily granted as a workaround for http://b/26394120, note that the access"" will be removed in future releases of Android.",name, realpath.c_str(), sopath, ns->get_name());add_dlwarning(sopath, "unauthorized access to",  name);}} else {// do not load libraries if they are not accessible for the specified namespace.const char* needed_or_dlopened_by = task->get_needed_by() == nullptr ?"(unknown)" :task->get_needed_by()->get_realpath();DL_ERR("library \"%s\" needed or dlopened by \"%s\" is not accessible for the namespace \"%s\"",name, needed_or_dlopened_by, ns->get_name());PRINT("library \"%s\" (\"%s\") needed or dlopened by \"%s\" is not accessible for the"" namespace: [name=\"%s\", ld_library_paths=\"%s\", default_library_paths=\"%s\","" permitted_paths=\"%s\"]",name, realpath.c_str(),needed_or_dlopened_by,ns->get_name(),android::base::Join(ns->get_ld_library_paths(), ':').c_str(),android::base::Join(ns->get_default_library_paths(), ':').c_str(),android::base::Join(ns->get_permitted_paths(), ':').c_str());return false;}}...return true;
}

我们注意到注释:

 // TODO(dimitry): workaround for http://b/26394120 - the grey-list// print warning only if needed by non-system library// do not load libraries if they are not accessible for the specified namespace.

这里其实是Android N的新的特性。

2. Android N 限制应用使用非公共NDK API

  从Android N开始,本地库只能引用公共的NDK API,系统将阻止应用动态连接非NDK平台的so库。Android 7.0 行为变更 中指出如果您的应用使用动态链接到私有平台 API 的第三方库,您可能也会看到上述错误 logcat 输出。
  谷歌也列了一个灰名单(grey list),这个灰名单上的so库可以被第三方apk调用。这个灰名单是Google play上Top 100的apk使用频率最高的so库。但是谷歌也声明了,这个灰名单措施是暂时性的,以后会逐步去除。

/bionic/linker/linker.cpp
// TODO(dimitry): The grey-list is a workaround for http://b/26394120 ---
// gradually remove libraries from this list until it is gone.
static bool is_greylisted(const char* name, const soinfo* needed_by) {static const char* const kLibraryGreyList[] = {"libandroid_runtime.so","libbinder.so","libcrypto.so","libcutils.so","libexpat.so","libgui.so","libmedia.so","libnativehelper.so","libskia.so","libssl.so","libstagefright.so","libsqlite.so","libui.so","libutils.so","libvorbisidec.so",nullptr};

  我们尝试将库 “/data/user/0/com.xxx.xxx.voc/lib/libgnustl_shared.so”,加到这个名单中,然后单编linker,push到system/bin下,添加可执行权限,然后发现,在load这个库时通过了,但该应用引用的其他第三方库还是报了上面log的异常,我们添加该应用所有的第三方库这里就不会报错了,但显然这不是一个好的解决方案。

3. 比较adb install -r 和adb push区别

  在linker.cpp的load_library()函数一开始的地方加上log

static bool load_library(android_namespace_t* ns,LoadTask* task,LoadTaskList* load_tasks,int rtld_flags,const std::string& realpath) {off64_t file_offset = task->get_file_offset();const char* name = task->get_name();const android_dlextinfo* extinfo = task->get_extinfo();PRINT("mydebug library \"%s\" (\"%s\") ,permitted_paths=\"%s\"]",name, realpath.c_str(),android::base::Join(ns->get_permitted_paths(), ':').c_str());...}

  a.使用adb push至system/priv-app下时这里打的log是:

06-02 16:28:19.299  8809  8809 E linker  : mydebug library "/data/user/0/com.xxx.xxx.voc/lib/libapp_BaiduVIlib.so" ("/system/priv-app/xxx/lib/arm/libapp_BaiduVIlib.so") , permitted_paths="/data:/mnt/expand"]

  b.使用adb install -r的方式发现初始化成功这里打的log是:

06-02 16:29:42.346  9585  9585 E linker  : mydebug library "/data/user/0/com.xxx.xxx.voc/lib/libapp_BaiduVIlib.so" ("/data/app/com.xxx.xxx.voc-1/lib/arm/libapp_BaiduVIlib.so") ,permitted_paths="/data:/mnt/expand"]

  可以看到adb install -r的方式是将apk安装到了data/app下,而后面的log显示permitted_paths是包含/data路径下的因此使用adb install -r的方式是不会报错的;,
  在源码里搜”/data:/mnt/expand”意外的发现:

//system/core/libnativeloader/native_loader.cpp
// (http://b/27588281) This is a workaround for apps using custom classloaders and calling
// System.load() with an absolute path which is outside of the classloader library search path.
// This list includes all directories app is allowed to access this way.
static constexpr const char* kWhitelistedDirectories = "/data:/mnt/expand";

  意思就是使用System.load() 一个在classloader的库之外的绝对路径下的库,这些路径之下是可以获取的。使用adb install 或者点击安装包安装都会安装在data/app下,而kWhitelistedDirectories 默认就有/data目录,因此正常安装的三方应用即使调用三方库也是ok的。
  我们尝试将system/priv-app加到这个permitted_paths中:

static constexpr const char* kWhitelistedDirectories = "/data:/mnt/expand:/system/priv-app";

  编system.img后验证ok:预置的apk调用百度地图sdk初始化成功。

三、总结:

  1.以上是做ROM开发时遇到的Android N上的库引用失败的案例,由于是手机ROM预置的应用,我们可以在平台的基础上修改已达到适配;
  2.但在网上搜了一番,很多出现类似的出错log是在三方应用加载系统私有库时出现:
    在AndroidN上当三方应用加载系统私有库的时候,系统会限制其加载,Android N加载系统私有库时出现is not accessible for the namespace错误和针对该错误的解决方法 这篇博客中指出,可以将需要的系统私有库pull出来,集成在apk项目中再使用System.loadLibrary()方法调用。
    当然,Android 7.0 行为变更#NDK 应用链接至平台库 这篇文章中详细介绍了三方应用调用私有库的规范方式,上面的方法及前面所提到的灰名单是Android N上的过度方式,做应用开发的话应当及时发现这个新改动并及时完善,以避免应用在新平台新机器中出现不好的用户体验。
  日后有新的使用经历再更新本篇博客。

参考博客:
Android 7.0 行为变更#NDK 应用链接至平台库:
https://developer.android.google.cn/about/versions/nougat/android-7.0-changes.html#perm
Android中adb push和adb install的使用区别:
http://www.cnblogs.com/permanent2012moira/p/4181317.html
Framework基础:AndroidN公共so库怎么定义:
http://www.qingpingshan.com/rjbc/az/247686.html
Android N加载系统私有库时出现is not accessible for the namespace错误和针对该错误的解决方法:
http://blog.csdn.net/wjskeepmaking/article/details/70153995

这篇关于【Android N兼容问题】Android N上系统预置应用调用第三方库初始化失败的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/825628

相关文章

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

Docker镜像pull失败两种解决办法小结

《Docker镜像pull失败两种解决办法小结》有时候我们在拉取Docker镜像的过程中会遇到一些问题,:本文主要介绍Docker镜像pull失败两种解决办法的相关资料,文中通过代码介绍的非常详细... 目录docker 镜像 pull 失败解决办法1DrQwWCocker 镜像 pull 失败解决方法2总

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

在C#中调用Python代码的两种实现方式

《在C#中调用Python代码的两种实现方式》:本文主要介绍在C#中调用Python代码的两种实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#调用python代码的方式1. 使用 Python.NET2. 使用外部进程调用 Python 脚本总结C#调

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用