RePlugin插件化框架解析——Activity生命周期管理

插件化要解决的一个关键的技术点就是Activity生命周期管理的问题。我们知道,在Android中,所有的Activity都必须注册在AndroidManifest中。这是因为,在Activity的启动过程中,系统要经过校验,如果没有在AndroidManifest中注册,启动Activity就会失败。在插件化方案中,宿主和插件是独立开发的,宿主的AndroidManifest是整个App的AndroidManifest,而插件的AndroidManifest,系统是不认的,这样就导致了插件Activity无法在AndroidManifest中注册,从而启动时校验失败的问题。所以RePlugin插件化框架要解决的首要问题就是如果让插件中的Activity通过系统的验证,通过系统来实现Activity生命周期管理的问题。

RePlugin中“坑位Activity”替换“插件Activity”过程

RePlugin使用了“占坑”方案来解决Activity必须在AndroidManifest注册的问题。我们通过在宿主的AndroidManifest中,注册了各种task类型的Activity,作为“坑位”,在启动插件中的Activity过程中,会使用“坑位”Activity替换掉插件的Activity,这样就会通过系统的验证过程了。我们也就实现了狸猫换太子,用“坑位”Activity替换掉了“需要启动的插件的Activity”。

我们从startActivity来看这个过程。
在这里插入图片描述

  1. 在插件中,所有Activity都必须继承PluginActivity。

PluginActivity中的startActivity方法:

    @Override
    public void startActivity(Intent intent) {
        //
        if (RePluginInternal.startActivity(this, intent)) {
            // 这个地方不需要回调startActivityAfter,因为Factory2最终还是会回调回来,最终还是要走super.startActivity()
            return;
        }

        super.startActivity(intent);
    }

该方法首先调用RePluginInternal.startActivity(this, intent),如果成功,就返回,说明已经查找到相应的坑位了;如果失败,说明不需要坑位机制,直接交给系统启动即可。

调用RePluginInternal.startActivity(this, intent):

    /**
     * @param activity Activity上下文
     * @param intent
     * @return 插件机制层是否成功,例如没有插件存在、没有合适的Activity坑
     * @hide 内部方法,插件框架使用
     * 启动一个插件中的activity
     * 通过Extra参数IPluginManager.KEY_COMPATIBLE,IPluginManager.KEY_PLUGIN,IPluginManager.KEY_ACTIVITY,IPluginManager.KEY_PROCESS控制
     */
    public static boolean startActivity(Activity activity, Intent intent) {
        if (!RePluginFramework.mHostInitialized) {
            return false;
        }

        try {
            Object obj = ProxyRePluginInternalVar.startActivity.call(null, activity, intent); //反射调用
            if (obj != null) {
                return (Boolean) obj;
            }
        } catch (Exception e) {
            if (LogDebug.LOG) {
                e.printStackTrace();
            }
        }

        return false;
    }
    
    final String factory2 = "com.qihoo360.i.Factory2"; //Factory2类的路径

    startActivity = new MethodInvoker(classLoader, factory2, "startActivity", new Class<?>[]{Activity.class, Intent.class}); //反射Factory2的startActivity方法。


通过反射调用的方式,直接调用宿主中,插件化框架中的Factory2类的startActivity方法。这时,代码逻辑由插件,进入宿主。

  1. Factory2的startActivity方法:
    public static final boolean startActivity(Context context, Intent intent) {
        return sPLProxy.startActivity(context, intent);
    }
  1. 调用PluginLibraryInternalProxy的startActivity(Context context, Intent intent)方法:
    public boolean startActivity(Context context, Intent intent) {
        ……

        ……
        // 调用“特殊版”的startActivity,不让自动填写ComponentName,防止外界再用时出错
        return Factory.startActivityWithNoInjectCN(context, intent, plugin, name, process);
    }

该函数做了大量逻辑处理工作:

  • 兼容模式判断及处理。
  • 获取Activity名称。
  • 判断将要启动的Activity是否本身就是坑位Activity,如果是,直接返回false,直接由系统启动。
  • 获取插件名称。
  • 判断要启动的 Activity 是否是动态注册的类,如果是动态注册类,则返回false,直接由系统启动。
  • 再次检查插件名称,并尝试获取,如果最终获取失败,则返回false,直接由系统启动。
  • 获取进程值,看目标Activity要打开哪个进程。
  • 调用“特殊版”的startActivity,不让自动填写ComponentName,防止外界再用时出错。
  1. 调用Factory的startActivityWithNoInjectCN方法。
  2. 调用PluginCommImpl的startActivity方法,该方法又调回PluginLibraryInternalProxy类,并调用多参数的startActivity方法。
  3. PluginLibraryInternalProxy类的startActivity方法(多参):
    public boolean startActivity(Context context, Intent intent, String plugin, String activity, int process, boolean download) {
        ……
        // 缓存打开前的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);
        }
        context.startActivity(intent); //这里已经替换成坑位Activity了,接下来由系统启动即可。

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

        return true;
    }

