android N : UnsatisfiedLinkError 只能访问设置为公用库的so库

在android N上使用 .so作为apk的第三方库的时候,会发生java.lang.UnsatisfiedLinkError:

09-27 12:17:01.280 D/ListenSoundModel( 3635): Load libxxxjni 
09-27 12:17:01.292 D/AndroidRuntime( 3635): Shutting down VM 
——— beginning of crash 
09-27 12:17:01.293 E/AndroidRuntime( 3635): FATAL EXCEPTION: main 
09-27 12:17:01.293 E/AndroidRuntime( 3635): Process: com.qualcomm.xxx, PID: 3635 
09-27 12:17:01.293 E/AndroidRuntime( 3635): java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/xxxApp.apk”],nativeLibraryDirectories=[/data/app-lib/xxxApp, /system/lib64, /vendor/lib64]]] couldn’t find “libxxxjni.so” 
09-27 12:17:01.293 E/AndroidRuntime( 3635): at java.lang.Runtime.loadLibrary0(Runtime.java:972) 
09-27 12:17:01.293 E/AndroidRuntime( 3635): at java.lang.System.loadLibrary(System.java:1530) 
… 
09-27 12:17:01.293 E/AndroidRuntime( 3635): at android.os.Handler.dispatchMessage(Handler.java:102) 
09-27 12:17:01.293 E/AndroidRuntime( 3635): at android.os.Looper.loop(Looper.java:154)

这在google的变更说明有 介绍https://developer.android.com/preview/behavior-changes.html#ndk

具体是什么原因呢,怎么解决这种问题呢,google给出了一个官方的解决办法(如上链接),这里也给出另外一种方法。

首先,主要原因是google在N上对.so库的加载进行了限制,限制了so库指从部分指定的路径进行加载,不在这个路径的so提示 
java.lang.UnsatisfiedLinkError: dlopen failed: library “xxx.so” not found 或 
java.lang.UnsatisfiedLinkError: dlopen failed: library “/vendor/lib64/xxx.so” needed or dlopened by “/system/lib64/libnativeloader.so” is not accessible for the namespace “classloader-namespace” 或 其他异常错误提示。

N上对so库加载的搜索路径方式为ld_library_path, runtime path, permit path,不在这个搜索路径下则加载失败。

从代码层面看,主要是类加载器ClassLoader的相关处理, 
code1: (loadedApk.java getClassLoader()) check sdk version 
// DO NOT SHIP: this is a workaround for apps loading native libraries 
// provided by 3rd party apps using absolute path instead of corresponding 
// classloader; see http://b/26954419 for example. 
if (mApplicationInfo.targetSdkVersion <= 23) { 
libraryPermittedPath += File.pathSeparator + “/data/app”; 
}

Code2: (loadedApk.java getClassLoader()) N add a new PermittedPath 
String libraryPermittedPath = mDataDir;

Code3: (native_loader.cpp) use the new namespace rule with search path: ld_library_path, runtime path, permit path.

在明白原因之后, 
解决办法则是将自己的so加入到允许路径的白名单里面,具体操作为,如果不改代码实现,则导出设备的/vendor/etc/public.libraries.txt 或/etc/public.libraries.txt文件,将so名字添加进去,在push到设备,重启即可。

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Android N 版本有个新feature,就是普通应用不能直接引用系统的一些so库了,只能直接引用public.libraries.txt文件中过滤的so库。这个网址有介绍怎么处理。
source.android.com/devices/tec…
具体情况是这样的:我有一个系统权限的apk,这个apk会编译出一些so库放在system/lib目录下面,刚刷机这个apk是可以引用到这些so库的,但我调试的时候直接install这个apk,运行的时候居然直接挂了!!我也是醉了。没想到这个新feature居然影响到我的系统权限的apk。真蛋疼,我可不想每次push这个apk然后重启才生效,一般我都是直接install的,但现在好日子貌似到头了。

一.看看报错信息罗

具体报错的信息如下:

