`
iaiai
  • 浏览: 2203901 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

在Android平台上加载本地库的危险性[转]

 
阅读更多
在2012年KeepSafe的创业初期,我们试图找到一种为Android应用加密的方案,通过多次迭代与原型设计,我们最终找到了最佳方案——利用JNI(Java本地接口)。我们决定将接口写入Java加密库中,完全通过JNI来调用加密库,以实现加密与解密。我们选用了即时解决的方案,以期将对用户体验的影响减到最小,并决定在方案通过后就部署到生产应用上。我们严格测试代码,确认一切顺利,直到事情超出了控制。

遇到可怕的“UnsatisfiedLinkError”错误

在版本发布、大家焦虑地刷新错误报告之际,我们开始注意到,有一个错误反复重现。用户遇到“UnsatisfiedLinkError”错误的意思是,要么A) 我们调用的本地库不存在;要么B) 我们调用的本地方法不存在。鉴于B) 可能性在编译与基础测试时一般都能被发现,我们立即困惑于这一事实:用户并未安装我们打包在APK内的本地库。

下面有一些我们碰到过的异常样例,从标准类型的:
java.lang.UnsatisfiedLinkError: Couldn’t load stlport_shared from loader dalvik.system.PathClassLoader[dexPath=/data/app/com.kii.safe-1.apk,libraryPath=/data/app-lib/com.kii.safe-1]: findLibrary returned null   
at java.lang.Runtime.loadLibrary(Runtime.java:365)   
at java.lang.System.loadLibrary(System.java:535)   
at com.kii.safe.Native.<clinit>(Native.java:16)   
… 63 more   
Caused by: java.lang.UnsatisfiedLinkError: Library stlport_shared not found   
at java.lang.Runtime.loadLibrary(Runtime.java:461)   
at java.lang.System.loadLibrary(System.java:557)   
at com.kii.safe.Native.<clinit>(Native.java:16)   
… 5 more   
Caused by: java.lang.UnsatisfiedLinkError: Cannot load library: get_lib_extents[760]: 1305 — /mnt/asec/com.kii.safe-1/lib/libstlport_shared.so is not a valid ELF object   
at java.lang.Runtime.loadLibrary(Runtime.java:434)   
at java.lang.System.loadLibrary(System.java:554)   
at com.kii.safe.Native.<clinit>(Native.java:15)   
Caused by: java.lang.UnsatisfiedLinkError: Library cryptopp not found   
at java.lang.Runtime.loadLibrary(Runtime.java:461)   
at java.lang.System.loadLibrary(System.java:557)   
at com.kii.safe.Native.<clinit>(Native.java:17)

到更为诡异的……
Caused by: java.lang.UnsatisfiedLinkError: Cannot load library: reloc_library[1286]: 1748 cannot locate ‘쯰ҷЦf1Ϙ˗˞ք᣼0Ⱉض夘Ϛ.͏闑㥁ج뭫ර⓻в^ӎ3c`+W#Ҽ?-Bַˌ֕꼠’…   
at java.lang.Runtime.loadLibrary(Runtime.java:370)   
at java.lang.System.loadLibrary(System.java:535)   
at com.kii.safe.Native.<clinit>(Native.java:17)   
Caused by: java.lang.UnsatisfiedLinkError: Cannot load library: reloc_library[1312]: 1327 cannot locate ‘Pܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭXߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭXߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#׶ʭX Ϛߐܝ#   
at java.lang.Runtime.loadLibrary(Runtime.java:434)   
at java.lang.System.loadLibrary(System.java:554)   
at com.kii.safe.Native.<clinit>(Native.java:17)  

出问题的库没有明确的模式可循,似乎所有库都可能会抛出异常,并不拘于某个版本的Android OS,也不拘于特定型号的设备。此外在特定情况下,一些本地库加载正常,但并非所有库都能正常加载。我们在互联网上就这个问题疯狂地搜索,寻找答案或帮助,却一无所获。我们开始发布专门的修补程序,大多都是测试性修复补丁,并附有跟踪数据,以便让我们更好地了解故障出现时的具体环境,补丁中还包括一块代码,专门负责检查本地库是否安装在正确的位置上。

