前面谈到了Hook技术,那么今天来说说Hook的应用场景之一插件化开发技术。
为什么要插件化?插件化的好处有什么?
为了业务灵活解耦,减小apk的体积,方便各业务开发管理以及更新维护,同时也为了避免65535问题,我们需要插件化。
那么插件化是怎么的一个流程?
首先,用户会安装我们的app,这里叫做宿主apk,然后当用户点击使用某个业务功能时,我们就可以从我们的服务器端下载对应的插件apk,下载完之后加载进去,就可以跳转业务功能页面了。大体如下图
了解插件化目的和流程之后,接下来就是我们的重点——技术实现原理
重点
虽然市面上有很多插件化框架,如VirtualAPK,但是这里我们讲讲实现原理。分加载和页面跳转两部分讲。
一、加载文件
从服务器下载到存储插件apk这里不再赘述,主要讲加载到宿主里。我们知道app安装启动这过程就会把相应文件加载到虚拟机,所以我们要处理的就是把插件也加载到我们的宿主虚拟机,达到文件可用。因为我们打开activity页面主要涉及到class类和布局或图片等文件,所以这里我主要以class文件和资源文件作描述。
public class PluginManager {
private static PluginManager instance;
private Context context;
private DexClassLoader classLoader;
private Resources resources;
public static PluginManager getInstance(){
if (instance == null){
instance = new PluginManager();
}
return instance;
}
/**
* 加载插件apk
* @param path 插件apk路径
*/
public void loadPlugin(String path){
File dexOutFile = context.getDir("dex",Context.MODE_PRIVATE);
if (Build.VERSION.SDK_INT> Build.VERSION_CODES.CUPCAKE) {
//初始化classLoader
classLoader = new DexClassLoader(path, dexOutFile.getAbsolutePath(), null, context.getClassLoader());
}
try {
//初始化assetManager
AssetManager assetManager = AssetManager.class.newInstance();
Method method = AssetManager.class.getMethod("addAssetPath",String.class);
method.invoke(assetManager,path);
//初始化resources
resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
}catch (InstantiationException ine){
}catch (IllegalAccessException ile){
}catch (NoSuchMethodException noe){
}catch (InvocationTargetException inve){
}
}
public DexClassLoader getClassLoader() {
return classLoader;
}
public Resources getResources() {
return resources;
}
public void setContext(Context context) {
this.context = context.getApplicationContext();
}
}
这样我们就基本完成了插件加入宿主虚拟机的过程,然后我们只要把这里的classLoader和resources供插件及加载插件class类使用就行了(具体如何设计架构这里不细说),接下来我们看实现页面跳转的方案
二、打开插件Activity
如何打卡没有在Manifest.xml文件种注册的Activity网上有很多资料,大致思路相似,都是通过Hook技术,“占坑”然后替换的方式,这里也不再赘述。这再跟大家分享另一种方式,不用Hook技术,我使用一个“代理”Activity,然后再把生命周期方法“嫁接”到目标Activity上。以下是“代理”Activity的实现
/**
* 代理activity
*/
public class ProxyActivity extends Activity {
private ActivityInterface activityInterface;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//接收到目标activity
String className = getIntent().getStringExtra("className");
try {
//实例化目标activity并开始生命周期
Class activityClass = getClassLoader().loadClass(className);
Constructor activityConstructor = activityClass.getConstructor(new Class[]{});
Object obj = activityConstructor.newInstance(new Object[]{});
activityInterface = (ActivityInterface) obj;
activityInterface.onCreate(savedInstanceState);
}catch (ClassNotFoundException ce){
}catch (NoSuchMethodException ne){
}catch (InvocationTargetException ive){
}catch (InstantiationException inse){
}catch (IllegalAccessException ile){
}
}
@Override
protected void onResume() {
super.onResume();
activityInterface.onResume();
}
@Override
protected void onStop() {
super.onStop();
activityInterface.onStop();
}
@Override
public ClassLoader getClassLoader() {
return PluginManager.getInstance().getClassLoader();
}
@Override
public Resources getResources() {
return PluginManager.getInstance().getResources();
}
}
需要注意的是在插件的开发过程中必须使用宿主的上下文、resource等,所以要把这些对象传到插件中引用,具体实现这里不再赘述。如Service,BroadcastReceiver等其他组件类似,到此插件开发讲解结束。