01-01 02:17:24.222  7475  7475 E linker  : library "/system/lib64/libhaha_utils.so" ("/system/lib64/libhaha_utils.so") needed or dl
opened by "/system/lib64/libnativeloader.so" is not accessible for the namespace: [name="classloader-namespace", ld_library_paths
="", default_library_paths="/system/fake-libs64:/data/app/com.example.haha-1/base.apk!/lib/arm64-v8a", permitted_paths="/dat
a:/mnt/expand:/data/data/com.example.haha"]
01-01 02:17:24.223  7475  7475 E System  : java.lang.UnsatisfiedLinkError: dlopen failed: library "/system/lib64/libhaha_utils.so"
 needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace "classloader-namespace"

大概的意思就是应用nativeloader打不开libhaha_utils.so这个so库了,就崩溃了!!好残忍。libhaha_utils.so这个库是用我用Android.mk编译后放在system/lib64下面的。但现在打不开了。
为啥呢?
因为/system/lib64/不在APK查找so库的合法路径啊,合法路径有啥呢?
上面log就有说明啦。下面三个路径都没有找到libhaha_utils.so库,所以就挂了。
ld_library_paths="",
default_library_paths="/system/fake-libs64:/data/app/com.example.haha-1/base.apk!/lib/arm64-v8a", permitted_paths="/data:/mnt/expand:/data/data/com.example.haha

二.那为啥刚刷机时APK可以用,Install这个apk后就不能用了呢?

这个apk可是系统权限的哟,就是apk的清单AndroidManifest中有下面一句
android:sharedUserId="android.uid.system"

正常来说,这种高端apk的permitted_paths是包含system/lib64的,从源码可以知道
/frameworks/basecore/java/android/app/LoadedApk.java

 1 //如果是系统apk并且没有升级过
 2         final boolean isBundledApp = mApplicationInfo.isSystemApp()
 3                 && !mApplicationInfo.isUpdatedSystemApp();
 4
 5         String libraryPermittedPath = mDataDir;
 6         if (isBundledApp) {   //permitted_paths就增加system/lib64
 7             // This is necessary to grant bundled apps access to
 8             // libraries located in subdirectories of /system/lib
 9             libraryPermittedPath += File.pathSeparator +
10                                     System.getProperty("java.library.path");
11         }

看上面的注释就知道啦,如果是系统apk并且没有升级过的话,so库的搜索路径就会增加一个system/lib64。我去,google搞啥呢,为什么还要限定不能升级。
因为install -r来安装apk就相当于升级,所以刷机时apk可以用,install升级后不能用。

三.那如何解决这个鬼问题呢?

我纯粹是为了调试方便,所以参考google的链接
source.android.com/devices/tec…

google.png

应用可以调用/vendor/etc/public.libraries.txt和/system/etc/public.libraries.txt里面的所有so库,所以哥往这个文件写入libhaha_utils.so,这个库就变成共用的了,任意应用就可以找到这个so库了,终于可以欢快地使用install apk的方式调试啦!!再也不用重启了!!
下面是原生google的图

Paste_Image.png

大概有这么些个so库是共用的。
libandroid.so
libc.so
libdl.so
libEGL.so
libGLESv1_CM.so
......
可以看看原生的这笔提交修改的。
android-review.googlesource.com/#/c/209029/

四.看看源码这个public.libraries.txt文件是咋玩的

在这个源码里面用到这个txt文件
/system/core/libnativeloader/native_loader.cpp
在LibraryNamespaces类的Initialize()会读取这个文件,将so库设置为公共so库,所谓公共so库,就是这个so库谁都能用啦。

 1 static constexpr const char* kPublicNativeLibrariesSystemConfigPathFromRoot = "/etc/public.libraries.txt";
 2 static constexpr const char* kPublicNativeLibrariesVendorConfig = "/vendor/etc/public.libraries.txt";
 3   void Initialize() {
 4     ..................
 5     std::vector<std::string> sonames;
 6     ReadConfig(public_native_libraries_system_config, &sonames, &error_msg),
 7     ReadConfig(kPublicNativeLibrariesVendorConfig, &sonames);
 8     public_libraries_ = base::Join(sonames, ‘:‘);
 9     .............
10   }

这个方法时什么时候调用的呢?大概过下流程罗。
首先在创建一个虚拟机的时候,初始化NativeLoader,这个NativeLoader,顾名思义,就是用来装载so库的。
/art/runtimejava_vm_ext.cc

1 extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
2   ................
3   // Initialize native loader. This step makes sure we have
4   // everything set up before we start using JNI.
5   android::InitializeNativeLoader();
6   ..............
7 }

然后进入native_loader,进行初始化
/system/core/libnativeloader/native_loader.cpp

1 static LibraryNamespaces* g_namespaces = new LibraryNamespaces;
2
3 void InitializeNativeLoader() {
4   g_namespaces->Initialize();
5 }

初始化是调用LibraryNamespaces类的Initialize完成公共so库的赋值,哈哈哈,搞定!!

1 void Initialize() {
2     ..................
3     std::vector<std::string> sonames;
4     ReadConfig(public_native_libraries_system_config, &sonames, &error_msg),
5     ReadConfig(kPublicNativeLibrariesVendorConfig, &sonames);
6     public_libraries_ = base::Join(sonames, ‘:‘);
7     .............
8   }

总结:
1.Android N 不能直接调用系统的一些私有库了,公用的库都定义在public.libraries.txt里面。
2.系统应用刚刷机是能够调用system/lib64下的库,但通过install升级该应用时,应用打开会挂。因为升级后permitted_paths就不再包含system/lib64了。所以我们可以将apk要用到的库名称写到public.libraries.txt中去解决快速调试问题。

原文地址:https://www.cnblogs.com/zl1991/p/9409462.html

时间: 2024-09-18 18:48:38

