Replugin源码阅读-replugin-host-library

1.replugin-host-gralde:   宿主脚本
2.replugin-host-library:  宿主库
3.replugin-plugin-gradle: 插件脚本
4.replugin-plugin-library:插件库

打算从以上四个模块对replugin进行拆解阅读

RePlugin的思想是Hook ClassLoader, 因此接下来就围绕ClassLoader进行分析

一、流程总结

1.1 插件的安装
1.2 内置插件
1.3 外置插件
1.4 replugin-host-gradle
1.5 replugin-host-library
1.6 replugin-plugin-gradle
1.7 replugin-plugin-library
1.1 插件的安装

  RePlugin插件的安装并不会真正的处理插件APK中的dex、so库、资源等, 只是将插件移动到需要的位置, 这个位置默认是宿主的context.getFilesDir(), 然后将插件信息包装成Plugin对象并绑定宿主的Context、宿主ClassLoader、负责和宿主通信的PluginContextImpl类, 最后将Plugin对象存入插件管理进程统一管理.

1.2 内置插件

  内置插件的安装是在初始化时就自动安装和加载了

1.3 外置插件

  外置插件的安装需要调用Replugin.install()方法来安装插件, 这个过程和内置插件类似, 区别就是内置插件是通过assets目录下的json文件来生成插件对象, 外置插件则是通过获取插件apk的PackageInfo来生成插件对象, 但是并不会处理apk中的dex、so库、资源等, 只有当真正使用这个插件中的类时才会去真正的解析加载这个插件

1.4 replugin-host-gradle

  主程序使用的Gradle插件, 主要职责是在我们的主程序打包的过程中(编译的过程中)动态的修改AndroidManifest.xml的信息, 动态的生成占位各种Activity、provider和service的声明.
  其次还会动态生成一个HostBuildConfig的java类, 这个类是根据app下的build.gradle中配置的参数信息(repluginHostConfig)产生的, 这个类的路径在BuildConfig的同级目录的gen包下.
  然后会扫描内置插件目录assets/plugins目录, 解析插件文件生成包含文件名、包名、版本、路径的plugins-build.json文件, 这个文件的路径在assets目录.

1.5 replugin-host-library

  这个库是由主程序依赖的, 也是RePlugin的核心, 它的主要职责是初始化Replugin的整体框架, 整体框架使用了Binder机制来实现多进程直接的沟通和数据共享, 或者说是插件之间和宿主之间沟通和数据共享, hook住ClassLoader, 加载插件、启动插件、多插件的管理全部都由这个库辅助

1.6 replugin-plugin-gradle

  这个是插件工程使用的gradle插件, 这个库使用了Transform API和Javassist实现了编译期间动态修改字节码文件, 主要是替换插件工程中的Activity的继承全部替换成Replugin库中定义的XXXActivity, 动态的将插件APK中调用LocalBroadcastManager的地方修改为Replugin中的PluginLocalBroadcastManager调用, 动态修改ContentResolver和ContentProvider的调用修改成Replugin调用, 动态的修改插件工程中所有调用Resource.getIdentifier方法的地方, 将第三个参数修改为插件工程的包名

1.7 replugin-plugin-library

  这个库是由插件工程依赖的, 这个库的主要目的是通过反射的方式来使主程序中接口和功能, 这个库在主程序加载插件apk后悔进行初始化.

二、RePluginClassLoader初始化

2.1 ClassLoader
2.2 RePluginApplication初始化
2.3 PatchClassLoaderUtils.patch
2.4 创建RePluginClassLoader
2.1 ClassLoader

Android系统默认三个ClassLoader

1. BootClassLoader: 系统启动时创建, 一般不需要用到. 加载系统类
2. PathClassLoader: 应用启动时创建, 只能加载内部dex
3. DexClassLoader: 可以加载外部的dex