此外在特定情况下,一些本地库加载正常,但并非所有库都能正常加载。

结果发现,果然是本地库缺失所导致的。这些错误并不是系统类或文件系统所导致的偶然性奇特错误,而且用户的设备似乎也是正常的。

想法1—用户设备空间用尽

在猜测异常可能原因的时候,我们最开始想到的就是:也许用户设备的空间不足而导致本地库未能正确安装。在快速检测后,我们很快发现这个想法是错误的,用户设备空间足够安装我们打包的库。

想法2—本地库未包含在更新中

第二个看似合理的猜测:在Google Play提供给用户的版本中,我们的APK被破坏掉了。在阅读了类似的一些报告后,我们更加确信这一点:据说Google Play向所有受到影响的应用开发者发送了通知,让他们通知用户在升级后先不要运行应用,因为本地库安装错误。唯一的问题在于,这个报告出现在8月份,而我们遇到问题的时候已经是好几个月以后了,而且我们也从未收到Google Play为这类错误负责的通知。此外,这一点很难证实。

想法3—与真实用户直接联调

由于在手头的十几台各种类型的设备上都无法重现这一问题,我们决定找一个遇到这个问题的用户来联调。一位友好用户决定要帮助我们,他表示应用在最近一次更新前都能正常使用。而这里有一个问题,用户表示能正常运行的应用版本正是包含了我们加密代码与本地库的那个版本,这让我们更加困惑。我们决定直接提供给这名用户一个验证过的APK包,其中包含了所有的本地库。在他安装了APK包之后,再次运行应用,又遇到了同样的UnsatisfiedLinkError错误。这证明Google Play不是问题的根源,Android的PackageManager安装过程才是问题所在,在安装期间会发生某种错误,导致APK中的本地库无法被提取。

问题在于Android的PackageManager安装过程

得出解决方案

鉴于我们发现了问题是安装过程所导致的,我们决定将该部分的安装过程复制下来,并将本地库提取到应用代码中。幸运地是,通过以下代码就能很容易的获得应用的APK文件参考:
Context.getApplicationInfo().sourceDir;  

并用来将本地库提取到内部存储中。由于APK文件只不过是ZIP文件,写个ZIP提取代码只是小事一桩。我们很快实现了代码提取和打包,大幅降低了报错率。

日均UnsatisfiedLinkError错误的总量

尽管这是件好事,我们还是发现了一些用户时不时遇到异常抛出,在此我们寻求了Google的帮助。幸运地是,他们给了我们建议:用户可以通过除了Play Store以外的其他渠道来安装我们的应用,并告诉我们了一个技巧:通过
Context.getPackageManager().getInstallerPackageName(packageName);

就可以得知是哪个package安装了我们的应用。

为了减少APK大小,并确保应用尽可能在所有设备上可用,我们有支持x86、Armv7和Arm架构的应用包。每种只包含相应架构的本地库,所以很有可能某个用户安装的APK包并非是支持相应设备架构的那种。

我们开始在崩溃中记录安装包的名称,并且很快发现,问题产生的确是因为用户从各种途径安装应用,新增的UnsatisfiedLinkError错误都是由于用户手动安装应用时,选择了不适用他们设备架构的应用所致。这是最终的“问题揭秘”,我们都放下心来,这个解释简单明了。

引入ReLinker

我们决定将提取的代码打包成一个人人适用的小型库。鉴于我们所经历过的这个debug过程,不应当再有人重蹈覆辙,尤其是其中还涉及到了超出应用开发者控制能力之外的Android基础功能。

使用ReLinker十分简单,就像用
ReLinker.loadLibrary(context, “mylibrary”)

来代替标准的
System.loadLibrary(“mylibrary”);

Github库中 你能找到更多ReLinker的相关内容。

问题解决

在发布修复补丁的时候,有约10万的独立用户持续不断地遇到这种崩溃性错误。我们希望ReLinker有用,不再让大家遇到UnsatisfiedLinkerError错误。

