动态加载so库

Android集成虹软人脸demo动态加载so库问题
本文讲述在集成虹软人脸demo时,因so库大导致apk体积大,采用动态加载so库的方法。介绍了将so文件拷贝到指定位置并加载的步骤,同时阐述了在操作过程中遇到的文件读写权限申请、so文件位数不匹配、找不到so文件等问题及相应解决办法。

今天在集成虹软的人脸demo的时候,发现so库太大也就导致apk体积大。于是用动态加载,理论上是从服务器下载,然后放到指定位置进行加载,这里先在本地进行拷贝。一共两个文件:libarcsoft_face.so和libarcsoft_face_engine.so。

1.将so文件放到sd卡根目录的arcFace下

String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "arcFace";

然后需要用到一个工具类SoFile

package com.arcsoft.arcfacedemo.common;

import android.content.Context;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class SoFile {

    /**
     * 加载 so 文件
     * @param context
     * @param fromPath 下载到得sdcard目录
     */
    public static void loadSoFile(Context context, String fromPath) {
        File dirs = context.getDir("libs", Context.MODE_PRIVATE);
        if (!isLoadSoFiles("libarcsoft_face.so", dirs) || !isLoadSoFiles("libarcsoft_face_engine.so", dirs)) {
            copyFile(fromPath, dirs.getAbsolutePath());
        }
    }

    /**
     * 判断immqy so 文件是否存在
     * @param dir
     * @param name "libimmqy" so库
     * @return boolean
     */
    public static boolean isLoadSoFiles(String name,File dirs) {
        boolean getSoLib = false;
        File[] currentFiles;
        currentFiles = dirs.listFiles();
        if (currentFiles == null) {
            return false;
        }
        for (int i = 0; i < currentFiles.length; i++) {
            if (currentFiles[i].getName().contains(name)) {
                getSoLib = true;
            }
        }
        return getSoLib;
    }


    /**
     * 要复制的目录下的所有非子目录(文件夹)文件拷贝
     * @param fromFile 指定的下载目录
     * @param toFile 应用的包路径
     * @return int
     */
    public static int copySdcardFile(String fromFiles, String toFile) {
        Log.d("要复制到", toFile);
        try {
            FileInputStream fileInput = new FileInputStream(fromFiles);
            FileOutputStream fileOutput = new FileOutputStream(toFile);
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024*1];
            int len = -1;
            while ((len = fileInput.read(buffer)) != -1) {
                byteOut.write(buffer, 0, len);
            }
            // 从内存到写入到具体文件
            fileOutput.write(byteOut.toByteArray());
            // 关闭文件流
            byteOut.close();
            fileOutput.close();
            fileInput.close();
            return 0;
        } catch (Exception ex) {
            return -1;
        }
    }



    /**
     *
     * @param fromFiles 指定的下载目录
     * @param toFile 应用的包路径
     * @return int
     */
    public static int copyFile(String fromFiles, String toFile) {
        //要复制的文件目录
        File[] currentFiles;
        File root = new File(fromFiles);
        //如同判断SD卡是否存在或者文件是否存在,如果不存在则 return出去
        if (!root.exists()) {
            return -1;
        }
        //如果存在则获取当前目录下的全部文件 填充数组
        currentFiles = root.listFiles();
        if (currentFiles == null) {
            Log.d("soFile---","未获取到文件");
            return -1;
        }
        //目标目录
        File targetDir = new File(toFile);
        //创建目录
        if (!targetDir.exists()) {
            targetDir.mkdirs();
        }
        //遍历要复制该目录下的全部文件
        for (int i = 0; i < currentFiles.length; i++) {
            if (currentFiles[i].isDirectory()) {
                Log.d("当前项为子目录 进行递归", "copyFile: ");
                //如果当前项为子目录 进行递归
                copyFile(currentFiles[i].getPath() + "/", toFile + currentFiles[i].getName() + "/");
            } else {
                //如果当前项为文件则进行文件拷贝
                if (currentFiles[i].getName().contains(".so")) {
                    Log.d("当前项为文件则进行文件拷贝", "copyFile: ");
                    int id = copySdcardFile(currentFiles[i].getPath(), toFile + File.separator + currentFiles[i].getName());
                }
            }
        }
        return 0;
    }
}

2.再调用下面这个方法完成拷贝

SoFile.loadSoFile(getApplicationContext(), path);

3.加载so文件