android N : UnsatisfiedLinkError 只能访问设置为公用库的so库的相关文章

Android(java)学习笔记150:为什么局部内部类只能访问外部类中的 final型的常量

为什么匿名内部类参数必须为final类型: 1)  从程序设计语言的理论上:局部内部类(即:定义在方法中的内部类),由于本身就是在方法内部(可出现在形式参数定义处或者方法体处),因而访问方法中的局部变量(形式参数或局部变量)是天经地义的,是很自然的. 2) 为什么JAVA中要加上一条限制:只能访问final型的局部变量? JAVA语言的编译程序的设计者当然全实现:局部内部类能访问方法中的所有的局部变量(因为:从理论上这是很自然的要求),但是:编译技术是无法实现的或代价极高. 至于为什么只能是fi

Android中通过反射来设置显示时间

这个Toast的显示在Android中的用途还是很大的,同时我们也知道toast显示的时间是不可控的,我们只能修改他的显示样式和显示的位置,虽然他提供了一个显示时间的设置方法,但是那是没有效果的(后面会说到),他有两个静态的常量Toast.SHORT和Toast.LONG,这个在后面我会在源码中看到这个两个时间其实是2.5s和3s.那么我们如果真想控制toast的显示时间该怎么办呢?真的是无计可施了吗?天无绝人之路,而且Linux之父曾经说过:遇到问题就去看那个操蛋的源代码吧!!下面就从源代码开

MSSQL只能访问特定的数据库

让用户只能访问特定的数据库(MSSQL) 背景 客户的SQL Server实例上有多个厂商的数据库,每个数据库由各自的进行厂进行商维护, 为了限定不同厂商的维护人员只能访问自己的数据库,现需要给各个厂商限定权限,让他们登录SQL Server只能看到授权的数据库而无法看到其他数据库. 解决方案 1.先给不同的厂商创建不同的登录名(如下以一个厂商为例) 2.将登录名加入到public服务器角色中,然后点击确定 3.将待授权的数据库的dbowner指派给该用户 Use [xjcs] go EXEC

Android源码AOSP之设置Settings阅读记录

Android 4.4 系统的设置源码阅读记录 ----------2014-7-3------------------ AndroidManifest.xml launch的activity是 Settings,另外有40多个activity继承于它,比如设置的一级菜单: wifi,蓝牙,声音,显示,安全,应用程序,语言和时间,关于设备等等.实际上都是这一个acitivy. 这里从安全设置看起,SecuritySettings.java 以资源文件R.xml.security_settings

Android主线程不能访问网络异常解决办法

从两个方面说下这个问题: 1. 不让访问网络的原因 2. 解决该问题的办法 不让访问网络的原因: 由于对于网络状况的不可预见性,很有可能在网络访问的时候造成阻塞,那么这样一来我们的主线程UI线程 就会出现假死的现象,产生很不好的用户体验.所以,默认的情况下如果直接在主线程中访问就报出了这个异常,名字是NetworkOnMainThreadException 解决该问题的办法 1. 独立线程 2. 异步线程AsyncTask 3. StrictMode修改默认的策略 1) 独立线程的办法 启动一个

如何给VSFTP增加用户,只能访问指定目录

1 增加组 groupadd ftpgroup 2 修改/etc/vsftpd/vsftpd.conf 将底下三行 #chroot_list_enable=YES # (default follows) #chroot_list_file=/etc/vsftpd.chroot_list 改为 chroot_list_enable=YES # (default follows) chroot_list_file=/etc/vsftpd/chroot_list 3 增加用户 useradd -g f

如何让用户只能访问特定的数据库(MSSQL)

背景 客户的SQL Server实例上有多个厂商的数据库,每个数据库由各自的进行厂进行商维护, 为了限定不同厂商的维护人员只能访问自己的数据库,现需要给各个厂商限定权限,让他们登录SQL Server只能看到授权的数据库而无法看到其他数据库. 解决方案 1.先给不同的厂商创建不同的登录名(如下以一个厂商为例) 2.将登录名加入到public服务器角色中,然后点击确定 3.将待授权的数据库的dbowner指派给该用户 Use [xjcs] go EXEC dbo.sp_changedbowner

Android之XML资源访问

颜色资源 <span style="font-size:18px;"><resources> <color name="red">#f00</color> <color name="green">#0f0</color> <color name="blue">#00f</color> </resources></s

centos 限制只能访问某个目录的php文件

vi /etc/php.ini #编辑 open_basedir = .:/tmp/ #在380行 设置表示允许访问当前目录(即PHP脚本文件所在之目录)和/tmp/目录,可以防止php木马跨站,如果改了之后安装程序有问题(例如:织梦内容管理系统),可以注销此行, 或者直接写上程序的目录/data/www.osyunwei.com/:/tmp/ open_basedir =/home/upload/:/tmp         限制只能访问/home/upload目录下的php文件.