【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

相关文章

PHP应用中处理限流和API节流的最佳实践

《PHP应用中处理限流和API节流的最佳实践》限流和API节流对于确保Web应用程序的可靠性、安全性和可扩展性至关重要,本文将详细介绍PHP应用中处理限流和API节流的最佳实践,下面就来和小编一起学习... 目录限流的重要性在 php 中实施限流的最佳实践使用集中式存储进行状态管理(如 Redis)采用滑动

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

JWT + 拦截器实现无状态登录系统

《JWT+拦截器实现无状态登录系统》JWT(JSONWebToken)提供了一种无状态的解决方案:用户登录后,服务器返回一个Token,后续请求携带该Token即可完成身份验证,无需服务器存储会话... 目录✅ 引言 一、JWT 是什么? 二、技术选型 三、项目结构 四、核心代码实现4.1 添加依赖(pom

基于Python实现自动化邮件发送系统的完整指南

《基于Python实现自动化邮件发送系统的完整指南》在现代软件开发和自动化流程中,邮件通知是一个常见且实用的功能,无论是用于发送报告、告警信息还是用户提醒,通过Python实现自动化的邮件发送功能都能... 目录一、前言:二、项目概述三、配置文件 `.env` 解析四、代码结构解析1. 导入模块2. 加载环

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

PostgreSQL简介及实战应用

《PostgreSQL简介及实战应用》PostgreSQL是一种功能强大的开源关系型数据库管理系统,以其稳定性、高性能、扩展性和复杂查询能力在众多项目中得到广泛应用,本文将从基础概念讲起,逐步深入到高... 目录前言1. PostgreSQL基础1.1 PostgreSQL简介1.2 基础语法1.3 数据库

Java调用Python脚本实现HelloWorld的示例详解

《Java调用Python脚本实现HelloWorld的示例详解》作为程序员,我们经常会遇到需要在Java项目中调用Python脚本的场景,下面我们来看看如何从基础到进阶,一步步实现Java与Pyth... 目录一、环境准备二、基础调用:使用 Runtime.exec()2.1 实现步骤2.2 代码解析三、

Python中的filter() 函数的工作原理及应用技巧

《Python中的filter()函数的工作原理及应用技巧》Python的filter()函数用于筛选序列元素,返回迭代器,适合函数式编程,相比列表推导式,内存更优,尤其适用于大数据集,结合lamb... 目录前言一、基本概念基本语法二、使用方式1. 使用 lambda 函数2. 使用普通函数3. 使用 N

Python中yield的用法和实际应用示例

《Python中yield的用法和实际应用示例》在Python中,yield关键字主要用于生成器函数(generatorfunctions)中,其目的是使函数能够像迭代器一样工作,即可以被遍历,但不会... 目录python中yield的用法详解一、引言二、yield的基本用法1、yield与生成器2、yi