相比静态加载so,动态加载so最大好处是减少包的体积,节省一笔流量费用(针对使用移动卡的设备)。可以网络下发so文件到指定目录,再拷贝到可动态加载的文件目录下/data/data/packageName
或者网络下载so文件到可动态加载的文件目录下/data/data/packageName。国际规则:
https://github.com/kellysong/android-blog-demo/tree/master/soloader-demo
本demo支持从sd卡目录或者assets目录拷贝so文件到app私有目录
需要注意的是:直接拷贝so到app的私有目录,通过System.loadLibrary(soName)或System.load(soPath)加载时还是会报错的
Caused by: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader
我们需要通过ClassLoader
修改系统默认加载的so路径applicationInfo.nativeLibraryDir,替换成之前拷贝so到app的私有目录并放在DexPathList数组的第一位。
原理如下:
修改前so目录:
/data/app/com.sjl.soloader-nNYWatxQW10z30V7YgV9Og==/lib/arm64
DexPathList[[zip file "/data/app/com.sjl.soloader-nNYWatxQW10z30V7YgV9Og==/base.apk"],nativeLibraryDirectories=[/data/app/com.sjl.soloader-nNYWatxQW10z30V7YgV9Og==/lib/arm64, /system/lib64, /system/vendor/lib64]]
修改后so目录:
/data/user/0/com.sjl.soloader/app_libs/arm64-v8a //这是我们直接拷贝so到app的私有目录
DexPathList[[zip file "/data/app/com.sjl.soloader-nNYWatxQW10z30V7YgV9Og==/base.apk"],nativeLibraryDirectories=[/data/user/0/com.sjl.soloader/app_libs/arm64-v8a, /data/app/com.sjl.soloader-nNYWatxQW10z30V7YgV9Og==/lib/arm64, /system/lib64, /system/vendor/lib64, /system/lib64, /system/vendor/lib64, /system/lib64, /system/vendor/lib64, /system/lib64, /system/vendor/lib64]]
拷贝和加载的部分代码:
private void loadSoFromAssetPathCopy() {
List out = new ArrayList<>();
try {
SoUtils.copyAssetsDirectory(this, "so", out);
} catch (Exception e) {
e.printStackTrace();
}
//arm64-v8a, armeabi-v7a, armeabi
Log.d(TAG, "supported api:" + Build.CPU_ABI + " " + Build.CPU_ABI2);
if (Build.VERSION.SDK_INT >= 21) {
String[] abis = Build.SUPPORTED_ABIS;
for (String abi : abis) {
if (loadSoFile(abi, out)) {
break;
}
}
} else {
if (TextUtils.isEmpty(Build.CPU_ABI)) {
if (loadSoFile(Build.CPU_ABI, out)) {
return;
}
}
if (TextUtils.isEmpty(Build.CPU_ABI2)) {
if (loadSoFile(Build.CPU_ABI2, out)) {
return;
}
}
if (loadSoFile("armeabi", out)) {
return;
}
}
}
private boolean loadSoFile(String abi, List out) {
boolean success = false;
for (final String soPath : out) {
if (soPath.contains(abi)) {
String parentDir = SoUtils.getParentDir(new File(soPath));
//注入so路径,如果清除了的话。没有清除可以不用每次注入
boolean b = SoLoader.loadSoFile(this, parentDir);
//加载so库
if (b && new File(soPath).exists()) {
final String soName = soPath.substring(soPath.lastIndexOf("/") + 1, soPath.lastIndexOf(".")).substring(3);
/* final String soName = soPath.substring(soPath.lastIndexOf("/") + 1, soPath.lastIndexOf(".")).substring(3);
System.loadLibrary(soName); //加载有可能直接崩掉
//System.load(soPath); //load使用的是文件绝对路径
*/
//or 使用第三方库加载,这个加载报错回调failure,不会直接崩溃,底层也是 System.load实现,只不过加载之前做了一些验证
ReLinker.loadLibrary(this, soName, new ReLinker.LoadListener() {
@Override
public void success() {
Log.i(TAG, "加载成功:" + soPath);
}
@Override
public void failure(Throwable t) {
Log.e(TAG, "加载异常:" + soPath, t);
}
});
success = true;
break;
}
}
}
return success;
}
在Android系统上,每一个CPU架构对应一个ABI:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64
一般系统都支持多种abi,假设系统CPU架构是arm64-v8a,只适配arm64-v8a可以大幅降低包体积。当我们指定加载arm64-v8a,我们可以在gradle下,指定过滤cpu架构,避免出现没必要的错误
ndk {
abiFilters "arm64-v8a"
}
这时候项目只剩下arm64-v8a(包括第三方库中的so),项目中那些可以动态下发的so库,可以不配置在项目中,直接从网络下载加载。
注意需要先加载后使用native方法。
https://blog.csdn.net/hujin2017/article/details/102804883