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() {//回调的监听
    @Override
    public 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 或 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 = mActivityMa

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

香港云服务器:全面介绍与使用场景分析

这几年基于国内互联网技术的发展&#xff0c;各类海外贸易的兴起&#xff0c;很多网站都启用了海外云服务。这其中&#xff0c;香港的 IDC 市场异常火爆。也不奇怪&#xff0c;就目前来看&#xff0c;国内大多数网站的访问用户在国内外均有涉及&#xff0c;而香港云服务器恰好满…

linux云服务器开启防火墙注意事件

重要的事情先说三遍: linux云服务器开启防火墙要先获取到云服务器的管理界面控制权!! linux云服务器开启防火墙要先获取到云服务器的管理界面控制权!! linux云服务器开启防火墙要先获取到云服务器的管理界面控制权!! 也就是能打开这个页面: 为什么这么说呢?如果你…

最长连续序列(leetcode 128)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路方法一&#xff1a;排序方法二&#xff1a;哈希表 5.实现示例参考文献 1.问题描述 给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你…

服务器RAID系统的常见故障,结合应用场景谈谈常规的维修处理流程

常见的服务器RAID系统故障包括硬盘故障、控制器故障、电源故障、写入错误和热插拔错误。下面结合这些故障的应用场景和常规维修处理流程来详细讨论&#xff1a; 硬盘故障&#xff1a; 应用场景&#xff1a;在服务器RAID系统中&#xff0c;硬盘故障是最常见的问题之一。硬盘可能…

JavaSE基础50题:10. 计算1/1-1/2+1/3-……+1/99-1/100的值(两种方法)

概述 计算1/1 - 1/2 1/3 - …… 1/99 - 1/100的值。 当分母为偶数时&#xff0c;符号是负的&#xff0c;放分母为奇数时&#xff0c;符号是负的。 方法一 用 flg 做了一个正负交替 【代码】 public static double func() {double sum 0;int flg 1; //设置正负号的for (i…

【Python】Python音乐网站数据+音频文件数据抓取(代码+报告)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

优化 SQL 日志记录的方法

为什么 SQL 日志记录是必不可少的 SQL 日志记录在数据库安全和审计中起着至关重要的作用&#xff0c;它涉及跟踪在数据库上执行的所有 SQL 语句&#xff0c;从而实现审计、故障排除和取证分析。SQL 日志记录可以提供有关数据库如何访问和使用的宝贵见解&#xff0c;使其成为确…

住宅ip和机房ip的区别

随着互联网的普及&#xff0c;越来越多的人开始接触网络&#xff0c;而IP地址则是网络中不可或缺的一部分。在日常生活中&#xff0c;我们常常会听到住宅IP和机房IP这两个概念&#xff0c;那么它们之间有什么区别呢&#xff1f; 首先&#xff0c;让我们了解一下什么是住宅IP和…

Python上网神器,自动修改Hosts工具

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是彭涛&#xff0c;今天为大家分享 Python上网神器&#xff0c;自动修改Hosts工具&#xff0c;全文6400字&#xff0c;阅读大约18分钟。 在互联网时代&#xff0c;Hosts 文件的修改是一项常见的任务&#xf…

Vue.js 学习总结(4)—— Vue3响应式系统原理

概念 响应式是指当数据发生变化时&#xff0c;系统会自动更新与数据相关的 DOM 结构。在 Vue2 中&#xff0c;响应式系统的实现基于 Object.defineProperty。然而&#xff0c;Object.defineProperty 有一些局限&#xff0c;如&#xff1a;无法监听数组的变化、需要遍历对象的每…

MS8091/2运算放大器可Pin to Pin兼容AD8091/2

MS809x 系列是一种易用的、低成本的轨到轨输出电压反馈放大器&#xff0c;它具有典型的电流反馈放大器带宽和转换率的优势&#xff0c;同时也有较大的共模电压输入范围和输出摆幅&#xff0c;这使它很容易在单电源 2.5V 的低压情况下工作。可Pin to Pin兼容AD8091/AD8092。 虽然…

DAP数据集成与算法模型如何结合使用

企业信息化建设会越来越完善&#xff0c;越来越体系化&#xff0c;当今数据时代背景下更加强调、重视数据的价值&#xff0c;以数据说话&#xff0c;通过数据为企业提升渠道转化率、改善企业产品、实现精准运营&#xff0c;为企业打造自助模式的数据分析成果&#xff0c;以数据…

快捷支付是什么?快捷支付好申请吗?

快捷支付是指用户在购买商品时&#xff0c;不需要打开网上银行&#xff0c;只需提供银行卡号码、户名、手机号码等信息&#xff0c;银行验证手机号码的正确性&#xff0c;输入动态密码即可完成支付&#xff0c;无需打开网上银行。持卡人将银行卡绑定到第三方支付应用程序&#…

高并发爬虫用Python语言适合吗?

不管你用什么语言没在进行高并发前&#xff0c;有几点是需要考虑清楚的&#xff0c;&#xff1b;例如&#xff1a;数据集大小&#xff0c;算法、是否有时间和性能方面的制约&#xff0c;是否存在共享状态&#xff0c;如何调试&#xff08;这里指的是日志、跟踪策略&#xff09;…

成品短视频app源码行业前沿趋势

随着移动互联网技术的不断发展和智能手机的普及&#xff0c;视频已经成为人们获取信息、娱乐和交流的主要形式之一。在这一趋势下&#xff0c;成品短视频app源码应运而生&#xff0c;成为用户创作、分享和观看短视频内容的重要平台。本篇文章将为您揭示成品短视频app源码行业的…

在intelliJ spring boot gradle插件3.2.0中未找到匹配的变量

我正在尝试使用spring启动Gradle插件的版本3.2.0。这是我的build.gradle文件&#xff1a; plugins {id javaid org.springframework.boot version 3.2.0id io.spring.dependency-management version 1.1.4 }group com.yaxin version 0.0.1-SNAPSHOTjava {sourceCompatibilit…

计算机毕业设计 基于大数据的智能家居销量数据分析系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

超大规模集成电路设计----FPGA时序模型及FSM的设计(八)

本文仅供学习&#xff0c;不作任何商业用途&#xff0c;严禁转载。绝大部分资料来自----数字集成电路——电路、系统与设计(第二版)及中国科学院段成华教授PPT 超大规模集成电路设计----RTL级设计之FSM&#xff08;八&#xff09; 7.1 CPLD的时序模型7.1.1 XPLA3 时序模型7.1.…

[Linux] Bash脚本多函数应该如何执行?使用eval提高脚本编写效率!

在工作过程中经常会编写一些测试脚本&#xff0c;有些脚本里有多个函数&#xff0c;要通过用户输入执行对应的函数&#xff0c;如这样&#xff1a; 这也太麻烦了吧 执行如下&#xff1a; 这样在函数多的情况下需要写很多判断&#xff0c;效率低下。 我们可以使用eval命令来进行…

MySQL之数据库及表操作

MySQL之数据库及表操作 文章目录 MySQL之数据库及表操作一、数据库的基本结构二、数据库的创建和删除三、数据表的结构定义和操作四、数据的插入五、主键和自增长属性1、什么是主键2、自增长属性 一、数据库的基本结构 数据库系统由数据库服务器为载体&#xff0c;拥有一个或者…