感谢

感谢谷歌的支持团队为我们指出正确的方向,还有他们在这个问题上对我们持续不断地支持!

我们的代码主要是基于Chromium类似的权宜方案。在Chromium的源代码中有一条注释,解释了Android package manager中的具体问题:
/** 
* Try to load a native library using a workaround of 
* http://b/13216167. 
* 
* Workaround for b/13216167 was adapted from code in 
* https://googleplex-android-review.git.corp.google.com/#/c/433061 
* 
* More details about http://b/13216167: 
* PackageManager may fail to update shared library. 
* 
* Native library directory in an updated package is a symbolic link 
* to a directory in /data/app-lib/<package name>, for example: 
* /data/data/com.android.chrome/lib -> /data/app-lib/com.android.chrome[-1]. 
* When updating the application, the PackageManager create a new directory, 
* e.g., /data/app-lib/com.android.chrome-2, and remove the old symlink and 
* recreate one to the new directory. However, on some devices (e.g. Sony Xperia), 
* the symlink was updated, but fails to extract new native libraries from 
* the new apk. 
+ 
* We make the following changes to alleviate the issue: 
* 1) name the native library with apk version code, e.g., 
* libchrome.1750.136.so, 1750.136 is Chrome version number; 
* 2) first try to load the library using System.loadLibrary, 
* if that failed due to the library file was not found, 
* search the named library in a /data/data/com.android.chrome/app_lib 
* directory. Because of change 1), each version has a different native 
* library name, so avoid mistakenly using the old native library. 
* 
* If named library is not in /data/data/com.android.chrome/app_lib directory, 
* extract native libraries from apk and cache in the directory. 
* 
* This function doesn’t throw UnsatisfiedLinkError, the caller needs to 
* check the return value. 
*/
  • 大小: 11.8 KB
分享到:
评论