Replugin提供了两个ClassLoader
RePluginClassLoader: 宿主APP中的ClassLoader, 加载应用内部的Activity
PluginDexClassLoader: 加载插件的Loader.

public class RePluginClassLoader extends PathClassLoader;
public class PluginDexClassLoader extends DexClassLoader;
2.2 RePluginApplication初始化
调用链:
RepluginApplication.attachBaseContext -> RepluginApplication.App.attachBaseContext
                                      -> PMF.init()
                                      -> PatchClassLoaderUtils.patch()                                   
2.3 PatchClassLoaderUtils.patch
public static boolean patch(Application application) {
    //1.oBase指向ContextImpl: 具体结合LoadedApk.makeApplication()
    Context oBase = application.getBaseContext();
    // 2.反射获取ComTextImpl.mPackageInfo变量 -> LoadedApk
    Object oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo");
    // 3.反射获取LoadedApk.mClassLoader -> ClassLoader = PathClassLoader
    ClassLoader oClassLoader = (ClassLoader) ReflectUtils.readField(oPackageInfo, "mClassLoader");
    // 外界可自定义ClassLoader的实现,但一定要基于RePluginClassLoader类
    // 4.创建RePluginClassLoader
    ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader);
    // 将新的ClassLoader写入mPackageInfo.mClassLoader
    // 5.将新创建的RePluginClassLoader替换掉LoadedApk中的PathClassLoader
    ReflectUtils.writeField(oPackageInfo, "mClassLoader", cl);
    // 设置线程上下文中的ClassLoader为RePluginClassLoader
    // 防止在个别Java库用到了Thread.currentThread().getContextClassLoader()时,“用了原来的PathClassLoader”,或为空指针
    Thread.currentThread().setContextClassLoader(cl);
    return true;
}
2.4 创建RePluginClassLoader
RePlugin.getConfig().getCallbacks().createClassLoader:
/**
 * @param parent   该ClassLoader的父亲,通常为BootClassLoader
 * @param original 宿主的原ClassLoader,通常为PathClassLoader
 */
public RePluginClassLoader(ClassLoader parent, ClassLoader orig) {
    // 由于PathClassLoader在初始化时会做一些Dir的处理,所以这里必须要传一些内容进来
    // 但我们最终不用它,而是拷贝所有的Fields
    super("", "", parent);
    mOrig = orig;
    // 将原来宿主里的关键字段,拷贝到这个对象上,这样骗系统以为用的还是以前的东西(尤其是DexPathList)
    // 注意,这里用的是“浅拷贝”
    copyFromOriginal(orig);
    initMethods(orig);
}

三、加载插件

3.1 Replugin.startActivity
3.2 PluginLibraryInternalProxy.startActivity
3.1 Replugin.startActivity
// 调用链:
Replugin.startActivity() -> Factory.startActivityWithNoInjectCN()
                         -> PluginCommImpl.startActivity()
                         -> PluginLibraryInternalProxy.startActivity()
                                     
public static boolean startActivity(Context context, Intent intent) {
    // TODO 先用旧的开启Activity方案,以后再优化
    ComponentName cn = intent.getComponent();
    if (cn == null) {
        // TODO 需要支持Action方案
        return false;
    }
    String plugin = cn.getPackageName();
    String cls = cn.getClassName();
    return Factory.startActivityWithNoInjectCN(context, intent, plugin, cls, IPluginManager.PROCESS_AUTO);
}
3.2 PluginLibraryInternalProxy.startActivity
/**
 * 启动一个插件中的activity, 如果插件不存在会触发下载界面
 * @param context 应用上下文或Activity上下文
 * @param intent 
 * @param plugin 插件名
 * @param activity 待启动的activity类名
 * @param process 是否在指定进程中启动
 * @param download 下载
 * @return 插件机制是否成功, 例如没有插件存在, 没有合适的Activity坑
 */
