问题:不知道如何加载so包
过程:在Android Studio用Java代码运行一个Demo(原始音频录播)时发现运行失败,代码出错在so包没有加载,目前明确知道代码没错,但是不知道so包如何加载(有jni文件)
现在烦请各位教教我如何解决该问题
在 Android 中,so 库的加载是由 Dalvik 虚拟机通过 JNI 接口来完成的。因此,在 Java 代码中无法直接加载 so 库。要加载一个 so 库,需要考虑以下几个方面:
确认你的 so 库是否已经被正确编译和生成。通常情况下,Android Studio 会自动为你编译生成 so 库。
将生成的 so 库文件放到正确的位置。在 Android 中,so 库默认存放在 app/src/main/jniLibs 目录下,其中 main 目录是必须存在的,而 jniLibs 则是需要手动创建的,目录名字必须是这样的,否则无法正常加载。
在 Java 代码中通过 System.loadLibrary 或者 System.load 方法来加载 so 库。其中,System.loadLibrary 用于加载预编译库(即将 .so 文件打包到 APK 中),而 System.load 则用于加载外部库(即从外部文件系统中加载 .so 文件)。
例如,如果你想加载名为 "native-lib" 的预编译库,可以在 Java 代码中使用以下语句来加载:
java
static {
System.loadLibrary("native-lib");
}
这里假设你的 so 文件名为 libnative-lib.so,那么它应该位于 app/src/main/jniLibs/armeabi-v7a/ 目录下,其中 armeabi-v7a 是针对 ARM 处理器的 ABI 类型,不同的 ABI 类型对应不同的目录名。
如果你想加载外部库,可以使用以下语句来加载:
java
static {
System.load("/path/to/libnative-lib.so");
}
其中 /path/to/ 是你的 .so 文件所在的路径。
请注意,Java 代码中加载 so 库需要在 AndroidManifest.xml 文件中添加以下权限:
xml
这是因为 Dalvik 虚拟机会通过网络获取和加载库文件。
so 动态加载其实就是把本应该打包进 apk 的 so 库通过网络的方式,经过 md5、加解密等安全性校验后下载下来,再通过插件化的方式将下载的 so 正常装载使用的方案。
从代码层面考虑就是我们要将下载的 so 文件路径插入到 DexPathList 的 nativeLibraryDirectories 列表,这样就能让 ClassLoader 查找到我们的 so 文件路径正确加载出来。这里参考 Tinker 的思路写个简单的 demo 了解思路:
private void install(ClassLoader classLoader, File soAbsoluteFilePathDir) throws Throwable {
// 获取 DexClassLoader 的 DexPathList
final Field pathListField = classLoader.getClass().getDeclaredField("pathList");
pathListField.setAccessible(true);
final Object dexPathList = pathListField.get(classLoader);
// 获取 DexPathList 的 nativeLibraryDirectories
final Field nativeLibraryDirectories = dexPathList.getClass().getDeclaredField("nativeLibraryDirectories");
nativeLibraryDirectories.setAccessible(true);
List<File> origLibDirs = (List<File>) nativeLibraryDirectories.get(dexPathList);
if (origLibDirs == null) {
origLibDirs = new ArrayList<>(2);
}
// 如果 so 已经存在,移除路径重新装载
final Iterator<File> libDirIt = origLibDirs.iterator();
while (libDirIt.hasNext()) {
final File libDir = libDirIt.next();
if (soAbsoluteFilePathDir.equals(libDir)) {
libDirIt.remove();
break;
}
}
// 在索引为 0 的位置加入远程 so 文件路径
origLibDirs.add(0, soAbsoluteFilePathDir);
// 获取 DexPathList 的 systemNativeLibraryDirectories
final Field systemNativeLibraryDirectories = dexPathList.getClass().getDeclaredField("systemNativeLibraryDirectories");
systemNativeLibraryDirectories.setAccessible(true);
List<File> origSystemLibDirs = (List<File>) systemNativeLibraryDirectories.get(dexPathList);
if (origSystemLibDirs == null) {
origSystemLibDirs = new ArrayList<>(2);
}
// 将添加了远程 so 的 nativeLibraryDirectories 和 systemNativeLibraryDirectories 的 so 路径装载
final List<File> newLibDirs = new ArrayList<>(origLibDirs.size() + origSystemLibDirs.size() + 1);
newLibDirs.addAll(origLibDirs);
newLibDirs.addAll(origSystemLibDirs);
final Method makeElements = dexPathList.getClass().getDeclaredMethod("makePathElements", List.class);
makeElements.setAccessible(true);
final Object[] elements = (Object[]) makeElements.invoke(dexPathList, newLibDirs);
final Field nativeLibraryPathElements = dexPathList.getClass().getDeclaredField("nativeLibraryPathElements");
nativeLibraryPathElements.setAccessible(true);
nativeLibraryPathElements.set(dexPathList, elements);
}
当然动态 so 加载还需要考虑很多问题,譬如 so 存在依赖关系、版本控制等等,具体可以参考网上写得比较详细的两篇有关动态 so 加载的文章学习:
我很抱歉,以上参考资料和您的问题并没有直接关联。针对您的问题,我提供以下的解决方案:
确认so包的正确性:首先要确认您使用的so包是否正确。检查so库是否编译成功,以及它是否与您编译的目标架构兼容。可以使用以下命令进行检查:
file
如果返回的结果包含“ELF”,则表示该so库编译成功。
将so包添加到工程中:您需要将编译好的so库添加到工程中以便程序运行时能够加载。方法如下:
a. 在工程的“app”目录下创建名为“jniLibs”的文件夹。
b. 将so库放在该文件夹的对应目录下。例如,如果你的so库针对的是armeabi-v7a架构,则应该将so库放在“jniLibs/armeabi-v7a/”目录下。
加载so包:您需要编写jni代码来加载so库。具体步骤如下:
a. 在Java代码中声明native方法,例如:
public native void loadNativeLibrary();
b. 编写jni代码实现该方法。示例代码如下:
JNIEXPORT void JNICALL Java_com_example_MyClass_loadNativeLibrary(JNIEnv env, jobject obj) { // 加载so库 const char libraryName = "libYourLibraryName.so"; jint result = -1; result = env->GetJavaVM(&Jvm); if(result != JNI_OK){ return; } void* handle = dlopen(libraryName, RTLD_LAZY | RTLD_GLOBAL); if (NULL == handle) { return; } }
调用native方法:在Java代码中调用该native方法即可完成so库的加载。调用代码示例:
loadNativeLibrary();
希望我的解决方案对您有所帮助。如果您在实施过程中遇到其他问题,请随时与我联系。
名字写错了或者路劲错了或者配置写错了。
cmake文件贴出来,app的gradle配置贴出来,so库放的路劲贴出来。
三者对比就可以确认。