File dir = getApplicationContext().getDir("libs", Context.MODE_PRIVATE);
File[] currFiles = null;
currFiles = dir.listFiles();
Log.d("listFiles", String.valueOf(currFiles.length));
for (int i = 0; i < currFiles.length; i++) {
    System.load(currFiles[i].getAbsolutePath());
}

然后,没什么问题就能正常使用了。

这里遇到几个问题:

一、在此之前要进行文件读写权限的申请,否则可能出现找不到你存放的文件(listFiles返回值为null)。

二、"xxx.so" is 64-bit instead of 32-bit ,这个问题是由于在64位的android机上,会有32位的虚拟机和64位的虚拟机,在启动apk的时候,虚拟机会根据apk中的so的位数启动对应的虚拟机。这个时候最简单的解决办法就是将任意一个32位的so文件放到指定目录下,我的就是本来有两个so文件(如下图),当我只把libarcsoft_face.so单独拿出来动态加载就可以,但是把两个一起拿出来动态加载就会出现这个问题,所以应该可以判断libarcsoft_face_engine.so文件是32位的。

来自:加载包含so文件的插件报错XXXX.so" is 32-bit instead of 64-bit

三、java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader *** couldn't find "libarcsoft_face.so"

这里报错的原因是,在虹软提供的jar包里面,在FaceEngine这个类中有一个静态代码块,会从系统目录加载so库,也就是仍然调用了“System.loadLibrary("arcsoft_face.so")”,因为App无法把so文件复制到系统目录,所以导致System.loadLibrary方法找不到arcsoft_face.so。我这里解决的办法是,把jar包中的类复制出来,删掉jar包,在复制的类中删掉这个静态代码块。这样就能走我们的System.load()方法动态加载了。

参考资料:动态加载so库的实现方法与问题处理

四、dlopen failed: library "libarcsoft_face.so" not found

还有其他类型的报错,但是都是提示找不到这个so文件。查阅大量资料后得知,问题可能出在so文件的加载顺序不对,我把加载的顺序打印出来,发现先加载的是libarcsoft_face_engine.so这个文件,我把顺序改变后,这个错误就没有了,因此结合自己的猜想,应该是在libarcsoft_face_engine有对libarcsoft_face.so的调用,但是libarcsoft_face.so此时还未加载,所以报错说找不到。

后来我想着这样毕竟不太好,就把listFiles获取的两个so文件按大小排序,但是又发现,华为nova 2s手机上排序正常,我的一加3T手机排序后还是那样,于是放弃,就这么写吧。

参考资料:Android——JNI加载so两种方式

在Linux系统下使用Qt5动态加载so,可按以下步骤进行: ### 确认环境变量 新安装的Qt环境,需将Qt目录添加到环境变量`LD_LIBRARY_PATH`,否则可能会导致加载`libQt5XcbQpa.so.5`等时从最低优先级地方寻找,影响的正常加载 [^1]。 ### 实现加载动态的代码 可以通过`QLibrary`类来动态加载so。以下是一个示例代码: ```cpp #include <QFile> #include <QLibrary> #include <QString> bool loadLibrary(const QString& appPath) { QLibrary *m_pLibTest; // 按照实际动态所在的目录 QString strLibFile = appPath + "/dll/libtestDll.so"; if (QFile::exists(strLibFile)) m_pLibTest = new QLibrary(strLibFile); else { slotAppendText(strLibFile + " don't exists"); return false; } if(!m_pLibTest->load()) { QString strErrMsg = m_pLibTest->errorString(); slotAppendText(strLibFile + " load failed: " + m_pLibTest->errorString()); return false; } else return true; } ``` 在上述代码中,首先检查动态文件是否存在,若存在则创建`QLibrary`对象并尝试加载,若加载失败则输出错误信息 [^2]。 ### 在应用中包含路径 如果要在创建加载的应用(如`QMainwindow`)中使用,可在其头文件中包含的路径。例如,若名为`libtest.so`,可使用如下相对路径(需根据实际情况修改): ``` LIBS += -L./../libTest -ltest ``` 这里`-L`指定的搜索路径,`-l`指定名(去掉`lib`前缀和`.so`后缀) [^3]。 ### 加载输入法静态插件(可选) 若涉及输入法插件,输入法插件正常编译后会生成静态,如`libfcitxplatforminputcontextplugin.a`,可直接在`CMakeList.txt`中使用`target_link_libraries()`加载 [^4]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值