该方法较长,主要执行内容如下:

  • 判断插件是否需要下载。
  • 检查是否是动态注册的类,如果要启动的 Activity 是动态注册的类,则不使用坑位机制,而是直接动态类。
  • 如果插件状态出现问题,则每次弹此插件的Activity都应提示无法使用,或提示升级(如有新版)。
  • 处理首次加载时的“大插件”相关逻辑。
  • 为Activity分配“坑位”Activity,并设置给intent。
  • 这时,intent中的Activity信息已经变为“坑位Activity”了,调用context.startActivity(intent),交由系统启动Activity即可。

到了这里,我们就实现了“坑位Activity”替换“插件Activity”,这样系统在启动Activity,执行后续校验逻辑时,会认为是已经在AndroidManifest中注册的“坑位Activity”在启动,校验过程就会顺利的通过。

Activity的校验过程不是本文的重点,我会在其他文章中进行分析。

到了这里,如果我们什么都不做,系统启动的Activity将是“坑位Activity”,这样对我们来说将没有意义,那么我们如何在启动前(通过系统校验之后),将“坑位Activity”替换回真正要启动的“插件Activity”呢?

“坑位Activity”替换回“插件Activity”

在“坑位Activity”替换“插件Activity”过程中,执行的所有操作,都是在宿主和插件中进行的,也就是他们都是在我们自己的APP端进行的。而Activity校验逻辑,是由ActivityManagerService来进行的,相对于我们的APP,ActivityManagerService就是服务端。我们使用“坑位Activity”在服务端通过“验证”之后,想要替换回我们真正想启动的“插件Activity”,有2个地方可以做替换:一个是在服务端处理完成之后,在服务端进行,这个难度太高,而且稳定性也不好,pass了;另一个地方就是回到APP端之后,再进行替换,RePlugin使用的就是这种方式。

我们来看具体过程。

hook点的选择

我们首先需要选择一个Hook点,在启动Activity之前,把“坑位Activity”替换回“插件Activity”。这个Hook点,RePlugin非常巧妙的使用了ClassLoader。

  1. 首先,我们通过宿主调用application.getBaseContext()获得Application的Context对象,它对应的实现类其实是ContextImpl;
  2. 通过ContextImpl的mPackageInfo属性,我们可以拿到一个LoadedApk类的实例对象;
  3. LoadedApk类对象的mClassLoader就是我们App类加载时所使用的ClassLoader对象;
  4. 我们在RePlugin插件化框架启动时,将LoadedApk类对象的mClassLoader,使用Hook方式,替换成我们自己的ClassLoader对象。

这样,我们就可以使用ClassLoader,在Activity对象加载时,将坑位Activity”替换回“插件Activity”了。

ClassLoader实现及Activity替换过程

在RePlugin中,RePluginClassLoader就是我们自定义的ClassLoader类,它必须继承自PathClassLoader类。

RePluginClassLoader的loadClass方法:

    @Override
    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        //
        Class<?> c = null;
        c = PMF.loadClass(className, resolve);
        if (c != null) {
            return c;
        }
        //
        try {
            c = mOrig.loadClass(className);
            // 只有开启“详细日志”才会输出,防止“刷屏”现象
            if (LogDebug.LOG && RePlugin.getConfig().isPrintDetailLog()) {
                LogDebug.d(TAG, "loadClass: load other class, cn=" + className);
            }
            return c;
        } catch (Throwable e) {
            //
        }
        //
        return super.loadClass(className, resolve);
    }

过程如下:

  1. 首先执行PMF的loadClass进行类加载,成功后直接返回。
  2. 如果失败,执行Hook之前的原生classloader对象的loadClass进行类加载,成功后返回。
  3. 如果失败,执行super的loadClass方法。

我们来看PMF的loadClass方法:

    public static final Class<?> loadClass(String className, boolean resolve) {
        return sPluginMgr.loadClass(className, resolve);
    }

