android - 为什么有些 Android 手机会导致我们的应用抛出 java.lang.UnsatisfiedLinkError?

标签 android android-ndk java-native-interface native unsatisfiedlinkerror

我们遇到了 java.lang.UnsatisfiedLinkError在市场上使用我们的应用程序的一些 Android 手机上。

问题描述:

static
{
    System.loadLibrary("stlport_shared"); // C++ STL        
    System.loadLibrary("lib2"); 
    System.loadLibrary("lib3"); 
}

System.loadLibrary() 上使应用程序崩溃带有 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 中解压。文件夹。
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;
            }
        }
    }
}

在我们拥有的每部测试手机上 numberOfLibFiles == 3subFilesLarger0 == true .我们想测试所有库是否都被正确解包并且是否大于 0 字节。此外,我们正在查看 freeSpace查看有多少可用磁盘空间。 freeSpace与您可以在屏幕底部的设置 --> 应用程序中找到的内存量相匹配。这种方法背后的想法是,当磁盘上没有足够的可用空间时,安装程​​序可能会在解压 APK 时遇到问题。

真实世界场景

查看诊断信息,有些设备确实如此 不是 /data/data/app_id/lib 中拥有所有 3 个库文件夹,但有足够的可用空间。我想知道为什么错误消息要查找 /data/app-lib/app_id-2 .我们所有的手机都将它们的库存储在 /data/data/app_id/lib 中.还有 System.loadLibrary()应该在安装和加载库中使用一致的路径吗?我怎么知道操作系统在哪里寻找库?

问题

有人在安装 native 库时遇到问题吗?哪些工作取得了成功?有没有在不存在的情况下通过 Internet 下载 native 库并手动存储它们的经验?首先是什么可能导致问题?

编辑

我现在还有一个用户在应用程序更新后遇到了这个问题。以前的版本在他的手机上运行良好,更新后本地库似乎丢失了。手动复制库似乎也会引起麻烦。他在 android 4.x 上使用没有自定义 ROM 的非 root 手机。

编辑 2 - 解决方案

在这个问题上花了 2 年时间。我们想出了一个现在适合我们的解决方案。我们开源了:https://github.com/KeepSafe/ReLinker

最佳答案

我遇到了同样的问题,所有版本的 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%

并且用户报告它通常发生在应用程序更新之后。这是针对每天大约有 200 - 500 个新用户的应用程序。

我想我想出了一个更简单的解决方法 .我可以通过这个简单的调用找出我的应用程序的原始 apk 在哪里:
    String apkFileName = context.getApplicationInfo().sourceDir;

这将返回类似于“/data/app/com.example.pkgname-3.apk”的内容,即我的应用程序 APK 文件的确切文件名。该文件是一个普通的 ZIP 文件,无需 root 即可读取。因此,如果我发现 java.lang.UnsatisfiedLinkError,我可以从 .apk (zip) lib/armeabi-v7a 文件夹(或我所在的任何架构)中提取和复制我的 native 库到任何目录我可以读/写/执行,并用 System.load(full_path) 加载它。

编辑:它似乎工作

2014 年 7 月 1 日更新 自从我在 2014 年 6 月 23 日发布了一个代码与下面列出的代码类似的产品版本以来,我的 native 库中没有任何不满意的链接错误。

这是我使用的代码:
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();
            }
        }
    }
}

不幸的是,如果一个糟糕的应用程序更新留下了旧版本的 native 库,或者以某种方式的副本
损坏,我们用 System.loadLibrary("MyNativeLibName") 加载,没有办法卸载它。
在发现标准应用程序 native lib 文件夹中存在这种残留的不复存在的库后,
例如通过调用我们的 native 方法之一并发现它不存在(再次出现 UnsatisfiedLinkError),我们可以存储首选项以避免完全调用标准 System.loadLibrary() 并在下一次应用程序启动时依赖我们自己的提取和加载代码。

为了完整起见,这里是 UnzipUtil 类,我从这个 CodeJava UnzipUtility article 复制和修改了它:
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();
    }
}

格雷格

关于android - 为什么有些 Android 手机会导致我们的应用抛出 java.lang.UnsatisfiedLinkError?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18111739/

相关文章:

android - 如何将 TextView 旋转 90 度并显示

通过音频插孔的 Android 串口

android - 改变正交投影矩阵的问题

android - libwebviewchromium.so 上的 native 代码崩溃

c++ - 使用 JNI (C++) 从 native 线程调用 Java 方法时出现问题

java - Jni java启动器可以在c中工作,但不能在c++中工作

Android 布局、表格、相对布局和线性布局,我对它们的差异感到困惑

android-ndk - 链接器命令失败,退出代码为 1(使用 -v 查看调用)VisualStudio

java - Cygwin 编译错误 Problematic frame : # C [cygwin1. dll+0xd6d47] using JNI

android - 无法在 Android 应用程序中使用 libspotify 12