public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
    // 是否启动下载
    // 若插件不可用(不存在或版本不匹配),则直接弹出“下载插件”对话框
    // 因为已经打开UpdateActivity,故在这里返回True,告诉外界已经打开,无需处理
    if (download) {
        if (PluginTable.getPluginInfo(plugin) == null) {
            // 如果用户在下载即将完成时突然点按“取消”,则有可能出现插件已下载成功,但没有及时加载进来的情况
            // 因此我们会判断这种情况,如果是,则重新加载一次即可,反之则提示用户下载
            // 原因:“取消”会触发Task.release方法,最终调用mDownloadTask.destroy,导致“下载服务”的Receiver被注销,即使文件下载了也没有回调回来
            // NOTE isNeedToDownload方法会调用pluginDownloaded再次尝试加载
            if (isNeedToDownload(context, plugin)) {
                return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
            }
        }
    }
    /* 检查是否是动态注册的类 */
    // 如果要启动的 Activity 是动态注册的类,则不使用坑位机制,而是直接动态类。
    // 原因:宿主的某些动态注册的类不能运行在坑位中(如'桌面'插件的入口Activity)
    if (Factory2.isDynamicClass(plugin, activity)) {
        intent.putExtra(IPluginManager.KEY_COMPATIBLE, true);
        intent.setComponent(new ComponentName(IPC.getPackageName(), activity));
        context.startActivity(intent);
        return true;
    }
    // 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版)
    if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) {
        return RePlugin.getConfig().getCallbacks().onPluginNotExistsForActivity(context, plugin, intent, process);
    }
    // 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中”
        // Added by Jiongxuan Zhang
        if (!RePlugin.isPluginDexExtracted(plugin)) {
            PluginDesc pd = PluginDesc.get(plugin);
            if (pd != null && pd.isLarge()) {
                if (LOG) {
                    LogDebug.d(PLUGIN_TAG, "PM.startActivity(): Large Plugin! p=" + plugin);
                }
                return RePlugin.getConfig().getCallbacks().onLoadLargePluginForActivity(context, plugin, intent, process);
            }
        }

        // WARNING:千万不要修改intent内容,尤其不要修改其ComponentName
        // 因为一旦分配坑位有误(或压根不是插件Activity),则外界还需要原封不动的startActivity到系统中
        // 可防止出现“本来要打开宿主,结果被改成插件”,进而无法打开宿主Activity的问题

        // 缓存打开前的Intent对象,里面将包括Action等内容
        Intent from = new Intent(intent);

        // 帮助填写打开前的Intent的ComponentName信息(如有。没有的情况如直接通过Action打开等)
        if (!TextUtils.isEmpty(plugin) && !TextUtils.isEmpty(activity)) {
            from.setComponent(new ComponentName(plugin, activity));
        }

        ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);
        if (cn == null) {
            if (LOG) {
                LogDebug.d(PLUGIN_TAG, "plugin cn not found: intent=" + intent + " plugin=" + plugin + " activity=" + activity + " process=" + process);
            }
            return false;
        }

        // 将Intent指向到“坑位”。这样:
        // from:插件原Intent
        // to:坑位Intent
        intent.setComponent(cn);

        if (LOG) {
            LogDebug.d(PLUGIN_TAG, "start activity: real intent=" + intent);
        }

//        if (RePluginInternal.FOR_DEV) {
//            try {
//                String str = cn.getPackageName() + "/" + cn.getClassName();
//                if (LOG) {
//                    LogDebug.d(PLUGIN_TAG, "str=" + str);
//                }
//                new ProcessBuilder().command("am", "start", "-D", "--user", "0", "-n", str).start();
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//        } else {

        context.startActivity(intent);

        // 通知外界,已准备好要打开Activity了
        // 其中:from为要打开的插件的Intent,to为坑位Intent
        RePlugin.getConfig().getEventCallbacks().onPrepareStartPitActivity(context, from, intent);

        return true;
    }

你可能感兴趣的:(Replugin源码阅读-replugin-host-library)