插件化技术

一.概述

Android插件化技术,可以实现功能模块的按需加载和动态更新,其本质是动态加载未安装的apk。分为宿主apk和插件apk:
(1)所谓宿主,就是需要能提供运行环境,给资源调用提供上下文环境,一般也就是我们主 APK ,要运行的应用,它作为应用的主工程所在,实现了一套插件的加载和管理的框架,插件都是依托于宿主的APK而存在的。
(2)所谓插件,可以想象成每个独立的功能模块封装为一个小的 APK ,可以通过在线配置和更新实现插件 APK 在宿主 APK 中的上线和下线,以及动态更新等功能。

在这里插入图片描述

二.原理

插件化要解决的三个核心问题:类加载、组件生命周期管理、资源加载

三.好处

(1) 让用户不用重新安装APK 就能升级应用功能,减少发版本频率,增加用户体验。
(2) 提供一种快速修复线上 BUG 和更新的能力。
(3) 按需加载不同的模块,实现灵活的功能配置,减少服务器对旧版本接口兼容压力。
(4)模块化、解耦合、并行开发、 65535 问题。

四.插件化涉及到的技术以及常用的插件化框架

  • 反射机制
  • 类加载过程
  • activity的启动流程
  • 资源文件的加载流程
  • hook技术
  • 代理模式:动态代理静态代理

五.详细说明

1.第一个问题:类加载

(1)Android 项目中,动态加载技术按照加载的可执行文件的不同大致可以分为两种:

a。动态加载 .so库(c/c++通过jni技术调用)
b. 动态加载 dex/jar/apk文件(现在动态加载普遍说的是这种)

(2)在 Android 中的 ClassLoader 机制主要用来加载 dex 文件,系统提供了两个 API 可供选择:

a. PathClassLoader:只能加载已经安装到 Android 系统中的 APK 文件。因此不符合插件化的需求,不作考虑。
b. DexClassLoader:支持加载外部的 APK、Jar 或者 dex 文件,正好符合文件化的需求,所有的插件化方案都是使用 DexClassloader 来加载插件 APK 中的 .class文件的
在这里插入图片描述

(3)插件化类加载原理

①通过DexClassLoader加载插件apk中的文件,通过反射技术获得dexElements
②通过PathClassLoader获得已经加载宿主apk中的dexElements,同样利用反射
③将1、2步获得dexElements数组合并成,并将新数组通过反射为PathClassLoader的dexElements重新赋值

在这里插入图片描述

(3)核心代码,未适配

private void loadApk() throws NoSuchFieldException, IllegalAccessException {
        //0。插件apk位置以及缓存位置
        String pluginStr = mContext.getExternalFilesDir(null).getAbsolutePath()+"/pluginapp-debug.apk";
        String cache_plugin = mContext.getDir("cache_plugin", Context.MODE_PRIVATE).getAbsolutePath();
        //1:通过DexClassLoader获得插件apk中dexElements
        DexClassLoader dexClassLoader = new DexClassLoader(pluginStr,cache_plugin,null,
                mContext.getClassLoader());
        Class<?> superclass = dexClassLoader.getClass().getSuperclass();
        Field pathListField = superclass.getDeclaredField("pathList");
        pathListField.setAccessible(true);
        Object pathListObject = pathListField.get(dexClassLoader);
        Field dexElementsField = pathListObject.getClass().getDeclaredField("dexElements");
        dexElementsField.setAccessible(true);
        Object dexElementsObject = dexElementsField.get(pathListObject);
        //2:通过pathClassLoader获得宿主dexElements
        ClassLoader pathClassLoader =mContext.getClassLoader();
        Object hostPathListObject = pathListField.get(pathClassLoader);
        Object hostDexElementsObject = dexElementsField.get(hostPathListObject);
        //3:合并
        int pluginLength = Array.getLength(dexElementsObject);
        int hostLength = Array.getLength(hostDexElementsObject);
        int new_dexElementsLength = pluginLength + hostLength;
        Object newDexElements = Array.newInstance(hostDexElementsObject.getClass().getComponentType(),
                hostLength + pluginLength);
        for (int i = 0; i < new_dexElementsLength; i++) {
            if (i < pluginLength) {
                Array.set(newDexElements, i, Array.get(dexElementsObject, i));
            } else {
                Array.set(newDexElements, i, Array.get(hostDexElementsObject, i - pluginLength));
            }
        }
        //4.最后为类加载器通过反射将新的数组设置回pathClassLoader
        dexElementsField.set(hostPathListObject,newDexElements);
    }

2.第二个问题:组件生命周期管理

(1)问题描述

插件中有activity,通过第一步类已经加载进宿主app中,但是清单文件中未注册该activity。想要在宿主app的activity页面点击跳转到插件app中的activity页面,就会有问题,提示未注册该activity

(2)hook技术

