美文网首页程序员安卓博客Android技术知识
八个类搞定 Android 插件化—— Activity 终极化

八个类搞定 Android 插件化—— Activity 终极化

作者: 码农的书柜 | 来源:发表于2020-11-06 16:15 被阅读0次

    在 序文 【Android 插件化的过去 现在 未来】中简单的跟大家讲过现在开源社区中所有插件化的基本实现原理。
    从本文开始就带大家用最简单的办法实现一个插件化库。

    Activity 加载过程

    首先讲讲最主要的功能,Activity 的动态加载。查看源码我们知道

    • 每个Activity的启动过程都是通过startActivityForResult() 最终都会调用Instrument.execStartActivity()

    • 再由ActivityManagerNative.startActivity() 通过 IPC AMS所在进程,ActivityManagerService.startActivity()

    • 最后 ActivityStackSupervisor.startActivityLocked(),权限以及安全检查mService.checkPermission。我们的Activity如果不注册就会在这个检查时返回一个没有注册的错误,最后回到应用进程的时候抛出这个没注册的异常。

    • 安全校验完成以后,会调用ApplicationThread.scheduleLaunchActivity()

    app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
    System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
    r.compat, r.task.voiceInteractor, app.repProcState, r.icicle, r.persistentState,
    results, newIntents, andResume, mService.isNextTransitionForward(),
    profilerInfo);  
    
    //ApplicationThread.scheduleLaunchActivity中发送消息的部分(只有这部分是有用的)
    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
        Message msg = Message.obtain();
        msg.what = what;
        msg.obj = obj;
        msg.arg1 = arg1;
        msg.arg2 = arg2;
        if (async) {
            msg.setAsynchronous(true);
        }
        mH.sendMessage(msg);
    }
    

    顺带一说:上面app.thread.scheduleLaunchActivity()的第7个参数,task字段包含了一个ActivityStack,就是我们即将创建的Activity所在的ActivityStack,而如果是通过直接调用Context类的startActivity()方法;这种方式启动的Activity没有 Activity栈,因此不能以 standard 方式启动,必须加上FLAG_ACTIVITY_NEW_TASK这个 Flag 。而通常我们都是调用被Activity类重载过的startActivity()方法,这个是有 Stack 的。

    这一步让ApplicationThread做好跳转 activity 的准备(一些数据的封装),紧接着通过handle发送消息通知app.thread要进行Activity启动调度了,然后 app.thread接收到消息的时候才开始进行调度。

    • 这个message的接收是在ActivityThread中的handleMessage(Message msg)处理的。
    case LAUNCH_ACTIVITY: {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
        final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
    
        r.packageInfo = getPackageInfoNoCheck(
                r.activityInfo.applicationInfo, r.compatInfo);
        handleLaunchActivity(r, null);
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    
    
    • 这句中handleLaunchActivity()又调用了performLaunchActivity(r, customIntent); 而最终又调用了这句:
    java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(
            cl, component.getClassName(), r.intent);
    StrictMode.incrementExpectedActivityCount(activity.getClass());
    r.intent.setExtrasClassLoader(cl);
    
    

    兜了一圈又回到Instrumentation了。结果终于找到了可以hook的点了,就是这个mInstrumentation.newActivity()

    这一部分详细讲解可以查看:Android应用程序启动过程源代码分析
    Activity生命周期管理

    替换Activity加载过程

    知道了上面Activity启动过程,我们要做的就是通过替换掉Instrumentation类,达到定制插件运行环境的目的。

    // 先获取到当前的ActivityThread对象
    Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
    Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
    currentActivityThreadMethod.setAccessible(true);
    Object currentActivityThread = currentActivityThreadMethod.invoke(null);
    
    // 拿到原始的 mInstrumentation字段
    Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
    mInstrumentationField.setAccessible(true);
    Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
    
    //如果没有注入过,就执行替换
    if (!(mInstrumentation instanceof PluginInstrumentation)) {
        PluginInstrumentation pluginInstrumentation = new PluginInstrumentation(mInstrumentation);
        mInstrumentationField.set(currentActivityThread, pluginInstrumentation);
    }
    
    

    这样子就替换掉了系统的Instrumentation

    而在Instrumentation中,有一个方法叫newActivity()
    这个方法就是实际创建Activity的方法,它的返回值就是我们应用中实际使用的 activity。
    我们就可以在这里,判断到如果即将加载的 className 是一个插件中的Activity,那么就通过 ClassLoader.load(className).newInstance(); 创建插件 Activity 并返回来替换掉原本系统要创建的 Activity 了。

    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        if (intent != null) {
            isPlugin = intent.getBooleanExtra(HJPlugin.FLAG_ACTIVITY_FROM_PLUGIN, false);
        }
        if (isPlugin && intent != null) {
            className = intent.getStringExtra(HJPlugin.FLAG_ACTIVITY_CLASS_NAME);
        } else {
            isPlugin = HJPlugin.getInstance().getPluginAtySet().contains(className);
        }
        return super.newActivity(cl, className, intent);
    }
    
    

    插件的跳转支持

    如果仅仅是启动一个未安装的Activity,上面所做的事情已经足够了。但是如果我们需要从插件中启动另一个插件Activity,就需要多做一些事了。
    Activity启动时,会调用Instrumentation. execStartActivity()方法,我们所要做的就是重写这个方法,并且重新定义一个intent,来替换掉原本代码中的intent,这个替换的目的就是为了防止上文提到的ActivityStackSupervisor.startActivityLocked()安全校验,我们要把 intent 原本的setClass()方法传入的 class 给替换成一个合法的已经注册过的Activity(可以是任何一个,只要是注册过就行),接着将原本要启动的插件 Activity 类名作为一个字符串保存在Bundle里面,这样到我们的Instrumentation.newActivity()执行时判断如果是一个插件Activity,就不去创建 intent 传递的 Activity.class,而是创建Intent.Bundle里面保留的插件 Activity。

    /**
     * 覆盖掉原始Instrumentation类的对应方法,用于插件内部跳转Activity时适配
     *
     * @Override
     */
    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        replaceIntentTargetIfNeed(who, intent);
        try {
            // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity", Context.class, IBinder.class, IBinder.class,
                    Activity.class, Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(mBase, who,
                    contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("do not support!!!" + e.getMessage());
        }
    }
    
    

    结尾

    至此,通过替换掉系统的 Instrumentation,我们已经可以将 Activity 动态加载到应用中了。但是如果完整实现出来,还会有个问题,就是类可以完美执行,但是资源还不能加载进来,下章就讲资源的加载以及 so文件和 Service 的加载了。【8个类搞定插件化——Service实现方案

    PS:关于我


    本人是一个拥有6年开发经验的帅气Android攻城狮,记得看完点赞,养成习惯,微信搜一搜「 程序猿养成中心 」关注这个喜欢写干货的程序员。

    另外耗时两年整理收集的Android一线大厂面试完整考点PDF出炉,资料【完整版】已更新在我的【Github】,有面试需要的朋友们可以去参考参考,如果对你有帮助,可以点个Star哦!

    地址:【https://github.com/733gh/xiongfan】

    相关文章

      网友评论

        本文标题:八个类搞定 Android 插件化—— Activity 终极化

        本文链接:https://www.haomeiwen.com/subject/qhmtbktx.html