插件化技术

news2025/1/16 14:02:22

插件化技术

  • 一.概述
  • 二.原理
  • 三.好处
  • 四.插件化涉及到的技术以及常用的插件化框架
  • 五.详细说明
    • 1.第一个问题:类加载
      • (1)Android 项目中,动态加载技术按照加载的可执行文件的不同大致可以分为两种:
      • (2)在 Android 中的 ClassLoader 机制主要用来加载 dex 文件,系统提供了两个 API 可供选择:
      • (3)插件化类加载原理
      • (3)核心代码,未适配
    • 2.第二个问题:组件生命周期管理
      • (1)问题描述
      • (2)hook技术
      • (3)activity的启动过程如何偷梁换柱
      • (4)hook AMS
      • (5)hook Handler
    • 3. 第三个问题:资源加载
      • (1)资源加载思路解析
      • (2)代码实现

一.概述

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()
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/626582.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

本地安装 Stable Diffusion 教程 Mac 版本

前面两篇讲了如何用免费的网络资源搭建 Stable Diffusion&#xff0c;有朋友问&#xff0c;有没有在本地搭建的教程。 以 MacBook Pro 为例&#xff0c;下面是安装步骤。 前置要求&#xff1a;Homebrew&#xff0c;Python 3.0。 如未安装Homebrew&#xff0c;请按照https://bre…

CASAIM与北京大学达成科研合作,基于3D打印技术加快力学性能试验分析,实现高效的力学结构设计和力学测试

近期&#xff0c;CASAIM与北京大学达成科研合作&#xff0c;基于3D打印技术进行力学性能试验分析&#xff0c;快速制造各种力学测试样件&#xff0c;从而实现高效的力学结构设计和力学测试。 北京大学是我国教育部直属的全国重点大学&#xff0c;位列国家“双一流”A类 、“985…

SpringBoot的宠物医院管理系统(有文档)

SpringBoot的宠物医院管理系统 本项目适合用来学习&#xff0c;以及二次开发&#xff0c;分享下 简介 1.访问地址 http://localhost:8080/ 超级管理员账户 账户名&#xff1a;admin 密码&#xff1a;admin123 宠物医生 账户名&#xff1a; laozhang 密码&#xff1a;12345…

异常检测学习笔记 二、基于角度和深度的极值分析技术

一、异常检测的概率模型 为您的数据选择合适的模型,选择一个概率阈值,低于该阈值将数据标记为异常,计算观察数据中每个实例的概率,低于阈值的情况属于异常情况。 研究表明,世界杯比赛的进球数可以很好地近似于泊松分布。在一场比赛中进n球的概率由下式给出: ,其中λ是每…

IP地址与MAC地址

引言&#xff08;有基础的同学可以不看&#xff09;&#xff1a;在复杂的网络通信中&#xff0c;有茫茫多的数据在中传输&#xff0c;它们是如何在相隔一步一步寻找到对方的呢&#xff1f; 网络通信的基本结构https://blog.csdn.net/qq_68140277/article/details/130937717?sp…

OpenStack部署(五)

OpenStack部署 11. 启动一个实例11.1 获取凭证11.2 创建虚拟网络11.3 创建主机规格11.4 生产环境的规格推荐11.5 生成一个键值对11.6 增加安全组规则11.7 创建块设备存储11.8 创建实例 12. 资源整理12.1 用到的端口12.2 openstack各组件常用命令1. openstack命令2. nova的常用命…

chatgpt赋能python:Python怎么5个一行?——提高代码可读性的方法

Python怎么5个一行&#xff1f;——提高代码可读性的方法 在Python编程中&#xff0c;提高代码可读性是非常重要的。然而&#xff0c;如果代码缩进不当&#xff0c;代码块就会非常难以辨认。那么&#xff0c;如何在不影响代码可读性的情况下使代码更清晰易懂呢&#xff1f;本文…

javaScript蓝桥杯---一起会议吧

目录 一、介绍二、准备三、目标四、代码五、完成 一、介绍 网络会议已经成为当下最流行的会议模式&#xff0c;为网络会议提供支持的当然是一些优秀的会议软件。 本题需要在已提供的基础项目中使用 Vue 2.x 知识完善代码&#xff0c;最终实现网络会议软件中&#xff0c;参会人…

javaScript蓝桥杯----权限管理

目录 一、介绍二、准备三、目标四、代码五、知识点六、完成 一、介绍 你有没有想过&#xff0c;在我们日常浏览的网页中&#xff0c;那些新闻或者商品内容是如何被输入到数据库中的呢&#xff1f;大家虽然没有用过&#xff0c;但是肯定听过“后台管理系统”&#xff0c;运营人…

