動態加載so文件

我想對於靜態加載 so 庫文件,大家都已經很熟悉了,這裏就不多說了。在 Android 開發中調用動態庫文件(*.so)都是通過 jni 的方式,而靜態加載往往是在 apk 或 jar 包中調用so文件時,都要將對應 so 文件打包進 apk 或 jar 包。

動態加載的優點

靜態加載,不靈活,apk 包有可能大。所以採用動態加載 so 庫文件,有以下幾點好處:

  1. 靈活,so 文件可以動態加載,不是綁定死的,修改方便,so 庫有問題,我們可以動態更新。
  2. so 庫文件很大的話,採用動態加載可以減少 apk 的包,變小。
  3. 其實我們常用第三方 so 庫,單個可能沒問題,如果多個第三方 so 庫文件,同時加載可能會出現衝突,而動態加載就能夠解決這一問題。

注意路徑陷阱

動態加載 so 庫文件,並不是說可以把文件隨便存放到某個 sdcard 文件目錄下,這樣做既不安全,系統也加載不了。

我們在 Android 中加載 so 文件,提供的 API 如下:

1
2
3
4
//第一種,pathName 庫文件的絕對路徑
void System.load(String pathName);
//第二種,參數爲庫文件名,不包含庫文件的擴展名,必須是在JVM屬性Java.library.path所指向的路徑中,路徑可以通過System.getProperty('java.library.path') 獲得
void loadLibrary(String libname)

注意:而這裏加載的文件路徑只能加載兩個目錄下的 so 文件。那就是:

  1. /system/lib
  2. 應用程序安裝包的路徑,即:/data/data/packagename/…

所以,so 文件動態加載的文件目錄不能隨便放。這是需要注意的一點。

實現思路

既然使用動態加載的好處和陷阱我們都大致瞭解了,那就可以在實現的時候,注意陷阱就可以了。那基本思路如下:

  1. 網絡下載 so 文件到指定目錄
  2. 從指定下載的目錄複製 copy so文件到可動態加載的文件目錄下,比如:/data/data/packagename/…
  3. 配置 gradle ,指定 cpu 架構
  4. load 加載

第一步,我們這裏可以簡單忽略,假設我們把 so 文件下載到了 /mnt/sdcard/armeabi 目錄下。

複製目錄到包路徑下

那我們就應該把 /mnt/sdcard/armeabi 目錄下的 so 文件,複製到 應用的包路徑下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* Created by loonggg on 2017/3/29.
*/
public class SoFile {
/**
* 加載 so 文件
* @param context
* @param fromPath 下載到得sdcard目錄
*/
public static void loadSoFile(Context context, String fromPath) {
File dir = context.getDir("libs", Context.MODE_PRIVATE);
if (!isLoadSoFile(dir)) {
copy(fromPath, dir.getAbsolutePath());
}
}
/**
* 判斷 so 文件是否存在
* @param dir
* @return
*/
public static boolean isLoadSoFile(File dir) {
File[] currentFiles;
currentFiles = dir.listFiles();
boolean hasSoLib = false;
if (currentFiles == null) {
return false;
}
for (int i = 0; i < currentFiles.length; i++) {
if (currentFiles[i].getName().contains("libwedsa23")) {
hasSoLib = true;
}
}
return hasSoLib;
}
/**
*
* @param fromFile 指定的下載目錄
* @param toFile 應用的包路徑
* @return
*/
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 {
//如果當前項爲文件則進行文件拷貝
if (currentFiles[i].getName().contains(".so")) {
int id = copySdcardFile(currentFiles[i].getPath(), toFile + File.separator + currentFiles[i].getName());
}
}
}
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;
}
}
}

配置 grade 指定 cpu 架構

我們都知道,在使用 so 文件的時候,so 庫類型和 CPU 架構類型,要一致,否則是會報錯的。原因很簡單,不同 CPU 架構的設備需要用不同類型 so 庫。CPU架構有如下幾種類型:ARMv5,ARMv7,x86,MIPS,ARMv8,MIPS64 和 x86_64。如果要適配很多手機,就需要在不同的類型下,放置對應的 so 文件。
配置方法如下:

1
2
3
4
5
6
7
8
9
10
11
defaultConfig {
applicationId "xxxx"
minSdkVersion 17
targetSdkVersion 25
versionCode 1
versionName "1.0"
ndk {
abiFilters "armeabi","armeabi-v7a","x86"
}
}

load 加載 so 文件

複製到可加載使用的包路徑下後,配置完 gradle 之後,就可以使用 load API 調用了。

1
2
3
4
5
6
File dir = getApplicationContext().getDir("l ibs", Context.MODE_PRIVATE);
File[] currentFiles;
currentFiles = dir.listFiles();
for (int i = 0; i < currentFiles.length; i++) {
System.load(currentFiles[i].getAbsolutePath());
}

這樣,我們就實現了動態加載 so 文件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章