phi*_*ipp 38 java-native-interface android native android-ndk unsatisfiedlinkerror
我们正在体验java.lang.UnsatisfiedLinkError一些在市场上使用我们的应用程序的Android手机.
问题描述:
static
{
System.loadLibrary("stlport_shared"); // C++ STL
System.loadLibrary("lib2");
System.loadLibrary("lib3");
}
Run Code Online (Sandbox Code Playgroud)
System.loadLibrary()使用a 使应用程序崩溃java.lang.UnsatisfiedLinkError.
java.lang.UnsatisfiedLinkError: Couldn't load stlport_shared from loader dalvik.system.PathClassLoader[dexPath=/data/app/app_id-2.apk,libraryPath=/data/app-lib/app_id-2]: findLibrary returned null
解决方法
我们开始在所有安装上运行一些自定义诊断程序,以检查/data/data/app_id/lib文件夹中是否已解压缩每个lib .
PackageManager m = context.getPackageManager();
String s = context.getPackageName();
PackageInfo p;
p = m.getPackageInfo(s, 0);
s = p.applicationInfo.dataDir;
File appDir = new File(s);
long freeSpace = appDir.getFreeSpace();
File[] appDirList = appDir.listFiles();
int numberOfLibFiles = 0;
boolean subFilesLarger0 = true;
for (int i = 0; i < appDirList.length; i++) {
if(appDirList[i].getName().startsWith("lib")) {
File[] subFile = appDirList[i].listFiles(FileFilters.FilterDirs);
numberOfLibFiles = subFile.length;
for (int j = 0; j < subFile.length; j++) {
if(subFile[j].length() <= 0) {
subFilesLarger0 = false;
break;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
在每一个测试手机,我们有numberOfLibFiles == 3和subFilesLarger0 == true.我们想测试是否所有的库都正确解包并且大于0字节.此外,我们正在寻找freeSpace可用的磁盘空间.freeSpace匹配您可以在屏幕底部的设置 - >应用程序中找到的内存量.这种方法背后的想法是,当磁盘上没有足够的可用空间时,安装程序可能在解压APK时遇到问题.
现实场景
看一下诊断,那里的一些设备没有/data/data/app_id/lib文件夹中的所有3个库,但是有足够的可用空间.我想知道为什么错误消息正在寻找/data/app-lib/app_id-2.我们所有的手机都存放着自己的库/data/data/app_id/lib.另外,System.loadLibrary()应使用整个安装和装载库一致的路径?我怎么知道操作系统在哪里寻找libs?
题
有人在安装本机库时遇到问题吗?什么工作成功了?有没有经验只是在不存在时通过互联网下载本机库并手动存储它们?什么可能导致问题首先?
编辑
我现在还有一个用户在应用程序更新后遇到此问题.之前的版本在他的手机上运行良好,经过更新后,原生库似乎缺失了.手动复制库似乎也会带来麻烦.他在Android 4.x上使用没有自定义ROM的非root电话.
编辑2 - 解决方案
经过2年的时间花在这个问题上.我们想出了一个适合我们的解决方案.我们开源:https://github.com/KeepSafe/ReLinker
tig*_*chi 19
编辑:因为昨天我的一个应用程序收到了另一个崩溃报告,我更深入地研究了这个问题,并找到了第三个非常可能的解释:
Google Play Partial APK更新错误
说实话,我不知道这个功能.APK文件名后缀"-2.apk"让我怀疑.这个问题的崩溃消息中提到了这一点,我也可以在我的客户的崩溃报告中找到后缀.
我相信"-2.apk"提示部分更新可能会为Android设备提供更小的增量.该delta显然不包含本机库,因为它们自上一版本以来没有发生变化.
无论出于何种原因,System.loadLibrary函数都会尝试从部分更新(不存在)中查找本机库.这既是Android和Google Play的缺陷.
这是一个非常相关的错误报告,其中包含有关类似观察结果的有趣讨论:https://code.google.com/p/android/issues/detail?id = 35962
看起来Jelly Bean在本机库安装和加载方面可能存在缺陷(两个崩溃报告都是Jelly Bean设备).
如果确实如此,我怀疑NDK库代码中的某些强制更改可能会解决问题,例如为每个版本更改一些未使用的虚拟变量.但是,这应该以编译器保留该变量而不优化它的方式完成.
编辑(12/19/13):遗憾的是,为每个版本更改本机库代码的想法不起作用.我用我的一个应用程序尝试了它并得到了一个"不满意的链接错误"崩溃报告,无论如何更新的客户.
不完整的APK安装
不幸的是,这只是我的记忆,我再也找不到链接了.去年我读了一篇关于这个不满意的链接问题的博客文章.作者说这是APK安装例程中的一个错误.
当将原生库复制到目标目录时,无论出于何种原因(设备用完存储空间,也可能搞乱了目录写入权限......),安装程序仍会返回"成功",就像本机库只是"可选扩展"一样一个应用程序
在这种情况下,唯一的解决方法是重新安装APK,同时确保应用程序有足够的存储空间.
但是,我找不到任何Android错误票,也不能找到原始的博客文章,我搜索了很多.因此,这种解释可能属于神话和传说的范畴.
"armeabi-v7a"目录优先于"armeabi"目录
此错误故障单讨论暗示了安装的本机库的"并发问题",请参阅Android错误凭单#9089.
如果只有一个本机库存在"armeabi-v7a"目录,则该体系结构的整个目录优先于"armeabi"目录.
如果您尝试加载"armeabi"中存在的库,您将收到UnsatisfiedLinkException.顺便提一下,这个bug被标记为"按预期工作".
可能的解决方法
在任何一种情况下:我在SO上找到了一个类似问题的有趣答案.这一切都归结为将所有本机库打包为APK的原始资源,并在第一个应用程序上复制,将当前处理器体系结构的正确库启动到(app private)文件系统.使用带有完整文件路径的System.load来加载这些库.
然而,这种解决方法存在一个缺陷:由于本地库将作为资源驻留在APK中,Google Play将无法再找到它们并为强制处理器架构创建设备过滤器.它可以通过将"虚拟"本机库放入所有目标体系结构的lib文件夹来解决.
总的来说,我确实认为这个问题应该正确传达给谷歌.好像Jelly Bean和Google Play都有缺陷.
通常有助于告诉客户该问题重新安装应用程序.遗憾的是,如果应用数据丢失是一个问题,这不是一个好的解决方案.
gre*_*gko 13
我遇到了同样的麻烦,所有版本的Android都出现了UnsatisfiedLinkErrors--在过去的6个月中,对于目前有超过90000个有效安装的应用,我有:
Android 4.2 36 57.1%
Android 4.1 11 17.5%
Android 4.3 8 12.7%
Android 2.3.x 6 9.5%
Android 4.4 1 1.6%
Android 4.0.x 1 1.6%
Run Code Online (Sandbox Code Playgroud)
并且用户报告它通常在应用更新后发生.这适用于每天大约200-500个新用户的应用.
我想我想出了一个更简单的解决方法.我可以通过这个简单的调用找出我的应用程序的原始apk的位置:
String apkFileName = context.getApplicationInfo().sourceDir;
Run Code Online (Sandbox Code Playgroud)
这会返回类似"/data/app/com.example.pkgname-3.apk"的内容,这是我的应用APK文件的确切文件名.此文件是常规ZIP文件,无需root即可读取.因此,如果我捕获java.lang.UnsatisfiedLinkError,我可以从.apk(zip)lib/armeabi-v7a文件夹(或我所在的任何架构)内部提取和复制我的本机库,到任何目录我可以读/写/执行,并使用System.load(full_path)加载它.
自2014年6月23 日发布我的产品版本以及类似下面列出的代码后,2014年7月1日更新,我的本机库没有任何不满意的链接错误.
这是我使用的代码:
public static void initNativeLib(Context context) {
try {
// Try loading our native lib, see if it works...
System.loadLibrary("MyNativeLibName");
} catch (UnsatisfiedLinkError er) {
ApplicationInfo appInfo = context.getApplicationInfo();
String libName = "libMyNativeLibName.so";
String destPath = context.getFilesDir().toString();
try {
String soName = destPath + File.separator + libName;
new File(soName).delete();
UnzipUtil.extractFile(appInfo.sourceDir, "lib/" + Build.CPU_ABI + "/" + libName, destPath);
System.load(soName);
} catch (IOException e) {
// extractFile to app files dir did not work. Not enough space? Try elsewhere...
destPath = context.getExternalCacheDir().toString();
// Note: location on external memory is not secure, everyone can read/write it...
// However we extract from a "secure" place (our apk) and instantly load it,
// on each start of the app, this should make it safer.
String soName = destPath + File.separator + libName;
new File(soName).delete(); // this copy could be old, or altered by an attack
try {
UnzipUtil.extractFile(appInfo.sourceDir, "lib/" + Build.CPU_ABI + "/" + libName, destPath);
System.load(soName);
} catch (IOException e2) {
Log.e(TAG "Exception in InstallInfo.init(): " + e);
e.printStackTrace();
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
不幸的是,如果一个糟糕的应用程序更新留下旧版本的本机库,或者某个副本以某种方式损坏,我们加载了System.loadLibrary("MyNativeLibName"),则无法卸载它.在找到遗留在标准应用程序本机lib文件夹中的此类残余已解散库时,例如通过调用我们的本机方法之一并发现它不存在(再次出现UnsatisfiedLinkError),我们可以存储首选项以避免调用标准System.loadLibrary( )完全依赖我们自己的提取和加载代码在下一个应用程序初创公司.
为了完整性,这里是UnzipUtil类,我从这个CodeJava UnzipUtility文章复制和修改:
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class UnzipUtil {
/**
* Size of the buffer to read/write data
*/
private static final int BUFFER_SIZE = 4096;
/**
* Extracts a zip file specified by the zipFilePath to a directory specified by
* destDirectory (will be created if does not exists)
* @param zipFilePath
* @param destDirectory
* @throws java.io.IOException
*/
public static void unzip(String zipFilePath, String destDirectory) throws IOException {
File destDir = new File(destDirectory);
if (!destDir.exists()) {
destDir.mkdir();
}
ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
ZipEntry entry = zipIn.getNextEntry();
// iterates over entries in the zip file
while (entry != null) {
String filePath = destDirectory + File.separator + entry.getName();
if (!entry.isDirectory()) {
// if the entry is a file, extracts it
extractFile(zipIn, filePath);
} else {
// if the entry is a directory, make the directory
File dir = new File(filePath);
dir.mkdir();
}
zipIn.closeEntry();
entry = zipIn.getNextEntry();
}
zipIn.close();
}
/**
* Extracts a file from a zip to specified destination directory.
* The path of the file inside the zip is discarded, the file is
* copied directly to the destDirectory.
* @param zipFilePath - path and file name of a zip file
* @param inZipFilePath - path and file name inside the zip
* @param destDirectory - directory to which the file from zip should be extracted, the path part is discarded.
* @throws java.io.IOException
*/
public static void extractFile(String zipFilePath, String inZipFilePath, String destDirectory) throws IOException {
ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
ZipEntry entry = zipIn.getNextEntry();
// iterates over entries in the zip file
while (entry != null) {
if (!entry.isDirectory() && inZipFilePath.equals(entry.getName())) {
String filePath = entry.getName();
int separatorIndex = filePath.lastIndexOf(File.separator);
if (separatorIndex > -1)
filePath = filePath.substring(separatorIndex + 1, filePath.length());
filePath = destDirectory + File.separator + filePath;
extractFile(zipIn, filePath);
break;
}
zipIn.closeEntry();
entry = zipIn.getNextEntry();
}
zipIn.close();
}
/**
* Extracts a zip entry (file entry)
* @param zipIn
* @param filePath
* @throws java.io.IOException
*/
private static void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
byte[] bytesIn = new byte[BUFFER_SIZE];
int read = 0;
while ((read = zipIn.read(bytesIn)) != -1) {
bos.write(bytesIn, 0, read);
}
bos.close();
}
}
Run Code Online (Sandbox Code Playgroud)
格雷格
| 归档时间: |
|
| 查看次数: |
12187 次 |
| 最近记录: |