什么是动态加载?
- 简单来说动态加载可以加载原本apk不存在的代码。
什么场景适用?
- 很多场景都适用,比如可以实现app无感更新,不过有些应用市场对含有动态更新功能的app抓得比较严,因为担心你偷偷更新了做些坏事。
动态加载的难点是哪里?
如何动态加载res文件
- 如果只是做动态加载java代码,则只需要加载dex就行了,简单方便
- 如果要动态加载的内容包括res资源,比如布局,资源图片。有两种方案可选:
- 布局采用java代码编写,资源图片放到assets中,这样assets可以打包到jar包中,这样就可以只加载dex,无需加载res,但是采用java代码开发res较为耗时不方便,若是只有少量简单布局及资源图片可能采用此方法
- 布局及资源文件等采用res的xml编写,需要避免资源的id与宿主apk的id冲突以及如何将动态加载的Resources注入到宿主Activity的Resources中,这样就能够通过宿主的
context.getResources().getIdentifier
接口获取到资源id了
动态更新需要怎么做?
加载dex
如何生成dex?
- 将java代码打成jar包,再用dex2jar工具将jar打成dex
- 需要注意,要将R.class文件也打包进入dex中,需要通过此文件关联res资源,若无res资源可忽略
如何加载dex?
- 通过DexClassLoader加载dex文件实例出ClassLoader
- 通过ClassLoader.loadClass("xx.xx.Xxx")
- 再通过反射的方式调用具体的方法
加载res
res打包成什么格式?
- apk格式,可以在在需要动态加载的项目中,生成成apk后用压缩工具打开,将里面的dex删除即可
打包res需要注意什么?
- 需要注意其中的资源id,修改其id段,避免与资源id冲突,需要在gradle中配置
修改为package-id为0x70段的id, 默认为0x7f,修改的id段不能大于0x7f段,否则会出问题,此方法只有在compileSdkVersion为28及以上才生效,低于28的可以在网上寻找修改aapt源码并编译出新的功能用于修改package-id
android {
aaptOptions {
additionalParameters '--allow-reserved-package-id','--package-id','0x70'
}
}
如何加载res?
- 在成功加载dex代码后,在加载dex运行的第一时间进行res的加载,或者说是将res注入到宿主中
try {
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
String resFilePath = resFile(context).getAbsolutePath();
String baseApkPath = baseApkPath(context);
if (!TextUtils.isEmpty(baseApkPath)) {
LOG.i("重设原生资源--->" + baseApkPath);
addAssetPath.invoke(assetManager, baseApkPath);
}
LOG.i("设置外置资源resFilePath--->" + resFilePath);
addAssetPath.invoke(assetManager, resFilePath);
mResources =
new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources()
.getConfiguration());
mAssetManager = mResources.getAssets();
} catch (Exception ignored) {
ignored.printStackTrace();
}
public String baseApkPath(Context context) {
try {
String s = context.getApplicationContext().getPackageResourcePath();
if (s == null){
Process process = Runtime.getRuntime().exec("pm path " + context.getPackageName());
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
s = reader.readLine();
LOG.i("getRuntime baseApkPath",s);
}
LOG.i("getApplication baseApkPath",s);
return s.substring(s.indexOf("/"));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
加载so
如何加载so?
- 实例化DexClassLoader将so所在的路径传入即可自动搜索加载
DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent)``librarySearchPath
即为so所以的路径,此路径需要为当前包所在的路径data/data/package/
下,要注意的是,so会根据cpu的架构不同而有不同的文件,所以我们在选择so时要先根据Build.CPU_ABI
判断cpu的架构选择哪个so文件。
奈何表达能力太差,下次写个详细的,下次一定!
网友评论