钩子,勾住系统的程序逻辑,在某段SDK源码逻辑执行的过程中,通过代码手段(其实就是反射)拦截执行该逻辑,加入自己的代码逻辑。

在这里插入图片描述

(3)activity的启动过程如何偷梁换柱

在这里插入图片描述
activity1跳转到activity2过程中app和AMS交互2次
①第一次app和AMS通信,AMS会检查activity2是否在清单文件中注册,所以说我们要使用hook技术将要跳转的activity2换成已经在宿主清单文件中注册的RegisterActivity,越过AMS检查;通过hook AMS 实现
②第二次AMS和app通信,可以启动activity2,我们需要使用hook技术将activity2替换回去;通过hook handler 实现

在这里插入图片描述

(4)hook AMS

①思路源码解析

在这里插入图片描述

②核心代码(未进行适配)

 /**
     * hook AMS对象
     * 对AMS对象的startActivity方法拦截
     */
    public static void hookAms(Context context) throws NoSuchFieldException, ClassNotFoundException, IllegalAccessException {
        //1.获得AMS对象
        //1.1获得静态属性IActivityManagerSingleton
        Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
        Field iActivityManagerSingletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
        iActivityManagerSingletonField.setAccessible(true);
        Object iActivityManagerSingletonObject = iActivityManagerSingletonField.get(null);//静态变量通过null直接获取
        //1.2获得Single的mInstance属性值
        Class<?> singletonClazz = Class.forName("android.util.Singleton");
        Field mInstanceField = singletonClazz.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);
        Object AMSSubject = mInstanceField.get(iActivityManagerSingletonObject);//这就是AMS对象
        //2.对AMS对象进行代理动态代理:代理对象和被代理对象同一个接口IActivityManager
        Class<?> IActivityManagerInterface = Class.forName("android.app.IActivityManager");
        AMSInvocationHandler handler = new AMSInvocationHandler(context, AMSSubject);
        Object AMSProxy = Proxy.newProxyInstance(//动态代理,交给AMSInvocationHandler处理
                Thread.currentThread().getContextClassLoader(),
                new Class[]{IActivityManagerInterface},
                handler
        );
        mInstanceField.set(iActivityManagerSingletonObject,AMSProxy);
        //3.InvocationHandler对AMS对象的方法进行拦截
    }

动态代理拦截,此处偷梁换柱

/**
 * @Author : yaotianxue
 * @Time : On 2023/6/6 08:27
 * @Description : AMSInvocationHandler
 */
public class AMSInvocationHandler implements InvocationHandler {
    private Context mContext;
    private Object subject;

    public AMSInvocationHandler(Context context, Object subject) {
        mContext = context;
        this.subject = subject;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //拦截startActivity方法
        if("startActivity".equals(method.getName())) {
            Log.d("ytx","Proxy IActivityTaskManager startActivity invoke...");
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    Intent intent = new Intent();
                    intent.setClass(mContext, RegisteredActivity.class);
                    intent.putExtra("actionIntent", (Intent) args[i]);
                    args[i] = intent;
                    Log.d("ytx","replaced startActivity intent");
                }
            }
        }
        return method.invoke(subject,args);
    }
}

(5)hook Handler

①思路源码解析
在这里插入图片描述

②代码实现

 /**
     * 获得到handler特定消息中的Intent进行处理
     * 将Intent对象的RegisteredActivity替换成PluginActvity
     */
    public static void hookHandler(Context context) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        //1。获取到handler对象
        Class<?>  activityThreadClass = Class.forName("android.app.ActivityThread");
        Field sCurrentActivityThreadField =  activityThreadClass.getDeclaredField("sCurrentActivityThread");
        sCurrentActivityThreadField.setAccessible(true);
        Object activityThreadObject = sCurrentActivityThreadField.get(null);//静态直接给null即可
        Field mHField = activityThreadClass.getDeclaredField("mH");
        mHField.setAccessible(true);
        Object mHObject = mHField.get(activityThreadObject);
        //2.给handler的mCallback的属性进行赋值,静态代理实现
        Field mCallbackField = Handler.class.getDeclaredField("mCallback");
        mCallbackField.setAccessible(true);
        mCallbackField.set(mHObject,new HookHmCallback());
        //3.在callback中将Intent对象的RegisberedActivity替换成PluginActvity
    }

静态代理拦截,此处将插件activity换回去

/**
 * @Author : yaotianxue
 * @Time : On 2023/6/6 08:50
 * @Description : MyCallBack
 */
public class HookHmCallback implements Handler.Callback {
    private static final int LAUNCH_ACTIVITY         = 100;
    private static final int EXECUTE_TRANSACTION = 159;
    private static final String TAG = "ytx" ;

