Android hook式插件化详解

引言

Android插件化是一种将应用程序的功能模块化为独立的插件,并动态加载到主应用程序中的技术。通过插件化,开发者可以将应用程序的功能分解成独立的模块,每个模块可以作为一个插件单独开发、测试和维护,然后通过动态加载的方式集成到主应用程序中,实现功能的动态扩展和更新。

Android插件化通常涉及到动态加载、组件化、插件生命周期管理、插件间通信等技术,开发者需要使用相关的框架和工具来实现插件化功能。插件化可以帮助开发者更好地管理应用程序的复杂性,提高开发效率,同时也能够实现应用程序的功能动态更新和扩展,为用户提供更好的体验。

1.startActivity源码解析

我们上篇提到,调用 startActivity 启动一个没有在 Manifest.xml 文件中注册的 Activity 会报异常,那么我们今天从 startActivity 开始分析为什么会报异常。

startActivity 的流程大概是上面所示,最后执行到 AMS.startActivity 后如果没有在 Manifest.xml 中注册,软件就会发生崩溃, 那么怎么样才能避免这样呢。其实非常简单,我们只要把我们未注册的 Activity 替换成我们已经在 Manifest.xml 注册不就行了吗。那么问题又来了,我们应该如何实现替换操作呢?说到替换,我们不得不借助动态代理的方式来实现了。

从上图可以看出,创建一个动态代理对象需要传递三个参数,下面介绍一下这些参数

ClassLoader loader: 类加载器

Class<?>[] interfaces: 需要监听的接口

InvocationHandler h: 监听的回调

说到这里,我们又延伸到另一个问题,我们应该监听哪一个接口呢?,其实从上面我们已经分析过了,我们应该要监听 AMS.startActivity 这一步,所以我们要监听的接口就是 IActivityTaskManager(9.0及以下是IActivityManager) 这个接口。接下来我们就对这个接口实现动态代理。

2.动态代理的实现

第一步:

我们可以通过 Class.forName() 得到 IActivityTaskManager 或者 IActivityManager 这个类,如下面代码所示:

Class<?> mIActivityManagerClass;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {mIActivityManagerClass = Class.forName("android.app.IActivityTaskManager");
} else {mIActivityManagerClass = Class.forName("android.app.IActivityManager");
}

有了类之后,我们可以创建动态代理对象了,但是 IActivityTaskManager 或者 IActivityManager 接口的方法那么多,我们没必要全部监听,我们只需要监听我们关注的 "startActivity" 这个方法就好了,如下代码所示:

//创建动态代理
Object mActivityManagerProxy = Proxy.newProxyInstance(getClassLoader(),//类加载器new Class[]{mIActivityManagerClass},//要监听的回调接口new InvocationHandler() {//回调的监听@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if ("startActivity".equals(method.getName())) {//做自己的业务逻辑//换成可以通过AMS检测的Activity}//为了程序能够正确执行,我们需要系统的IActivityManager实例return method.invoke(需要系统的IActivityManager实例, args);}
);

从上述代码中可以看出,要使动态代理生效,我们还需要一个 IActivityManager,查阅源码 ActivityTaskManager(8.0~9.0以下看 ActivityManager,8.0以下看 ActivityManagerNative) 可发现:

ActivityTaskManager.java:(Androdid10.0)

/** @hide */
public static IActivityTaskManager getService() {return IActivityTaskManagerSingleton.get();
}

ActivityManager.java:(Android 8.0 ~ Android9.0)

/**
* @hide
*/
public static IActivityManager getService() {return IActivityManagerSingleton.get();
}

ActivityManagerNative.java:(Android 7.0及以下)

/**
* Retrieve the system's default/global activity manager.
*/
static public IActivityManager getDefault() {return gDefault.get();
}

通过 getService() 或者 getDefault() 可以返回一个我们需要的对象实例,我们接下来可以反射来执行该方法来获取 IActivityTaskManager 或者 IActivityManager 实例对象。代码如下:

//获取 ActivityManager 或 ActivityManagerNative 或 ActivityTaskManager
Class<?> mActivityManagerClass;
Method getActivityManagerMethod;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {mActivityManagerClass = Class.forName("android.app.ActivityManagerNative");getActivityManagerMethod = mActivityManagerClass.getDeclaredMethod("getDefault");
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){mActivityManagerClass = Class.forName("android.app.ActivityManager");getActivityManagerMethod = mActivityManagerClass.getDeclaredMethod("getService");
} else {mActivityManagerClass = Class.forName("android.app.ActivityTaskManager");getActivityManagerMethod = mActivityManagerClass.getDeclaredMethod("getService");
}
getActivityManagerMethod.setAccessible(true);
//这个实例本质是 IActivityManager或者IActivityTaskManager
final Object IActivityManager = getActivityManagerMethod.invoke(null);

现在有了 IActivityTaskManager 或者 IActivityManager 实例对象我们就可以让程序继续能够执行下去了。

