Replugin startActivity流程

RePlugin.startActivity
    PmInternalImpl.startActivity
        PmLocalImpl.loadPluginActivity
            PmLocalImpl.getActivityInfo
            MP.startPluginProcess
            client.allocActivityContainer

下面是详细代码

// ** PmInternalImpl.startActivity **
// 这里先跳过非常规流程的代码,就是直接调用了PmLocalImpl.loadPluginActivity
public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {

    if (download) {
        // 下载流程,跳过
    }

    if (Factory2.isDynamicClass(plugin, activity)) {
        // 动态注册的类,跳过
    }

    // 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版)
    // Added by Jiongxuan Zhang
    if (PluginStatusController.getStatus(plugin) < PluginStatusController.STATUS_OK) {
        // ...
    }

    // 若为首次加载插件,且是“大插件”,则应异步加载,同时弹窗提示“加载中”
    // Added by Jiongxuan Zhang
    if (!RePlugin.isPluginDexExtracted(plugin)) {
        // ...
    }

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

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

    // ********* 关键方法,加载plugin并且分配坑位
    ComponentName cn = mPluginMgr.mLocal.loadPluginActivity(intent, plugin, activity, process);
    if (cn == null) {
        // ...
    }

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

    context.startActivity(intent);

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

    return true;
}
// ** PmLocalImpl.loadPluginActivity **
// 主要是下面标出来的3步
public ComponentName loadPluginActivity(Intent intent, String plugin, String activity, int process) {

    ActivityInfo ai = null;
    String container = null;
    PluginBinderInfo info = new PluginBinderInfo(PluginBinderInfo.ACTIVITY_REQUEST);

    try {
        // 1、获取ActivityInfo,期间会加载插件
        ai = getActivityInfo(plugin, activity, intent);
        if (ai == null) {
           // ...
        }

        // 存储此 Activity 在插件 Manifest 中声明主题到 Intent
        intent.putExtra(INTENT_KEY_THEME_ID, ai.theme);

        // 根据 activity 的 processName,选择进程 ID 标识
        if (ai.processName != null) {
            process = PluginClientHelper.getProcessInt(ai.processName);
        }

        // 2、容器选择(启动目标进程)
        IPluginClient client = MP.startPluginProcess(plugin, process, info); // 就是PluginProcessPer
        if (client == null) {
            return null;
        }

        // 3、远程分配坑位
        container = client.allocActivityContainer(plugin, process, ai.name, intent);
        // ...
    } catch (Throwable e) {
        // ...
    }

    PmBase.cleanIntentPluginParams(intent);
    return new ComponentName(IPC.getPackageName(), container);
}
第一步 加载插件
// ** PmLocalImpl.getActivityInfo **
// 主要是第一步:加载插件
public ActivityInfo getActivityInfo(String plugin, String activity, Intent intent) {
    // 1、获取插件对象
    Plugin p = mPluginMgr.loadAppPlugin(plugin);
    if (p == null) {
       // ...
    }

    ActivityInfo ai = null;

    // 2、activity 不为空时,从插件声明的 Activity 集合中查找
    if (!TextUtils.isEmpty(activity)) {
        ai = p.mLoader.mComponents.getActivity(activity);
    } else {
        // activity 为空时,根据 Intent 匹配
        ai = IntentMatcherHelper.getActivityInfo(mContext, plugin, intent);
    }
    return ai;
}
final boolean load(int load, boolean useCache) {
    PluginInfo info = mInfo;
    // 1、加载插件
    boolean rc = loadLocked(load, useCache);
    // 2、通过反射,调用插件的Application.onCreate
    if (load == LOAD_APP && rc) {
        callApp();
    }
    // 如果info改了,通知一下常驻
    // 只针对P-n的Type转化来处理,一定要通知,这样Framework_Version也会得到更新
    if (rc && mInfo != info) {
        UpdateInfoTask task = new UpdateInfoTask((PluginInfo) mInfo.clone());
        Tasks.post2Thread(task);
    }
    return rc;
}
// Plugin.doLoad
private final boolean doLoad(String tag, Context context, ClassLoader parent, IPluginManager manager, int load) {
    if (mLoader == null) {
        // 试图释放文件
        PluginInfo info = null;
        if (mInfo.getType() == PluginInfo.TYPE_BUILTIN) {
            // 1、这里,把插件拷贝到指定目录下,同时释放so到指定目录
            File dir = context.getDir(Constant.LOCAL_PLUGIN_SUB_DIR, 0);
            File dexdir = mInfo.getDexParentDir();
            String dstName = mInfo.getApkFile().getName();
            boolean rc = AssetsUtils.quickExtractTo(context, mInfo, dir.getAbsolutePath(), dstName, dexdir.getAbsolutePath());
            if (!rc) {
                // ...
            }
            File file = new File(dir, dstName);
            // 2、根据file名称构造pluginInfo的基本信息
            info = PluginInfo.build(file);
            if (info == null) {
                return false;
            }
            // 不会改变

        } else if (mInfo.getType() == PluginInfo.TYPE_PN_JAR) {
            // ...
        } else {
            // ...
        }

        //
        if (info != null) {
            // 替换
            mInfo = info;
        }

        // 3、构造plugin的Loader信息
        mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this);
        // 4、解析Dex,这里做的事情比较多,包括:
        // 1) 通过PackageManager解析到plugin的packageInfo,并替换sourceDir,publicSourceDir, nativeLibraryDir
        // 2) 通过第一步的packageInfo和解析manifest,缓存componentList;注册receiver(就是通过统一的receiver分发);调整process和taskAffinity
        // 3) 通过packageManager得到resource对象
        // 4) 构造classLoader,即PluginDexClassLoader
        // 5) 构造Context
        if (!mLoader.loadDex(parent, load)) {
            return false;
        }

        // 5、更新plugin对应的状态
        try {
            PluginManagerProxy.updateUsedIfNeeded(mInfo.getName(), true);
        } catch (RemoteException e) {
            // ...
        }

        // 6、加载plugin的Entry类,并调用其create方法;Entry还没弄明白,估计要看插件library的代码 *******
        if (load == LOAD_APP) {
            // NOTE Entry对象是可以在任何线程中被调用到
            if (!loadEntryLocked(manager)) {
                return false;
            }
            // ...
        }
    }

    if (load == LOAD_INFO) {
        return mLoader.isPackageInfoLoaded();
    } else if (load == LOAD_RESOURCES) {
        return mLoader.isResourcesLoaded();
    } else if (load == LOAD_DEX) {
        return mLoader.isDexLoaded();
    } else {
        return mLoader.isAppLoaded();
    }
}

