Android動態載入—so檔案
簡介
前幾天做一個視訊播放的功能,用到了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')
介紹
- 下載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異常