一, 原理(以浏览器为例)

本质

1, 主程序直接加载插件的Extension类(这样他们就在同一个进程里了,就相当于主程序和Extension合为一个新的大app),通过接口来交互。

2, 插件Extension需要实现接口IExt

接口包括(部分):

/* for browser activity */
    public void onBrowserCreate(Bundle bundle);
    public void onBrowserResume();
    public void onBrowserPause();
    public void onBrowserDestory();

3, 主程序需要实现IBrowserActivity接口

接口包括(部分):

public boolean showToast(String pkgname, String content);
    public boolean showProgressDialog(String pkgname,
            PopupProgressDialogParams params);
    public void dismissProgressDialog(String pkgname);
                 
    public IPopupProgressDialog getProgressDialog(String pkgname);
                 
    public boolean showPopupDialog(String pkgname, PopupDialogParams params);
    public void dismissPopupDialog(String pkgname);


4, 通过一个类BrowserActivityImpl来执行,它持有主程序的Context,保存各个插件的各种数据(每个插件的Extension)。设计上BrowserActivity是单例模式,且为弱引用。


架构的示意图如下:


流程示例:

1, 主程序的onCreate()方法如何传给Addon

1-1 在主程序Activity的onCreate中,获得BrowserActivityImpl的实例

调用实例的traverseCreateActivityCallback()方法

1-2 在BrowserActivityImpl对象持有每个插件的Extension 的实例。

在此,会调用Extension中的onCreate()方法。

BrowserActivityImpl


2, Addon如何让主程序显示PopupDialog

1-1 初始化

主程序启动,Addon Extension获得BrowserActivityImpl的对象mBrowserActivityImpl

1-2 调用

Addon中,mBrowserActivity调用方法:

mBrowserActivityImpl.showProgressDialog(PKGNAME, mLoadingParams);


二,要点


1, 插件Extension类的加载

比较2.3和4.1的源码,Classloader和PathClassloader之间有所不同。

所以分别处理。4.1中,PathClassloader可以直接加载class,2.3中,通过load一个错误的class name,让PassClassloader获得的mDex文件的数据,然后通过mdex文件,加载类,相关源码,不给出,请自行查看。给出实现代码供参考:

public class ExtClassLoader extends ClassLoader {
    private static Field dexField;
    private DexFile[] mDexs;
    private final ClassLoader mLoader;
    private static boolean ICE_OR_ABOVE = Build.VERSION.SDK_INT >= 14;
    static {
        if(!ICE_OR_ABOVE){
            Class<PathClassLoader> classLoader = PathClassLoader.class;
            try {
                dexField = classLoader.getDeclaredField("mDexs");
                dexField.setAccessible(true);
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
    }
    public ExtClassLoader(Context base,PackageInfo pi, String extPkg,
            ClassLoader mainClassLoader) {
        super(mainClassLoader);
        Context extContext = null;
        try {
            extContext = base.createPackageContext(
                    extPkg,
                    Context.CONTEXT_INCLUDE_CODE
                    | Context.CONTEXT_IGNORE_SECURITY);
        } catch (NameNotFoundException e1) {
            e1.printStackTrace();
        }
                  
        if(ICE_OR_ABOVE){
            String codePath = extContext.getPackageCodePath();
            String dir = pi.applicationInfo.nativeLibraryDir;
            mLoader = new PathClassLoader(codePath, dir, mainClassLoader);
        }else{
            mLoader = extContext.getClassLoader();
        }
    }
              
    @Override
    protected Class<?> loadClass(String className, boolean arg1)
            throws ClassNotFoundException {
        if(ICE_OR_ABOVE)
            return super.loadClass(className, arg1);
        Class<?> c = findLoadedClass(className);
        if (c != null)
            return c;
        try {
            c = getParent().loadClass(className);
            if (c == null)
                c = findClass(className);
        } catch (ClassNotFoundException e) {
            c = findClass(className);
        }
        return c;
    }
              
    @Override
    protected Class<?> findClass(String className)
            throws ClassNotFoundException {
        if(mLoader == null)
            return null;
        if(ICE_OR_ABOVE)
            return mLoader.loadClass(className);
        Class<?> c = null;
        if (mLoader instanceof PathClassLoader)
            c = loadClassFromDexFile(className);
        else
            c = mLoader.loadClass(className);
        return c;
    }
    private Class<?> loadClassFromDexFile(String className) {
        DexFile[] dfs = getDexFiles();
        if (dfs != null) {
            for (int i = 0; i < dfs.length; i++) {
                if (dfs[i] != null) {
                    Class<?> c = dfs[i].loadClass(className, this);
                    if (c != null)
                        return c;
                }
            }
        }
        return null;
    }
    private DexFile[] getDexFiles() {
        if (mDexs != null)
            return mDexs;
        try {
            mLoader.loadClass("all.money.go.my.home");
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(ExtMgrConstants.LOGTAG, "getDexFiles " + e.toString());
        }
        try {
            mDexs = (DexFile[]) dexField.get(mLoader);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return mDexs;
    }
}

2, 进程的处理

根据以上的说明,即Addon的Extension类与主程序属于同一进程。但是Addon的其他类,则不属于主程序进程。

2-1 两个进程之间切换,需要sync

2-2 如果使用SharedPreference,需要使用多进程的构造方法。

2-3 友盟统计,如果要单独统计为Addon的数据。需要从Extension单起一个service来统计,因为Extension在主程序的进程中,统计的数据会到主程序。


三:项目

插件是特定应用(例如浏览器)提供的开放api的实现。开发者主要关注的是功能, 交互和UI。当然,需要注意第二部分给出的要点。


我参与了两个插件的开发,中间隔了很长的时间,会觉得再次进入需要一些熟悉的时间。觉得可以优化的地方,就是给出开发步骤文档。一些记忆性质的code flow可以避免。开发效率可以提高。