现在对于JNI的困惑:
①如果so过大,程序是怎样加载的?
②System.load 与System.loadLibrary 的区别是啥?
Android加载so文件的方式有两种:
- System.loadLibrary
System.load
两种方式都可用来装载库文件:
System.load参数必须为库文件的绝对路径
System.loadLibrary参数为库文件名,不包含库文件的扩展名,必须是在JVM属性Java.library.path所指向的路径中,路径可以通过System.getProperty(‘java.library.path’)获得。
所有动态加载的时候我们不能用System.loadLibrary,只能用System.load来加载。
静态加载so的问题:
- 缺少灵活性比较类似静态加载了(不是静态加载),能加载的so文件绑定死了;
- 但so文件很多或很大时,会导致对应的apk和jar包很大;
- 不能动态的对so文件更新;
动态加载
- so文件不打包进apk,在安装完应用打开app的时候通过后台下载so库,将下载下来的so文件再写入到app里面。
Android中加载so文件的提供的API:
void System.load(String pathName);
说明:
- pathName:文件名+文件路径;
- 该方法调用成功后so文件中的导出函数都将插入的系统提供的一个映射表(类型Map);
注意:
- so文件是不能随便放到一个指定的目录然后再通过参数pathName直接引用的。
- 因为System.load只能加载两个目录路径下的so文件:
** ①/system/lib ;
** ②安装包的路径,即:/data/data//…
而且这两个路劲又是有权限保护的不能直接访问;
动态加载SO库流程
①从网络下载(或从其他文件夹中加载)so文件到手机目录(如:/test/device/test.so)
② 将test.so加载到内存(ByteArrayOutputStream)
③ 保存到对用安装包目录;
下载:
上文提到用system.load 完成动态加载so组件,而system.load需要传入绝对路径,所以在下载的时候直接指定下载的路径。建议将so文件copy到我们app的目录(data/data/包名/app_lib)下面。
public static void loadSoFile(Context context) {
//直接将so下载至 App的目录下
File dir = context.getDir("libs", Context.MODE_PRIVATE);
if (!isLoadSoFile(dir)) {
copy(formPath, dir.getAbsolutePath());
}
}
public static boolean isLoadSoFile(File dir) {
File[] currentFiles;
currentFiles = dir.listFiles();
boolean hasJkffmpeg = false;
if (currentFiles == null) {
return false;
}
for (int i = 0; i < currentFiles.length; i++) {
if (currentFiles[i].getName().contains(DuduUtil.SoFile.IJKFFMPEG)) {
hasJkffmpeg = true;
}
}
return hasJkffmpeg;
}
先判断app_lib下有没有我们需要加载的so文件,如果没有的话复制到指定目录,其中formPath是下载到sdcard上so文件的路径。
public static int copy(String fromFile, String toFile) {
//要复制的文件目录
File[] currentFiles;
File root = new File(fromFile);
//如同判断SD卡是否存在或者文件是否存在
//如果不存在则 return出去
if (!root.exists()) {
return -1;
}
//如果存在则获取当前目录下的全部文件 填充数组
currentFiles = root.listFiles();
//目标目录
File targetDir = new File(toFile);
//创建目录
if (!targetDir.exists()) {
targetDir.mkdirs();
}
//遍历要复制该目录下的全部文件
for (int i = 0; i < currentFiles.length; i++) {
if (currentFiles[i].isDirectory()) {
//如果当前项为子目录 进行递归
copy(currentFiles[i].getPath() + "/", toFile + currentFiles[i].getName() + "/");
} else {
//如果当前项为文件则进行文件拷贝
Log.e(TAG, "path:" + currentFiles[i].getPath());
Log.e(TAG, "name:" + currentFiles[i].getName());
if (currentFiles[i].getName().contains(".so")) {
int id = copySdcardFile(currentFiles[i].getPath(), toFile + File.separator + currentFiles[i].getName());
Log.e(TAG, "id:" + id);
}
}
}
return 0;
}
//文件拷贝
//要复制的目录下的所有非子目录(文件夹)文件拷贝
public static int copySdcardFile(String fromFile, String toFile) {
try {
FileInputStream fosfrom = new FileInputStream(fromFile);
FileOutputStream fosto = new FileOutputStream(toFile);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = fosfrom.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
// 从内存到写入到具体文件
fosto.write(baos.toByteArray());
// 关闭文件流
baos.close();
fosto.close();
fosfrom.close();
return 0;
} catch (Exception ex) {
return -1;
}
}
Load进工程:
File dir = context.getDir("libs", Context.MODE_PRIVATE);
File[] currentFiles;
currentFiles = dir.listFiles();
for (int i = 0; i < currentFiles.length; i++) {
Log.e(TAG, "#:" + currentFiles[i].getAbsolutePath());
System.load(currentFiles[i].getAbsolutePath());
}
出现的问题:
java.lang.UnsatisfiedLinkError: dlopen failed: “XXXX.so” is 32-bit instead of 64-bit
- 大体原因是手机cpu为64位,并且在app中你配置了加载64位(arm64-v8a),所有系统会去加载64位路径下的so库,但是你load的so库却是32位,解决方法有两种:1、将32为库换成64位,2、只加载32位库,在build.gradle中配置,armeabi,armeabi-v7a,x86位32位,arm64-v8a,x86_64是64位。
defaultConfig {
applicationId "xxxx"
minSdkVersion 10
targetSdkVersion 21
versionCode 1
versionName "1.0"
ndk {
abiFilters "armeabi","armeabi-v7a","x86"
}
}
2.java.lang.UnsatisfiedLinkError: Couldn’t load XXX from loader dalvik.system.DexClassLoader[DexPathList[]]: findLibrary returned null
加载时注意加载so文件的顺序,遇到的问题是load ijkffmpeg.so文件时需要先load另外一个so文件,但是我先load了ffmpeg,所以报了 couldn’t load异常