转发调用PmBase类的loadClass方法:

    final Class<?> loadClass(String className, boolean resolve) {
        // 加载Service中介坑位
        ……
        if (mContainerActivities.contains(className)) {
            Class<?> c = mClient.resolveActivityClass(className); //将“坑位Activity”替换为目标“插件Activity”
            if (c != null) {
                return c;
            }
            ……
            return DummyActivity.class; //空Activity,防止崩溃
        }

        ……
        “坑位Service”解析
        ……
        坑位Provider”解析
        ……
        ……
        return loadDefaultClass(className);
    }

该方法实现:

  • 载Service中介坑位。
  • 将“坑位Activity”替换为目标“插件Activity”。
  • 将“坑位Service”替换为目标“插件Service”
  • 将“坑位Provider”替换为目标“插件Provider”
  • “动态类”、大插件等逻辑处理。

这里我们重点来看将“坑位Activity”替换为目标“插件Activity”的PluginProcessPer类的resolveActivityClass方法:

    final Class<?> resolveActivityClass(String container) {
        String plugin = null;
        String activity = null;

        // 先找登记的,如果找不到,则用forward activity
        PluginContainers.ActivityState state = mACM.lookupByContainer(container); //找到“坑位”对应的Activity信息
        if (state == null) {
            // PACM: loadActivityClass, not register, use forward activity, container=
            if (LOGR) {
                LogRelease.w(PLUGIN_TAG, "use f.a, c=" + container);
            }
            return ForwardActivity.class;
        }
        plugin = state.plugin;
        activity = state.activity; //获得“坑位Activity”所对应的目标“插件Activity”

        if (LOG) {
            LogDebug.d(PLUGIN_TAG, "PACM: loadActivityClass in=" + container + " target=" + activity + " plugin=" + plugin);
        }

        Plugin p = mPluginMgr.loadAppPlugin(plugin); //找到插件
        if (p == null) {
            // PACM: loadActivityClass, not found plugin
            if (LOGR) {
                LogRelease.e(PLUGIN_TAG, "load fail: c=" + container + " p=" + plugin + " t=" + activity);
            }
            return null;
        }

        ClassLoader cl = p.getClassLoader(); //找到插件的ClassLoader对象
        if (LOG) {
            LogDebug.d(PLUGIN_TAG, "PACM: loadActivityClass, plugin activity loader: in=" + container + " activity=" + activity);
        }
        Class<?> c = null;
        try {
            c = cl.loadClass(activity);
        } catch (Throwable e) {
            if (LOGR) {
                LogRelease.e(PLUGIN_TAG, e.getMessage(), e);
            }
        }
        if (LOG) {
            LogDebug.d(PLUGIN_TAG, "PACM: loadActivityClass, plugin activity loader: c=" + c + ", loader=" + cl);
        }

        return c;
    }

  • 根据“坑位Activity”获取目标“插件Activity信息”,存储在PluginContainers.ActivityState对象中。
  • 获得目标插件的对象实例。
  • 获得“坑位Activity”所对应的目标“插件Activity”
  • 获得目标插件的ClassLoader对象。
  • 通过目标插件的ClassLoader对象,加载目标“插件Activity”,并返回该实例。

至此,我们就完成了“坑位Activity”替换为目标“插件Activity”的过程。

小结

使用“坑位”方案,通过系统逻辑验证过程:

  • RePlugin使用了“占坑”方案来解决Activity必须在AndroidManifest注册的问题。
  • 在宿主的AndroidManifest中,注册了各种task类型的Activity,作为“坑位”。
  • 在插件中,所有Activity都必须继承PluginActivity。
  • 在“插件Activity”启动过程中,经过一系列处理,为“插件Activity”分配“坑位Activity”,并设置给intent。
  • 这时,intent中的Activity信息已经变为“坑位Activity”了,调用context.startActivity(intent),交由系统启动Activity。

“坑位Activity”替换为目标“插件Activity”的过程:

  • “坑位Activity”在通过系统“验证”之后,我们需要替换回我们真正想启动的“插件Activity”。
  • 我们在APP启动时,Hook宿主的ClassLoader。
  • 在系统加载到“坑位Activity”时,我们把“插件Activity”替换回来。
  • 插件Activity类加载过程,使用的是插件的ClassLoader对象。
发布了9 篇原创文章 · 获赞 0 · 访问量 3228

猜你喜欢

转载自blog.csdn.net/u011578734/article/details/100576528