简介
前几天做一个视频播放的功能,用到了bilibili开源ijkplayer播放器的(集成ijkplayer),功能确实强大,但就是用到的ffmpeg解码库太大,不得已只能只能将so文件拿出来,通过动态的方式来加载。
什么是动态加载?
就是讲so文件不打包进apk,在安装完应用打开app的时候通过后台下载so库,将下载下来的so文件再写入到app里面。
首先我们要知道,Android加载so文件的方式有两种:
- System.loadLibrary
- System.load
它们都可以用来装载库文件,但是System.load参数必须为库文件的绝对路径,可以是任意路径;System.loadLibrary参数为库文件名,不包含库文件的扩展名,必须是在JVM属性java.library.path所指向的路径中,路径可以通过System.getProperty('java.library.path')
获得。所有动态加载的时候我们不能用System.loadLibrary,只能用System.load来加载。
介绍
- 下载so文件
- 安装
- load加载
其中下载没什么说的,不过推荐Netroid,下载一般下载到sdcard,前面说过System.load参数为绝对路径,也就是说你可以直接加载sdcard上面的so文件,但是在sdcard上可能会被用户删除或者修改,所以我建议将so文件copy到我们app的目录(data/data/包名/app_lib)下面
public static void loadSoFile(Context context) {
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文件的路径
ublic 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 ijkffmpeg from loader dalvik.system.DexClassLoader[DexPathList[]]: findLibrary returned null
加载时注意加载so文件的顺序,遇到的问题是load ijkffmpeg.so文件时需要先load另外一个so文件,但是我先load了ffmpeg,所以报了 couldn’t load异常