移动 前端 Python Android Java

基于类加载器的“插件”架构

2019-08-14  本文已影响0人  zcwfeng

用一个例子来说明DexClassLoader 的使用方法 ,假设有两个APK,第一个叫Host端(个人测试AndroidFace),一个Plugin端(个人测试Demo)。在Plugin 端,定义一个类PluginClass ,该类定义一个函数叫 function1.

    package com.zcwfeng.demo;
    import android.util.Log
    public class PluginClass {
        public PluginClass() {
            Log.e("PluginClass","PluginClass client initialied");
        }
        public int function1(int a,int b){
     return a + b;
     }
    }

接下来,我们的做法是,在Host端AndroidFace中使用DexClassLoader 动态装载PluginClass类,并调用其function1函数
新建Host工程AndroidFace,在主Activity中调用如下代码

// 测试调用DexClassLoader
public void useDexClassLoader(){
    Intent intent = new Intent("com.zcwfeng.demo",null);
    intent.setComponent(new ComponentName("com.zcwfeng.demo","com.zcwfeng.demo.view.MainActivity"));


    PackageManager pm = getPackageManager();
    final List<ResolveInfo> plugins = pm.queryIntentActivities(intent,0);
    Log.e("Host","plugins size:" + plugins.size());
    if(plugins.size() > 0) {
        ResolveInfo rinfo = plugins.get(0);
        ActivityInfo ainfo = rinfo.activityInfo;

        String div = System.getProperty("path.separator");
        String packageName = ainfo.packageName;
        String dexPath = ainfo.applicationInfo.sourceDir;
        String dexOutputDir = getApplicationInfo().dataDir;
        String libPath = ainfo.applicationInfo.nativeLibraryDir;

        DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDir,libPath,this.getClass().getClassLoader());
        try {
            Class<?> clazz = cl.loadClass(packageName + ".PluginClass");
            Object obj = clazz.newInstance();
            Class[] params = new Class[2];
            params[0] = Integer.TYPE;
            params[1] = Integer.TYPE;
            Method action = clazz.getMethod("function1",params);
            Integer ret = (Integer) action.invoke(obj,12,34);
            Log.i("Host","return value is " + ret);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

解释依稀DexClassLoader 的参数

代码最终输出:
AndroidFace host端

2019-08-14 21:07:18.061 7134-7134/com.zcwfeng.face I/Host: return value is 46

Demo client端

2019-08-14 21:07:18.061 7134-7134/com.zcwfeng.face I/PluginClass: PluginClass client initialied

基于类装载器实际一种 “插件” 架构

改进上面的结构,定义interface 接口,interface 仅仅定义函数的输入和输出,不定义具体实现。该接口存在Host和Plugin两个项目,也就是该类同事参与连个项目的编译

添加Comm接口

package com.zcwfeng.demo;
public interface Comm {
    public int function1(int a,int b);
}

Plugin 不需要改动,只要在实现类实现Comm接口即可

    DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDir,libPath,this.getClass().getClassLoader());
        try {
            Class<?> clazz = cl.loadClass(packageName + ".PluginClass");
            Comm comm = (Comm)clazz.newInstance();
            Integer ret = (Integer) comm.function1(12,34);
            Log.i("Host","return value is " + ret);


        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

Host 可以去掉动态Method反射可以提高效率,减少代码量

一般的做法,我们会把公共接口打包成jar。

这样宿主程序就可以查看PackageManager类queryIntentActivities()查询相关插件列表了。为了保证宿主程序和插件的兼容性,插件要定义一些版本号和相关名称信息。可以放在插件Plugin的res目录中,比如插件成续res/values/string.xml 定义相关信息。

        Resources res = pm.getResourcesForApplication(packageName);
        int id = 0;
        id = getResources().getIdentifier("version","string",packageName);
        String version = res.getString(id);

总结: 想一下这种结构其实挺巧妙,很灵活。
有两种思路:
第一种,系统主题架构。Android中的theme概念并不是真正的系统主题,所有的系统图标可以通过一种主题文件进行切换,比如状态栏上的图标,各种UI控件的图标桌面等。设计思路就是把每一种主题文件作为一个插件,在系统设置中可以使用不同的插件作为当前主题,然后修改Framework中读取Resources资源相关代码,达到切换的目的

第二种,AppStore架构。标准的Android电子商店程序只能连接Google的服务,但我们很多厂商希望提供独立的应用商店,如果让用户安装多个商店客户端显然是比较麻烦的。因此可以设计一个通用电子商店客户端,并定义一种标准的和电子商店服务器通信的机制。把这种机制定义为一个或多个interface文件,不同厂商只要实现这些interface即可。然后把实现后的程序作为插件安装到手机中,从而让用户选择不同的插件,而不再需要安装多个客户端。

上一篇 下一篇

猜你喜欢

热点阅读