到这里就做完了plugin加载,会到上面的getActivityInfo中,加载完插件之后,会通过解析到的ComponentList得到ActivityInfo。

然后,回到loadPluginActivity方法,看看第二步

第二步 启动目标进程

调用的是MP.startPluginProcess方法,最终是跨进程调用到了persist进程,PmHostSvc.startPluginProcess方法,最后调用PmBase.startPluginProcessLocked方法

// PmBase.startPluginProcessLocked
final IPluginClient startPluginProcessLocked(String plugin, int process, PluginBinderInfo info) {
    // 强制使用UI进程
    if (Constant.ENABLE_PLUGIN_ACTIVITY_AND_BINDER_RUN_IN_MAIN_UI_PROCESS) {
        if (info.request == PluginBinderInfo.ACTIVITY_REQUEST) {
            if (process == IPluginManager.PROCESS_AUTO) {
                process = IPluginManager.PROCESS_UI;
            }
        }
        // ...
    }

    // 1、这里执行CHECK任务,检查几个process record的状态,但是还没有看明白,在什么情况下会启动这样的进程
    // 执行方法在PluginProcessMain.attachProcessLocked,达到的条件比较苛刻
    PluginProcessMain.schedulePluginProcessLoop(PluginProcessMain.CHECK_STAGE1_DELAY);

    // 2、尝试获取client,因为这里是从主进程过来的,所以能匹配到主进程之前注册进来的client,也就是PluginProcessPer,
    // 存放在PluginProcessMain.ALL成员变量里的
    IPluginClient client = PluginProcessMain.probePluginClient(plugin, process, info);
    if (client != null) {
        return client;
    }

    // 3、获取失败,则尝试分配,这里最后的条件跟第1步里的一样,从PluginProcessMain.PROCESS中查找分配
    // 看代码逻辑还没都写完...,所以应该很少会走到这里
    int index = IPluginManager.PROCESS_AUTO;
    try {
        index = PluginProcessMain.allocProcess(plugin, process);
    } catch (Throwable e) {
        // ...
    }
    // 分配的坑位不属于UI、和自定义进程,就返回。
    if (!(index == IPluginManager.PROCESS_UI
            || PluginProcessHost.isCustomPluginProcess(index)
            || PluginManager.isPluginProcess(index))) {
        return null;
    }

    // 4、获取到Process index后,尝试启动,如果是主进程调用,在第2步应该就会结束;走到这里的是plugin自定义进程吗
    // 比如自定义了:process,个人感觉是;这里启动进程的方式是,通过ProcessPitProviderBase.insert(也就是ContentProvider)
    // 启动provider对应的进程,在insert方法里会调用PluginProviderStub.stubPlugin,这个方法会把新启动的进程的client注册
    // 到persist进程的PluginProcessMain.All中,这样下次在第2步就能找到了
    // 所以,为什么进程信息都保存在persist进程中呢,是因为可能有多个plugin的组件(如provider)运行在同一个进程里?个人觉得是这样
    boolean rc = PluginProviderStub.proxyStartPluginProcess(mContext, index);
    if (!rc) {
        return null;
    }

    // 5、再次获取,经过第4步,应该能获取到了
    client = PluginProcessMain.probePluginClient(plugin, process, info);
    if (client == null) {
        return null;
    }
    return client;
}
第三步 分配坑位

