Android 靜態和動態的呼叫so庫(JNI)
優點
為什麼我們需要動態載入?因為靜態載入中CPU的資料夾我們可能需要相容的話需要放在不同arm資料夾下,那麼就會導致apk 包體過大,還有安卓Android SDK系統版本導致的差異,所以我們採用動態載入 so 庫檔案的話最主要的好處就是可以減小包體,其次,我們去修改so庫時也比較方便,因為是動態的,我們可以動態的同步更新。
還有另外一個原因就是,我們常常會用到第三方的 so 庫,如果單個庫可能沒問題,如果多個第三方 so 庫檔案,同時載入可能會出現衝突,比如說騰訊的YSDK和BUGLY,而動態載入就能夠很好的解決這個問題。
實現步驟
基本步驟如下:
- 第一步,從網路下載 so庫檔案到指定目錄。
- 然後,從指定的目錄中 複製 copy so庫檔案到自身包名目錄下,比如:
/data/data/packagename/…
,當然我們需要配置 gradle ,這一點很多人會不記得操作,記得需要指定 cpu 的架構和可動態載入的檔案。如(/data/data/immqy/miqiyun/...
)。 最後load 載入。
那我們如何操作?
第一步:網路下載so庫,其實就是下載檔案,比較簡單,這裡不再過多的闡述,可檢視檔案下載的《Android 非同步檔案下載》
第二步:我們假設指定目錄為SdCard中的IMMQY資料夾,那麼路徑為:/mnt/sdcard/IMMQY/
,把此路徑複製到包下的可執行路徑,程式碼如下:
/**
*
* Author:Karl-Dujinyang
*
* 2016/11/10
*/
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(dirs)) {
copyFile(fromPath, dir.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) {
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(baos.toByteArray());
// 關閉檔案流
byteOut.close();
fileOutput.close();
fileInput.close();
return 0;
} catch (Exception ex) {
return -1;
}
}
/**
*
* @param fromFile 指定的下載目錄
* @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();
//目標目錄
File targetDir = new File(toFile);
//建立目錄
if (!targetDir.exists()) {
targetDir.mkdirs();
}
//遍歷要複製該目錄下的全部檔案
for (int i = 0; i < currentFiles.length; i++) {
if (currentFiles[i].isDirectory()) {
//如果當前項為子目錄 進行遞迴
copyFile(currentFiles[i].getPath() + "/", toFile + currentFiles[i].getName() + "/");
} else {
//如果當前項為檔案則進行檔案拷貝
if (currentFiles[i].getName().contains(".so")) {
int id = copySdcardFile(currentFiles[i].getPath(), toFile + File.separator + currentFiles[i].getName());
}
}
}
return 0;
}
}
程式碼寫入到目錄後,我們就需要配置 grade 了。
配置方法如下:
defaultConfig {
applicationId "immqy"
minSdkVersion 16
targetSdkVersion 26
versionCode 2
versionName "2.2"
ndk {
abiFilters "armeabi","armeabi-v7a","x86"
}
}
提示:CPU架構有如下幾種型別:ARMv5,ARMv7,ARMV7A,x86,MIPS,ARMv8,MIPS64 和 x86_64,so 庫型別和 CPU 架構型別要一致,否則是會報錯的。
配置完成後,就剩下最後一步了。
第三步:load程式碼載入進行呼叫,很簡單,程式碼如下:
File dir = getApplicationContext().getDir("libs", Context.MODE_PRIVATE);
File[] currFiles = null;
currFiles = dir.listFiles();
for (int i = 0; i < currFiles.length; i++) {
System.load(currFiles[i].getAbsolutePath());
}
需要注意的是,getApplicationContext引用不能為空,所以最好還是先判斷下上下文物件是否為空。
這樣就可以使用 load API 呼叫動態載入 so 檔案了。
踩坑
有人會想,檔案下載過來是否安全,其實是否定的,大家也可以把檔案加密,或者把檔案路徑丟深一點。
我們在 Android 中載入 so 檔案,提供的 API 如下:
- 第一種,pathName 庫檔案的絕對路徑
void System.load(String pathName)
。 - 第二種,引數為庫檔名,不包含庫檔案的副檔名,必須是在JVM屬性
Java.library.path
所指向的路徑中,路徑可以通過System.getProperty('java.library.path')
, 獲得 void loadLibrary(String libname)
注意:而這裡載入的檔案路徑只能載入兩個目錄下的 so 檔案,那就是:/system/lib和/data/data/packagename/…(應用程式安裝包下的路徑)
所以,so 檔案動態載入的檔案目錄不能隨便放。這是需要注意的一點。
|| 版權宣告:本文為博主杜錦陽原創文章,轉載請註明出處。