从零开始的软路由之爱快虚拟机搭建openwrt

缘起 上篇文章我们介绍了爱快软路由的搭建方法&#xff0c;成功了实现了软路由的初级布置——能上网了。接下来就是搭建双软路由中的另一个openwrt了&#xff0c;上期介绍了爱快的特点&#xff0c;主要是用来多拨&#xff0c;分流&#xff0c;流控等操作&#xff0c;在这些方面…

maven 插件 assembly 打tar.gz包

maven 插件 assembly 打tar.gz包 一、项目目录二、pom文件1. profiles2. plugins3. resource 三、assembly.xml四、application.yml五、启动脚本1. start.sh2. stop.sh 六、执行 mvn 打包命令七、tar.gz 包上传服务器并解压八、执行 start.sh 启动脚本九、访问 swagger GitHub:…

Tomcat的部署及优化(贼详细)

目录 一、Tomcat服务器简介 1、Tomcat服务器 2、Tomcat三大核心组件 3、 Java Servlet 4、JSP全称Java Server Pages 5、 Tomcat 功能组件结构 6、 Container 结构分析 7、Tomcat 请求过程 二&#xff1a;Tomcat部署与安装 1.关闭防火墙&#xff0c;上传所需软件包 2.安…

跨部门沟通与协作迟迟进展不下去,如何有效解决问题?

在一个完整的项目中&#xff0c;多个专业技能版块的联动是必不可少的。然而&#xff0c;由于各个部门之间工作交集的存在&#xff0c;跨部门沟通与协作成为了必经之路&#xff0c;需要我们各部门凝聚力量&#xff0c;携手闯关。 但是&#xff0c;在工作中总会出现各种问题&…

05_MySQL索引优化

四种&#xff1a;1.主键 2.单值 3.唯一 4.复合 1. 性能分析&#xff08;explain&#xff09; mysql5.6以后优化器做了很多改进&#xff0c;执行时会自动进行大量的优化&#xff0c;很多现象需要在5.5才能演示成功。 1.1 explain是什么? 模拟优化器查看执行计划 使用EXPLAIN关…

python基础----09-----类、对象、魔法方法、封装、继承、类型注解、多态

一 初识对象 说白了就是类的实例化&#xff0c;类是一个抽象层的定义。 例如下面class Student就是定义的一个类&#xff0c;它是抽象层&#xff0c;然后stu_1 Student()&#xff0c;我们根据类创建了一个对象&#xff0c;就是对类的实例化&#xff0c;这个实例化对象我们是可…

FinalShell界面左侧为什么能够监测系统指标动态变化的原理

前言&#xff1a; 我们可以看出FinalShell是用Java写的&#xff0c;具体怎么看出来的&#xff0c;不能光看界面logo是Java的logo&#xff0c;还要进它的安装目录下进行查看是否真是用Java编写的&#xff01;&#xff01;&#xff01; 具体查看如下&#xff1a; 查看finalshe…

Qt+QtWebApp开发笔记(五):http服务器html中使用json触发ajax与后台交互实现数据更新传递

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/131122772 红胖子网络科技博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

如何规划和执行安全测试

如何规划和执行安全测试 在现代软件开发中&#xff0c;安全测试已经成为一个必不可少的环节。在完成软件开发后&#xff0c;需要对应用程序进行安全测试&#xff0c;以确保其可以在生产环境中正常运行并能够抵御各种安全攻击和漏洞。 如何规划和执行安全测试是一个重要的问题&a…

Microsoft Excel中HYPERLINK函数的6个用途

Excel 在工具栏中提供了自己的内置链接功能。但 HYPERLINK 功能可以让你做更多的事情,比如公司内部网上的工作簿链接、共享服务器、其他驱动器,甚至 Word 文档中的书签。让我们来看看使用这个多功能功能可以做的一切。 HYPERLINK函数的6个用途 链接到电子表格中的单元格链接到…

概率论:方差、标准差、协方差、皮尔逊相关系数、线性相关

方差和标准差&#xff1a; 一个随机变量&#xff0c;的值的变化程度可以用方差计算&#xff1a; &#xff1b;其中 是期望。 我们举个例子&#xff1a; 服从均一分布&#xff0c;取值为0.1&#xff0c;0.2&#xff0c;0.3&#xff0c;0.4&#xff0c;0.5 &#xff0c;每种值…