相关推荐

    Android javascript 调用本地Java函数

    在Android开发中,JavaScript与本地Java代码的交互是常见的需求,尤其在开发混合式应用时。这个主题,"Android javascript 调用本地Java函数",涉及到如何在Android的Web视图(WebView)中实现JavaScript与Java之间...

    Android获取手机号码归属地

    在Android平台上,获取手机号码归属地是一项常见的需求,特别是在开发涉及通信或用户验证的应用时。本文将详细讲解如何实现这一功能。 首先,我们要明白手机号码的归属地信息通常包括运营商(如中国移动、中国联通...

    Android 个人头像切换

    - 在Android中,获取本地相册的图片需要`READ_EXTERNAL_STORAGE`权限,而使用相机拍照则需要`CAMERA`权限。自Android 6.0(API级别23)起,应用在运行时需要请求这些危险权限。 2. **Intent机制**: - Android...

    Android JNI实例

    9. **跨平台兼容性**:了解不同架构(armeabi, armeabi-v7a, arm64-v8a, x86, x86_64)的差异,确保库能在各种设备上运行。 10. **库的封装与管理**:对于复杂的原生代码库,可以使用CMakeLists.txt或Android.mk...

    Android-SeekWeather一个采用Kotlin开发的天气App

    在Android 6.0(API级别23)及以上,应用需要在运行时请求某些危险权限。 7. **coil库**: Coil是一个基于Kotlin的图片加载库,它简化了从URL加载和显示图片的过程,通常比原生的 Glide 或 Picasso 更轻量级且易于...

    eclipse android4.4.2源码

    8. **安全与隐私**:加强了安全性和隐私保护,如对SSL/TLS连接的强制验证,以及对危险权限的更严格管理。 通过研究这些源码,开发者不仅可以掌握Android系统的运行机制,还能发现潜在的性能瓶颈,从而优化自己的...

    Android应用源码之Android调用JavaScript.zip

    在Android应用开发中,与JavaScript交互是一个常见的需求,特别是在构建混合式移动应用或者增强原生应用功能时。这个"Android应用源码之Android调用JavaScript.zip"文件提供了一个实例,展示了如何在Android应用中...

    android解析JS不完全的demo

    在Android平台上,与JavaScript交互是常见的需求,尤其是在开发混合应用或者需要在原生应用中使用Web内容时。这个“android解析JS不完全的demo”虽然不完整,但我们可以从中了解到一些基本的Android与JavaScript交互...

    android面试题目几套

    - **危险权限与普通权限**:理解两者的区别,掌握如何在Manifest中配置权限。 6. **网络编程** - **HTTP/HTTPS**:理解HTTP协议的工作原理,使用OkHttp、Retrofit等库进行网络请求。 - **WebSocket**:实现实时...

    android demo

    14. **图片加载库**:如Glide或Picasso,用于高效地加载和显示网络或本地图片,避免内存溢出。 15. **动画(Animations)**:Android提供了多种动画效果,包括视图动画(View Animation)和属性动画(Property Animation...

    Android开发入门60个小案例+源代码

    11. **通知与广播**:Android的通知系统允许应用在状态栏上显示消息,而BroadcastReceiver则允许应用接收系统或自定义广播事件,即使应用未运行。 12. **权限管理**:从Android 6.0(API级别23)开始,部分危险权限...

    安卓开发-Android与js交互.zip

    5. **安全性考虑**:由于JavaScript可以直接访问暴露的Java方法,因此需要确保`JavaScriptInterface`中的方法不会泄露敏感信息或执行危险操作。避免在JavaScript Interface中使用`Context`对象,防止权限滥用。 6. ...

    大厂的Android面试题.pdf

    - **动态权限适配方案**:Android 6.0之后,应用必须在运行时请求危险权限。 - **网络请求缓存处理**:通过设置缓存策略和缓存有效期,优化网络请求性能。 - **Bitmap处理大图**:利用BitmapFactory.Options配置...

    Android开发入门书籍

    - 在Eclipse中创建Android项目 - Eclipse的常用功能介绍(如代码编辑、调试工具等) - 与Android Studio的对比 **1.6 增强第一个项目(Chapter 6: Enhancing Your First Project)** - **知识点:** - 添加更...

    超强功能通讯录.zip

    9. **适配不同Android版本**:Android系统版本众多,开发者需要考虑兼容性问题,可能需要使用Support Library或AndroidX库来支持低版本系统。 10. **测试与调试**:在开发过程中,充分的测试是保证应用质量的关键。...

    安卓通讯录联系人打电话归属地相关-Android项目通讯录的开发.rar

    在Android平台上,开发一个具有联系人管理以及打电话归属地显示功能的通讯录应用是一项常见的任务。这个名为"安卓通讯录联系人打电话归属地相关-Android项目通讯录的开发.rar"的压缩包,包含了相关的源码资源,可以...

    Android应用开发揭秘-书籍所需源码

    在Android应用开发领域,深入理解和实践是至关重要的。"Android应用开发揭秘"这本书籍提供了丰富的源码实例,旨在帮助读者全面掌握Android应用的构建过程。本文将详细解析这些源码,探讨其中的关键知识点,帮助...

    AppIconDemo-master.zip

    这通常需要权限`android.permission.CHANGE_APP_ICON`,但此权限在Android 6.0(API级别23)及以上版本是危险权限,需要在运行时动态请求。 2. **资源管理**:动态图标需要有一套图标资源库,可以是本地资源或者...

    Android应用源码之高仿生日管家.zip

    在Android应用开发中,"高仿生日管家"的源码是一个很好的学习资源,它展示了如何构建一个功能完善的日程管理应用程序。这个应用的核心目标是帮助用户有效地管理生日和其他重要日期,提供提醒服务,以及定制化的界面...

    RxTools-工具使用

    3. **权限管理**:在 Android 6.0(API 级别 23)及更高版本,应用需要在运行时请求危险权限。RxTools 可能提供了一种优雅的方式来处理权限请求,使得开发者可以更方便地集成权限检查和请求。 4. **日志打印**:...

Global site tag (gtag.js) - Google Analytics