第二步获取到了activity对应进程的PluginProcessPer对象,第三步就调用PluginProcessPer.allocActivityContainer来分配坑位。

在初始化的时候,PluginProcessPer分配了多个map来对应不同的activity,这里的分配就是在map中找到合适的坑位,真正的分配放在了PluginContainers类中,看看它的实现

private final ActivityState allocLocked(ActivityInfo ai, HashMap<String, ActivityState> map,
                                        String plugin, String activity, Intent intent) {
    // 坑和状态的 map 为空
    if (map == null) {
        return null;
    }

    // 首先找上一个活的,或者已经注册的,避免多个坑到同一个activity的映射
    // 也就是找一个已经分配给activity的坑
    for (ActivityState state : map.values()) {
        if (state.isTarget(plugin, activity)) {
            return state;
        }
    }

    // 新分配:找空白的,第一个
    for (ActivityState state : map.values()) {
        if (state.state == STATE_NONE) {
            state.occupy(plugin, activity);
            return state;
        }
    }

    ActivityState found;

    // 重用:则找最老的那个
    found = null;
    for (ActivityState state : map.values()) {
        if (!state.hasRef()) {
            if (found == null) {
                found = state;
            } else if (state.timestamp < found.timestamp) {
                found = state;
            }
        }
    }
    if (found != null) {
        found.occupy(plugin, activity);
        return found;
    }

    // 强挤:最后一招,挤掉:最老的那个
    found = null;
    for (ActivityState state : map.values()) {
        if (found == null) {
            found = state;
        } else if (state.timestamp < found.timestamp) {
            found = state;
        }
    }
    if (found != null) {
        found.finishRefs();
        found.occupy(plugin, activity);
        return found;
    }

    // never reach here
    return null;
}

分配坑位的方法如上,很清晰。只是如果坑位不够了怎么办?后面classLoader会根据这里分配的坑位进行对真正的activity类进行加载,这个过程很快,那么是不是loadClass之后坑位的对应就没有用处了?好像是的。后续分析Replugin代码的时候继续思考这个问题。

==到这里,加载和分配坑位结束了,startActivity即可,走的是android正常的流程,之后在ActivityThread new activity的时候,因为hack了classLoader,所以会走RepluginClassLoader,也是在这里把坑位的类替换成了真正的类。下面看一看这个流程==

第四步 类加载
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
    //
    Class<?> c = null;
    // 1、Replugin框架loadClass
    c = PMF.loadClass(className, resolve);
    if (c != null) {
        return c;
    }
    //
    try {
        // 2、用原始的PathClassLoader来loadClass
        c = mOrig.loadClass(className);
        return c;
    } catch (Throwable e) {
        //
    }
    // 3、实际上没什么用,因为在构造函数里什么也没有给父类传
    return super.loadClass(className, resolve);
}

这里流程很清晰

PMF.loadClass最终调用到的就是PluginDexClassLoader.loadClass,它会加载插件里的类,如果没有找到类的话,会再调用RepluginClassLoader.loadClass尝试加载宿主的类,这里不会形成循环调用,因为调用关系是RepluginClassLoader.loadClass(坑位的类) ->PluginDexClassLoader.loadClass(坑位对应的activity类)->RepluginClassLoader.loadClass(坑位对应的activity类,这里不会再调用PluginDexClassLoader)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值