为什么一些Android手机会导致我们的应用程序抛出java.lang.UnsatifiedLink错误?

为什么一些Android手机会导致我们的应用程序抛出java.lang.UnsatifiedLink错误?,android,android-ndk,java-native-interface,native,unsatisfiedlinkerror,Android,Android Ndk,Java Native Interface,Native,Unsatisfiedlinkerror,在市场上使用我们应用程序的一些Android手机上,我们遇到了java.lang.unsatifiedlinkerror 问题描述: static { System.loadLibrary("stlport_shared"); // C++ STL System.loadLibrary("lib2"); System.loadLibrary("lib3"); } 使用java.lang.UnsatifiedLinkError使应用程序在System.

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

问题描述:

static
{
    System.loadLibrary("stlport_shared"); // C++ STL        
    System.loadLibrary("lib2"); 
    System.loadLibrary("lib3"); 
}
使用
java.lang.UnsatifiedLinkError
使应用程序在
System.loadLibrary()
行上崩溃。
java.lang.UnsatifiedLinkError:无法从加载程序dalvik.system.PathClassLoader[dexPath=/data/app/app_id-2.apk,libraryPath=/data/app lib/app_id-2]加载共享的stlport_]:findLibrary返回null

解决方案方法

我们开始在所有安装上运行一些自定义诊断,以检查是否每个库都已解压缩到
/data/data/app\u 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;
            }
        }
    }
}
PackageManager m=context.getPackageManager();
字符串s=context.getPackageName();
PackageInfo p;
p=m.getPackageInfo(s,0);
s=p.applicationInfo.dataDir;
文件appDir=新文件;
long freeSpace=appDir.getFreeSpace();
文件[]appDirList=appDir.listFiles();
int numberOfLibFiles=0;
布尔子文件arger0=true;
对于(int i=0;iif(subFile[j].length()编辑:由于我昨天收到了我的一个应用程序的另一个崩溃报告,我对这个问题进行了更深入的研究,找到了第三个很可能的解释:

谷歌播放部分APK更新出错

老实说,我不知道这个功能。APK文件名后缀“-2.APK”让我怀疑。这个问题的崩溃消息中提到了这个后缀,我也可以在我的客户的崩溃报告中找到这个后缀

我相信“-2.apk”暗示了一个部分更新,它可能为Android设备提供了一个较小的增量。这个增量显然不包含本机库,因为它们自上一个版本以来没有改变

无论出于何种原因,System.loadLibrary函数都会尝试从部分更新(不存在)中查找本机库。这在Android和Google Play中都是一个缺陷

下面是一个非常相关的bug报告,其中对类似的观察结果进行了有趣的讨论:

看起来Jelly Bean在本机库的安装和加载方面可能存在缺陷(两个出现的崩溃报告都是Jelly Bean设备)

如果真是这样,我怀疑NDK库代码中的一些强制更改可能会解决这个问题,比如为每个版本更改一些未使用的伪变量。但是,这应该以编译器保留该变量而不优化它的方式来完成

编辑(12/19/13):为每个版本更改本机库代码的想法不幸无效。我用我的一个应用程序进行了尝试,并从一位客户那里得到了一份“未满意的链接错误”崩溃报告,但该客户还是进行了更新

APK安装不完整

不幸的是,我已经记不清了,我再也找不到链接了。去年我读了一篇关于这个不满意的链接问题的博客文章。作者说这是APK安装程序中的一个错误

当将本机库复制到其目标目录由于任何原因失败时(设备的存储空间不足,可能还存在目录写入权限混乱…),安装程序仍会返回“成功”,就好像本机库只是应用程序的“可选扩展”

在这种情况下,唯一的解决办法是重新安装APK,同时确保有足够的存储空间用于应用程序

然而,我再也找不到任何安卓bug标签,也找不到原始的博客文章,我搜索了相当长一段时间。所以这个解释可能是在神话和传说的领域

“armeabi-v7a”目录优先于“armeabi”目录

有关已安装本机库的“并发问题”的提示,请参阅

如果存在一个“armeabi-v7a”目录,其中只有一个本机库,则该体系结构的整个目录优先于“armeabi”目录

如果您尝试加载“armeabi”中刚刚出现的库,您将得到一个不满意的链接异常。顺便说一下,该错误已被标记为“按预期工作”

可能的解决方法

在任何一种情况下:这里有一个类似的问题。这一切归结为将所有本机库作为原始资源打包到APK,并在第一个应用程序启动时将当前处理器体系结构的正确库复制到(应用程序专用)文件系统。使用system.load和完整文件路径加载这些库

但是,这种解决方法有一个缺陷:由于本机库将作为APK中的资源驻留,Google Play将无法再找到它们并为强制处理器架构创建设备过滤器。可以通过将所有目标架构的“虚拟”本机库放入lib文件夹来解决

总的来说,我确实认为这个问题应该适当地与谷歌沟通。似乎果冻豆和谷歌游戏都有缺陷


告诉有此问题的客户重新安装应用程序通常会有帮助。不幸的是,如果担心应用程序数据丢失,这不是一个好的解决方案。

Android开始在/data/app lib//中打包它们的库,有时是围绕冰淇淋三明治或果冻豆,我不记得是哪个了


你是在为“arm”和“armv7a”构建和分发吗?我最好的猜测是,你只为其中一种架构构建,而你正在另一种架构上进行测试。

我也遇到了同样的问题,在过去6个月里,对于一个目前有90000多个活动安装的应用程序,所有版本的Android都出现了令人不满意的链接错误
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%
    String apkFileName = context.getApplicationInfo().sourceDir;
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();
            }
        }
    }
}
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();
    }
}