第二步: 既然我们自己创建了 IActivityTaskManager 或者 IActivityManager 的动态代理,我们就要把原来系统的 IActivityTaskManager 或者 IActivityManager 实例对象给替换掉。还是通过上面的 getService() 或者 getDefault() 方法入手,我们继续跟踪代码发现: ActivityManager 或 ActivityManagerNative 或 ActivityTaskManager 都有一个 Singleton 共同的属性,我们查看一下这个类的源码:

public abstract class Singleton<T> {private T mInstance;protected abstract T create();public final T get() {synchronized (this) {if (mInstance == null) {mInstance = create();}return mInstance;}}
}

里面的 mInstance 属性正好是 IActivityTaskManager 或者 IActivityManager 实例,所以我们直接替换掉 mInstance 值就可以了。代码如下:

//获取 IActivityTaskManagerSingleton 或者 IActivityManagerSingleton 或者 gDefault 属性
Field mSingletonField;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {mSingletonField = mActivityManagerClass.getDeclaredField("gDefault");
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){mSingletonField = mActivityManagerClass.getDeclaredField("IActivityManagerSingleton");
} else {mSingletonField = mActivityManagerClass.getDeclaredField("IActivityTaskManagerSingleton");
}
mSingletonField.setAccessible(true);
Object mSingleton = mSingletonField.get(null);//替换点
Class<?> mSingletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = mSingletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
//将我们创建的动态代理设置到 mInstance 属性当中
mInstanceField.set(mSingleton, mActivityManagerProxy);

到这里我们的动态代理算是实现好了,完整的代码如下:

private void hookAMSAction() throws Exception {//动态代理Class<?> mIActivityManagerClass;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {mIActivityManagerClass = Class.forName("android.app.IActivityTaskManager");} else {mIActivityManagerClass = Class.forName("android.app.IActivityManager");}//获取 ActivityManager 或 ActivityManagerNative 或 ActivityTaskManagerClass<?> mActivityManagerClass;Method getActivityManagerMethod;if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {mActivityManagerClass = Class.forName("android.app.ActivityManagerNative");getActivityManagerMethod = mActivityManagerClass.getDeclaredMethod("getDefault");} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q){mActivityManagerClass = Class.forName("android.app.ActivityManager");getActivityManagerMethod = mActivityManagerClass.getDeclaredMethod("getService");} else {mActivityManagerClass = Class.forName("android.app.ActivityTaskManager");getActivityManagerMethod = mActivityMa

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

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

相关文章

功能测试,接口测试,自动化测试,压力测试,性能测试,渗透测试,安全测试,具体是干嘛的?

软件测试是一个广义的概念&#xff0c;他包括了多领域的测试内容&#xff0c;比如&#xff0c;很多新手可能都听说&#xff1a;功能测试&#xff0c;接口测试&#xff0c;自动化测试&#xff0c;压力测试&#xff0c;性能测试&#xff0c;渗透测试&#xff0c;安全测试等&#…

CentOS系统中设置反向代理服务器的步骤

在CentOS系统中设置反向代理服务器可以帮助你隐藏原始服务器的细节&#xff0c;并提高服务器的安全性。以下是在CentOS系统中设置反向代理服务器的步骤概述&#xff1a; 安装反向代理软件&#xff1a; 常见的反向代理软件包括Nginx和Apache。你可以选择其中之一来作为你的反向…

【Git】error:failed to push some refs to ‘git@gitee.com:name/project.git‘

错误展示 今天在版本回退的时候&#xff0c;推送到gitee显示如下错误 先执行了git pull origin master&#xff0c;然后再执行push操作&#xff0c;也没有用 解决办法 执行git push -f origin master ,-f 表示强制推送。 结果 问题解决啦&#xff01;

区分node,npm,nvm

目录 一&#xff0c;nodejs二&#xff0c;npm三&#xff0c;nvm 区分node&#xff0c;npm&#xff0c;nvm 几年前学习前端的时候学习的就是htmlcssjs 三件套。 现在只学习这些已经不能满足需要了。 一&#xff0c;nodejs nodejs是编程语言javascript运行时环境。&#xff08;比…

项目架构-六边形架构的概述和实现

使用传统的分层架构&#xff0c;我们的所有依赖项都指向一个方向&#xff0c;上面的每一层都依赖于下面的层。传输层将依赖于交互器&#xff0c;交互器将依赖于持久层。 在六边形架构中&#xff0c;所有依赖项都指向内部——我们的核心业务逻辑对传输层或数据源一无所知。尽管如…

【深度学习】强化学习(一)强化学习定义

文章目录 一、强化学习问题1、交互的对象1. 智能体&#xff08;Agent&#xff09;2. 环境&#xff08;Environment&#xff09; 2、强化学习的基本要素1. 状态 &#x1d460;2. 动作 &#x1d44e;3. 策略 &#x1d70b;(&#x1d44e;|&#x1d460;)4. 状态转移概率 &#x1…

人工智能企业引入S-SDLC,推动安全能力大跃升,保障AI技术体系深化落地

某人工智能公司是国际知名的上市企业&#xff0c;核心技术处于世界前沿水平。多年来&#xff0c;该企业在智慧教育、智慧医疗、智慧城市、智慧司法、金融科技、智能汽车、运营商、消费者等领域进行深度技术赋能&#xff0c;深入推进各个行业的智能化、数字化转型建设。 人工智能…

【开源】基于Vue+SpringBoot的智慧家政系统

项目编号&#xff1a; S 063 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S063&#xff0c;文末获取源码。} 项目编号&#xff1a;S063&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询家政服…

简单实现Spring容器(一)

阶段1: 编写自己的Spring容器,实现扫描包,得到bean的class对象.思路: 使用 ElfSpringConfig.java 替代beans.xml文件作为配置文件,从中获取到: 1.扫描包,得到bean的class对象. 2.排除包下不是bean的 1.容器文件 ElfSpringApplicationContext.java 核心!!! package com.elf…

vuepress-----13、分割config

13、分割config config.js const headConfig require(./config/headConfig); const pluginsConfig require(./config/pluginsConfig); const themeConfig require(./config/themeConfig)module.exports {title: "小邵子",description: 小邵子的个人笔记,head: he…

Mybatis一级缓存与二级缓存

一、简介 数据库接收到sql语句后&#xff0c;需要词法/语法解析&#xff0c;优化sql语句&#xff0c;制定执行计划。多数情况下&#xff0c;相同的sql语句可能只是传入参数不同&#xff08;如where条件后的值不同...&#xff09;。 如果每次都需要经过上面的 词法/语法解析、语…

java基础之异常处理

1、概念 异常是程序中的一些错误&#xff0c;但并不是所有的错误都是异常&#xff0c;并且错误有时候是可以避免的。 2、异常发生的原因 用户输入了非法数据。要打开的文件不存在。网络通信时连接中断&#xff0c;或者JVM内存溢出。 3、三种类型的异常 检查性异常&#xff1a;…

Unity UGUI TextMeshPro实现输入中文和表情包(Emoji)表情

目录 实现中文显示 准备工作 1、打开Window——TextMeshPro——FontAssetCreator 2、把字体文件放入SourceFont中 3、把CharacterSet改为Characters from File 4、把字体库文件放入Characters File 5、设置好参数点击Generate Font Atlas等待完成后保存 6、把生成后保存…

科普小知识-3D 打印是什么?

3D 打印是什么&#xff1f;作为近年来备受关注的前沿科技&#xff0c;3D 打印技术正在不断改变着制造业、医疗领域、艺术设计等多个领域的面貌。其又被称为增材制造&#xff0c;是一种通过电脑设计&#xff0c;逐层堆叠材料来创建三维物体的技术。 3D 打印的基本原理 3D 打印…

【Ajax】发送get请求获取接口数据

编写html实现通过Ajax发送get请求并获取数据 代码实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title…

LinuxBasicsForHackers笔记 -- 管理用户环境变量

查看和修改环境变量 env – 您可以通过从任何目录在终端中输入 env 来查看所有默认环境变量。环境变量的名称始终为大写&#xff0c;如 HOME、PATH、SHELL 等。 查看所有环境变量 set – 查看所有环境变量&#xff0c;包括 shell 变量、局部变量和 shell 函数&#xff08;例…

2. PyTorch——Tensor和Numpy

2.1Tensor和Numpy Tensor和Numpy数组之间具有很高的相似性&#xff0c;彼此之间的互操作也非常简单高效。需要注意的是&#xff0c;Numpy和Tensor共享内存。由于Numpy历史悠久&#xff0c;支持丰富的操作&#xff0c;所以当遇到Tensor不支持的操作时&#xff0c;可先转成Numpy…

conda配置不同版本的python及依赖库--conda conda conda

一、conda介绍 Conda 是一个开源的软件包管理系统和环境管理系统&#xff0c;用于安装多个不同版本的软件包及其依赖关系&#xff0c;并在它们之间轻松切换。 Conda 是为 Python 程序创建的&#xff0c;适用于 Linux&#xff0c;OS X 和Windows Conda可以构建不同的环境&#…

【计算机网络学习之路】HTTP响应报文Cookie原理

目录 HTTP响应报文格式 一. 状态行 状态码与状态码描述 二. 响应头 Cookie原理 一. 前因 二. Cookie的状态管理 结束语 HTTP响应报文格式 HTTP响应报文分为四部分 状态行&#xff1a;包含三部分&#xff1a;协议版本&#xff0c;状态码&#xff0c;状态码描述响应头&a…

企业计算机服务器中了mallox勒索病毒如何处理,Mallox勒索病毒解密

随着计算机技术的不断发展&#xff0c;越来越多的企业利用网络来提高工作效率&#xff0c;但随之而来的网络安全威胁也在不断增加&#xff0c;各种勒索病毒种类不断增加&#xff0c;给企业的数据安全带来严重的威胁&#xff0c;影响企业的生产业务开展。近期&#xff0c;云天数…