    @Override
    public boolean handleMessage(@NonNull Message msg) {
        switch (msg.what){
            // API 21 ~ 27 启动Activity的消息是LAUNCH_ACTIVITY
            case LAUNCH_ACTIVITY:
                Log.d(TAG,"HookHmCallback handleMessage LAUNCH_ACTIVITY enter !!!");
                // 消息对象是ActivityClientRecord对象,其中包含Intent
                // 获取intent对象
                Object intentObject = ReflectUtils.getFieldValue(msg.obj,"intent");
                if(intentObject instanceof  Intent){
                    Intent intent = (Intent) intentObject;
                    // 将之前替换缓存下来的插件Intent替换回去
                    Parcelable actionIntent = intent.getParcelableExtra("actionIntent");
                    if(actionIntent != null){
                        boolean success = ReflectUtils.setField(msg.obj,"intent",actionIntent);
                        if(success){
                            Log.d(TAG,"HookHmCallback handleMessage LAUNCH_ACTIVITY replaced !!!");
                        }
                    }
                }
                break;
            // API 28 ~ 32,添加了事务管理,启动Activity的消息是EXECUTE_TRANSACTION
            case EXECUTE_TRANSACTION:
                Log.d(TAG,"HookHmCallback handleMessage EXECUTE_TRANSACTION enter !!!");
                // 启动Activity之中EXECUTE_TRANSACTION其中一条消息,需要找到属于启动Activity的那条消息
                // 消息对象是ClientTransaction对象,其中有ClientTransactionItem列表
                // 启动Activity的Item是LaunchActivityItem,其中包含Intent
                // 获取mActivityCallbacks,Item列表对象
                Object mActivityCallbacksObject = ReflectUtils.getFieldValue(msg.obj,"mActivityCallbacks");
                if(mActivityCallbacksObject instanceof List){
                    List mActivityCallbacks = (List) mActivityCallbacksObject;
                    // 循环列表
                    for (Object callbackItem : mActivityCallbacks) {
                        // 找到LaunchActivityItem对象
                        if(TextUtils.equals(callbackItem.getClass().getName(),"android.app.servertransaction.LaunchActivityItem")){
                            // 获取LaunchActivityItem的Intent对象
                            Object mIntentObject = ReflectUtils.getFieldValue(callbackItem,"mIntent");
                            if(mIntentObject instanceof Intent){
                                Intent mIntent = (Intent) mIntentObject;
                                // 将之前替换缓存下来的插件Intent替换回去
                                Parcelable actionIntent = mIntent.getParcelableExtra("actionIntent");
                                if(actionIntent != null){
                                    boolean success = ReflectUtils.setField(callbackItem,"mIntent",actionIntent);
                                    if(success){
                                        Log.d(TAG,"HookHmCallback handleMessage EXECUTE_TRANSACTION replaced !!!");
                                    }
                                }
                            }
                        }
                    }
                }
                break;
        }
        return false;
    }
}

3. 第三个问题:资源加载

(1)资源加载思路解析

在这里插入图片描述

a.独立运行时,宿主中只有自己资源
b.插件架构后,application中加载插件中的资源,插件activity中使用资源时通过application获得即可

在这里插入图片描述

(2)代码实现

①获得插件apk中的资源

/**
     * 获取插件的Resources
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    public Resources loadResources() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        AssetManager assetManager = AssetManager.class.newInstance();
        Method addAssetPathMethod = AssetManager.class.getMethod("addAssetPath", String.class);
        addAssetPathMethod.setAccessible(true);
        String pluginStr = mContext.getExternalFilesDir(null).getAbsolutePath()+"/pluginapp-debug.apk";
        addAssetPathMethod.invoke(assetManager, pluginStr);
        return new Resources(assetManager,mContext.getResources().getDisplayMetrics(),
                mContext.getResources().getConfiguration());

    }

②application中获得插件的资源

class App: Application() {
    private lateinit var loadResources:Resources//插件的资源
    override fun onCreate() {
        super.onCreate()
        val pluginManager = PluginManager.getInstance(this).init()
        loadResources = pluginManager.loadResources()//获得插件的资源文件
    }
    fun getLoadResources():Resources{
        return if(loadResources == null){//如果是null 就返回宿主资源
            super.getResources()
        }else{//如果不是null 就返回插件资源
            loadResources
        }
    }
}

③插件apk中调用appliction的资源,找到对应的资源

open class BaseActivity:AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun getResources(): Resources {
        // 因为插件的全部Activity都继承于这个类,所以当Activity需要加载资源的时候,会访问这个getResources方法
        // 如果获取application的resources不为空
        //    如果当前app以插件形式在宿主中运行,那得到的便是宿主Application中的Resources对象
        //    又因为宿主的Application返回的是插件的Resources对象,所以最终加载的仍然是插件的资源
        //    如果当前app独立运行,那么得到的便是是自身的Application,那么返回的将是自身的Resources对象
        // 否则返回自身的Resources对象
        val pluginResources = application?.resources
        if(pluginResources != null){
            return pluginResources
        }
        return super.getResources()
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值