【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

相关文章

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

Springboot项目启动失败提示找不到dao类的解决

《Springboot项目启动失败提示找不到dao类的解决》SpringBoot启动失败,因ProductServiceImpl未正确注入ProductDao,原因:Dao未注册为Bean,解决:在启... 目录错误描述原因解决方法总结***************************APPLICA编

Python标准库之数据压缩和存档的应用详解

《Python标准库之数据压缩和存档的应用详解》在数据处理与存储领域,压缩和存档是提升效率的关键技术,Python标准库提供了一套完整的工具链,下面小编就来和大家简单介绍一下吧... 目录一、核心模块架构与设计哲学二、关键模块深度解析1.tarfile:专业级归档工具2.zipfile:跨平台归档首选3.

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

Java Stream流之GroupBy的用法及应用场景

《JavaStream流之GroupBy的用法及应用场景》本教程将详细介绍如何在Java中使用Stream流的groupby方法,包括基本用法和一些常见的实际应用场景,感兴趣的朋友一起看看吧... 目录Java Stream流之GroupBy的用法1. 前言2. 基础概念什么是 GroupBy?Stream

python中列表应用和扩展性实用详解

《python中列表应用和扩展性实用详解》文章介绍了Python列表的核心特性:有序数据集合,用[]定义,元素类型可不同,支持迭代、循环、切片,可执行增删改查、排序、推导式及嵌套操作,是常用的数据处理... 目录1、列表定义2、格式3、列表是可迭代对象4、列表的常见操作总结1、列表定义是处理一组有序项目的

C++11范围for初始化列表auto decltype详解

《C++11范围for初始化列表autodecltype详解》C++11引入auto类型推导、decltype类型推断、统一列表初始化、范围for循环及智能指针,提升代码简洁性、类型安全与资源管理效... 目录C++11新特性1. 自动类型推导auto1.1 基本语法2. decltype3. 列表初始化3

C#中的Converter的具体应用

《C#中的Converter的具体应用》C#中的Converter提供了一种灵活的类型转换机制,本文详细介绍了Converter的基本概念、使用场景,具有一定的参考价值,感兴趣的可以了解一下... 目录Converter的基本概念1. Converter委托2. 使用场景布尔型转换示例示例1:简单的字符串到