【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

相关文章

linux系统中java的cacerts的优先级详解

《linux系统中java的cacerts的优先级详解》文章讲解了Java信任库(cacerts)的优先级与管理方式,指出JDK自带的cacerts默认优先级更高,系统级cacerts需手动同步或显式... 目录Java 默认使用哪个?如何检查当前使用的信任库?简要了解Java的信任库总结了解 Java 信

利用Python操作Word文档页码的实际应用

《利用Python操作Word文档页码的实际应用》在撰写长篇文档时,经常需要将文档分成多个节,每个节都需要单独的页码,下面:本文主要介绍利用Python操作Word文档页码的相关资料,文中通过代码... 目录需求:文档详情:要求:该程序的功能是:总结需求:一次性处理24个文档的页码。文档详情:1、每个

MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决

《MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决》MyBatis默认开启一级缓存,同一事务中循环调用查询方法时会重复使用缓存数据,导致获取的序列主键值均为1,... 目录问题原因解决办法如果是存储过程总结问题myBATis有如下代码获取序列作为主键IdMappe

Java中的分布式系统开发基于 Zookeeper 与 Dubbo 的应用案例解析

《Java中的分布式系统开发基于Zookeeper与Dubbo的应用案例解析》本文将通过实际案例,带你走进基于Zookeeper与Dubbo的分布式系统开发,本文通过实例代码给大家介绍的非常详... 目录Java 中的分布式系统开发基于 Zookeeper 与 Dubbo 的应用案例一、分布式系统中的挑战二

Java 缓存框架 Caffeine 应用场景解析

《Java缓存框架Caffeine应用场景解析》文章介绍Caffeine作为高性能Java本地缓存框架,基于W-TinyLFU算法,支持异步加载、灵活过期策略、内存安全机制及统计监控,重点解析其... 目录一、Caffeine 简介1. 框架概述1.1 Caffeine的核心优势二、Caffeine 基础2

使用Node.js和PostgreSQL构建数据库应用

《使用Node.js和PostgreSQL构建数据库应用》PostgreSQL是一个功能强大的开源关系型数据库,而Node.js是构建高效网络应用的理想平台,结合这两个技术,我们可以创建出色的数据驱动... 目录初始化项目与安装依赖建立数据库连接执行CRUD操作查询数据插入数据更新数据删除数据完整示例与最佳

Oracle数据库在windows系统上重启步骤

《Oracle数据库在windows系统上重启步骤》有时候在服务中重启了oracle之后,数据库并不能正常访问,下面:本文主要介绍Oracle数据库在windows系统上重启的相关资料,文中通过代... oracle数据库在Windows上重启的方法我这里是使用oracle自带的sqlplus工具实现的方

使用Go调用第三方API的方法详解

《使用Go调用第三方API的方法详解》在现代应用开发中,调用第三方API是非常常见的场景,比如获取天气预报、翻译文本、发送短信等,Go作为一门高效并发的编程语言,拥有强大的标准库和丰富的第三方库,可以... 目录引言一、准备工作二、案例1:调用天气查询 API1. 注册并获取 API Key2. 代码实现3

Android实现图片浏览功能的示例详解(附带源码)

《Android实现图片浏览功能的示例详解(附带源码)》在许多应用中,都需要展示图片并支持用户进行浏览,本文主要为大家介绍了如何通过Android实现图片浏览功能,感兴趣的小伙伴可以跟随小编一起学习一... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

在Android中使用WebView在线查看PDF文件的方法示例

《在Android中使用WebView在线查看PDF文件的方法示例》在Android应用开发中,有时我们需要在客户端展示PDF文件,以便用户可以阅读或交互,:本文主要介绍在Android中使用We... 目录简介:1. WebView组件介绍2. 在androidManifest.XML中添加Interne