Android插件化原理(二)—启动插件Activity

Android笔记(二十三):以插件化APK方式启动带WebView的Activity 本文记录插件化学习过程中,如何以插件化apk方式启动带WebView的Activity,分三大步骤完成。 阅读详情

涉及到的同系列往期文章:


本篇文章目录:

1 原理

1.1 涉及到的Framework原理

回顾第一篇Android插件化原理(一)—— 插件类加载、类加载原理、(双亲委托机制)两点:

  1. 类加载器

为什么要说ClassLoader呢,因为class 都是由ClassLoader加载的,应用的所有类都在类加载器中。

  • DexClassLoader 8.0之前提供给系统使用,8.0之后与PathClassLoader并无区别
  • PathClassLoader ,8.0之前提供给开发者使用,用于加载应用class文件,例如CustomActivity、CustomeFragment,包括三方jar、apk、库的class;8.0之后与DexClassLoader并无区别;
  • BootClassLoader:用于加载Framework的class文件,例如Activity、Fragment
  1. 加载插件类原理:
  • 创建插件的DexClassLoader类加载器,通过反射获取插件的dexElements
  • 获取宿主的PathClassLoader类加载器,通过反射获取宿主的dexElements
  • 合并宿主的dexElements和插件的dexElements,生成新的Element[]对象
  • 通过反射把新的Element[]复制给宿主的dexElements。

以及StartActivity原理(二)——Android9.0 startActivity原理,Activity启动原理提到的

  1. Activity启动原理:
    在这里插入图片描述

app进程告知AMS要启动activity,AMS检测Activity存在后,返回目标activity的类信息,并通知app进程构造插件Activity实例;

1.1 核心思想
  • 启动插件的activity;
  • 检测到启动的是插件Acitivity,将其hook成宿主app的ProxyActivity;
  • AMS检测完成后,app启动ProxyActivity时,将其hook成我们的插件Activity。
1.2 具体原理

上面三步虽然简单,但是要明白为什么这么做?这么做原因有两点:

  1. 目标插件activity不在宿主Manifest清单文件中;
  2. 启动Activity需要通过AMS检测(manifest)。

所以在启动插件Activity的时候需要将intent的插件Activity hook成宿主的ProxyActivity去通过AMS检测,ProxyActivity在宿主app中,可以添加到Manifest清单文件中,自然可以通过检测;最后在启动阶段,将ProxyActivity再次hook成我们的插件Activity,最后在app进程可以构造其实例;

实现步骤:

  • 加载插件apk/dex
  • 启动插件Activity,并添加标志位表明启动插件Activity;
  • Hook AMS,检测到intent中的标志位,替换插件Activity为ProxyActivity;
  • hook ActivityThread的Handle——H,在handleLaunchActivity对应的的messageWhat,检测intent中的标志位;
  • 将ProxyActivity重新替换成插件Activity;

附简单流程图:

在这里插入图片描述

2 实现

  • 1.准备工作

public class ProxyActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}



public abstract class BaseActivity extends Activity {

    protected void startPluginActivity(Intent intent) {
        intent.putExtra(LoadUtil.INTENT_LOAD_PLUGIN, true);
        startActivity(intent);
    }
  
}

  • 2.Hook AMS
public class LoadUtil{

    private static final String TAG = "LoadUtil";

    private final static String INTENT_ORIGIN_INTENT = "intent_origin_intent";
    private final static String METHOD_START_ACTIVITY = "startActivity";
    public final static String INTENT_LOAD_PLUGIN = "intent_load_plugin";

    public final static int WHAT_LAUNCH_ACTIVITY = 100;
    //9.0版本H 处理启动activity的流程
    public final static int WHAT_EXECUTE_TRANSACTION = 159;

    /**
     * Hook AMS 的startActivity,用ProxyActivity替换plugin 的Activity,以欺骗AMS的manifest校验
     */
    @TargetApi(Build.VERSION_CODES.KITKAT)
    @SuppressLint("PrivateApi")
    public static void hookAMS() {
        try {
            //- 获取singleton(它作为单例持有了IActivityManager实例)
            Field singletonFiled;
            //8.0
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
                Class<?> clazz = Class.forName("android.app.ActivityManagerNative");
                singletonFiled = clazz.getDeclaredField("gDefault");
            } else {
                final Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
                singletonFiled = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
            }

            singletonFiled.setAccessible(true);
            final Object singleton = singletonFiled.get(null);//静态对象,直接反射

            //- 获取IActivityManager 对象
            final Class<?> singletonClass = Class.forName("android.util.Singleton");
            final Field mInstanceFiled = singletonClass.getDeclaredField("mInstance");
            mInstanceFiled.setAccessible(true);
            final Object mInstance = mInstanceFiled.get(singleton);

            final Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
            //- 代理IActivityManager
            Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class[]{iActivityManagerClass},
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            String methodName = method.getName();
                            if (methodName.equals(METHOD_START_ACTIVITY)) {
                                Intent intent = null;
                                int intentIndex = -1;
                                //找到intent
                                for (int i = 0; i < args.length; i++) {
                                    if (args[i] instanceof Intent) {
                                        intent = (Intent) args[i];
                                        intentIndex = i;
                                        break;
                                    }
                                }
                                //启动的是插件的activity才进行替换
                                if (intent != null && intent.getBooleanExtra(INTENT_LOAD_PLUGIN, false)) {
                                    final Intent proxyIntent = new Intent();
                                    proxyIntent.putExtra(INTENT_LOAD_PLUGIN, true);
                                    //使用包含ProxyActivity的intent替换原来的intent,以骗过AMS
                                    proxyIntent.setClassName("com.margin.host", "com.margin.host.ProxyActivity");
                                    //保存原来的intent
                                    proxyIntent.putExtra(INTENT_ORIGIN_INTENT, intent);
                                    //替换原来的intent
                                    args[intentIndex] = proxyIntent;
                                }
                            }
                            return method.invoke(mInstance, args);
                        }
                    });
            //替换系统的IActivityManager
            mInstanceFiled.set(singleton, proxyInstance);

        } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }

    }

}


  • 3. Hook ActivityThread的Handler-H

这里需要再补充一点Handler的知识,在我另一篇帖子再论Handler—Handler事件分发、Handler线程切换原理解析有说到Handler处理消息有三种方式:

  • 派生Handler,重写handleMessage;
  • Message的Runnable回调;
  • 给Handler添加Callback回调。

这里我们选择第三种方式(这个H-Handler并未添加Callback)在Callback.handleMesage中把ProxyActivity替换成目标Activity,切记,不要拦截,不要影响后续的流程。

这里9.0版本和之前的版本原理不一样,如果不理解的,烦请回看我的


  /**
     * Hook ActivityThread 的H-Handler,将ProxyActivity还原为 plugin的Activity
     */
    public static void hoodH() {
        final Handler.Callback callback = new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                try {
                    Intent proxyIntent;
                    Field intentField = null;
                 
                    // 不同版本的 H 中 launchActivity what值不一样
                    if (msg.what == WHAT_LAUNCH_ACTIVITY) {
                        intentField = msg.obj.getClass().getDeclaredField("intent");
                        proxyIntent = (Intent) intentField.get(msg.obj);

                        if (proxyIntent != null
                                && proxyIntent.getBooleanExtra(INTENT_LOAD_PLUGIN, false)) {
                            intentField.setAccessible(true);
                            //替换为原来的intent
                            Intent originIntent = proxyIntent.getParcelableExtra(INTENT_ORIGIN_INTENT);
                            if (originIntent != null) {
                                intentField.set(msg.obj, originIntent);
                            }
                        }
                        //28-9.0
                    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
                            && msg.what == WHAT_EXECUTE_TRANSACTION) {
                        final Field mActivityCallbacksField = msg.obj.getClass().getDeclaredField("mActivityCallbacks");
                        mActivityCallbacksField.setAccessible(true);
                        final List mActivityCallbacks = (List) mActivityCallbacksField.get(msg.obj);

                        int launchItemIndex = -1;
                        for (int i = 0; i < mActivityCallbacks.size(); i++) {
                            if (mActivityCallbacks.get(i).getClass().getName().equals("android.app.servertransaction.LaunchActivityItem")) {
                                launchItemIndex = i;
                                break;
                            }
                        }
                        if (launchItemIndex > -1) {
                            final Object launchActivityItem = mActivityCallbacks.get(launchItemIndex);
                            intentField = launchActivityItem.getClass().getDeclaredField("mIntent");
                            intentField.setAccessible(true);
                            proxyIntent = (Intent) intentField.get(launchActivityItem);
                            Intent originIntent = proxyIntent.getParcelableExtra(INTENT_ORIGIN_INTENT);
                            if (originIntent != null) {
                                intentField.set(launchActivityItem, originIntent);
                            }
                        }

                    }
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    e.printStackTrace();
                }
                //切记返回false不拦截,这样H 可以处理后续流程
                return false;
            }
        };
        try {
            //-1.获取ActivityThread 实例
            final Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
            final Field sCurrentActivityThread = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThread.setAccessible(true);
            final Object activityThread = sCurrentActivityThread.get(null);

            //-2.获取ActivityThread的mH实例
            final Field mHFiled = activityThreadClazz.getDeclaredField("mH");
            mHFiled.setAccessible(true);
            final Handler mH = (Handler) mHFiled.get(activityThread);

            //-3.获取H的mCallback变量
            final Class<?> handlerClazz = mH.getClass();
            final Field mCallbackField = handlerClazz.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);
            //-4.注入我们的Handler.Callback
            mCallbackField.set(mH, callback);
        } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }


  • 4.启用hook

public class HostApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        final String pluginApkPath = "/sdcard/plugin-debug.apk";
        LoadUtil.loadClass(this, pluginApkPath);
       	LoadUtil.hookAMS();
       	LoadUtil.hoodH();
    }

}

  • 5.启动插件Activity

  startPluginActivity(new Intent()
                        .setComponent(new ComponentName("com.margin.plugin", "com.margin.plugin.MainActivity")));

验证结果我不再贴出。

如何打包插件Dex以及加载插件类,这里不再赘述,您可以回看我的系列第一篇:Android插件化原理(一)—— 插件类加载、类加载原理、(双亲委托机制)


好了,本篇就到此结束啦,非常感谢您的阅读,如果有什么问题,还望您不吝赐教,批评指正,如果您看的满意,麻烦您给我点个赞,非常感谢。


下一篇插件化原理(三)——使用插件资源

Android classLoader 双亲委托 反射 类加载 Class.forName classLoader.loadClass 双亲委托机制,就是导入类的时候判断parent是否已经导入过该类。 阅读详情

相关推荐

Android插件化原理(一)Activity插件化

相关文章 Android深入四大组件系列 Android解析AMS系列 Android解析ClassLoader系列 前言 四大组件的插件化是插件化技术的核心知识点,而Activity插件化更是重中之重,Activity插件化主要有三种实现方式,分别是反射实现、接口实现和Hook技术实现。反射实现会对性能有所影响,主流的插件化框架没有采用此方式,关于接口实现可以阅读dynamic-l...

Android进阶三部曲 - 刘望舒 1万+

安卓插件化课程-第三篇:埋桩的方式启动插件中的activity

序: 1.本文是安卓插件化课程的第三篇,完整课程链接参见下面链接: 安卓插件化课程-序章_分享+记录-优快云博客前言:目前安卓领域,插件化十分的流行,本以为这一类的文章会有很多,但是百度一搜,基本上讲的插件化都是皮毛,没有涉及到核心。所以就想写一系列的文章来一步一步深入的讲解插件化,通过实现逐渐增加难度的需求,最终实现插件化方案。章节:插件化系列文章主要包含以下几个篇章:1.加载插件apk中的类2.启动插件apk中的activity3.使用插桩的方式启动apk中的activity4.使用插件apk中.

分享+记录 1513

Android插件化完美实现代码资源加载及原理讲解 附可运行demo

*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 。 我们通过前4篇的分解,分别将插件化设计到的知识点全部梳理了一遍,如果没有看过的,建议先看前面4篇 1. Binder机制 2. 插件化知识详细分解及原理 之代理,hook,反射, 3. 类加载及dex加载 4. 应用启动过程及类加载过程 5. Android插件化完美实现代码资源加载及原理讲解 附可运行demo

yulong0809的专栏 2万+

Android插件化学习之启动插件Activity

上一篇我们学习了类加载机制原理以及如何调用插件apk的dex方法Android插件化学习之初识类加载机制,这一篇我们紧接着上一篇的内容,尝试去启动插件apk中未注册的activity。其实我们发现最大的难点是做各个版本适配,因为Android启动流程每个版本的更新,那就需要我们每更新一个版本就需要去阅读源码进行相关适配,这是一个让人比较头疼的问题,因此插件化方案也在慢慢被抛弃,但我们通过学习阅读源码,去尝试实现各种黑科技,理解插件化方案实现的思想,这又何尝不是一种进步呢;ღ(´・ᴗ・`)https。....

故不积跬步无以至千里 2315

[Android 插件化(二)] DroidPlugin 用法

1 简介关于Android插件化可以查看我的前一篇博客: [Android 插件化(一)] DynamicLoadApk的用法本篇介绍第二种实现插件化的框架,360公司出品的DroidPlugin框架,个人比较推荐使用这个框架来实现插件化,毕竟是大厂出的东西,已经在360手机助手上用上了,可以放心使用,如果有坑的话,360会去填的嘛 DroidPlugin的Github地址:https://g

梧桐那时雨 1万+

android 通过代理activity的方式实现插件化

前言: 一直以来就对插件化这技术推崇已久,在去年也写过两篇关于插件化基础的文章: Java中的ClassLoader 动态加载机制 Android中的动态加载 都是关于classLoader如何加载外部apk中的代码,在"android中的动态加载"这篇博客末尾,提了下如何打开插件的acti,所以这篇文章就是谈如何通过代理Activity实现打开插件中的activity的。由于插件化的技术

lxj_time的博客 3365

Android---深入理解ClassLoader的加载机制

ClassLoader 是用来加载 class 文件的,不管是 jar 中还是 dex 中的 class;Java 中的 ClassLoader 通过双亲委托来加载各自指定路径下的 class 文件;可以自定义 ClassLoader,一般覆盖 findClass() 方法,不建议重写 loadClass 方法;Android 中常用的两个 ClassLoader 分别为:PathClassLoader 和 DexClassLoader。

qq_44950283的博客 1714

Android-第三方开源框架—插件化原理之Activity插件化(腾讯专家最佳插件化讲解)

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!资料⬅专栏获取。

2401_85729264的博客 751

安卓之使用DexClassLoader&AssetManager启动插件的Activity实现功能插件化

前言: 写完 安卓之插件化开发使用DexClassLoader&AssetManager实现功能插件化 通过在宿主Activity中装载插件Fragment来实现功能插件化后, 在网上又看见了一篇比较插件化的文章 探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法 通过偷换Activity上下文的方法启动了插件apk的Activity。 深受启发,所以就写了这遍文章使

cxmscb的博客 2172

Android-第三方开源框架—插件化原理之Activity插件化(腾讯专家最佳插件化讲解),看这篇文章就行了

finish方法的调用链和Activity的启动过程是类似的,注释1处会调用的AMS的finishActivity方法,接着是AMS通过ApplicationThread调用ActivityThread,ActivityThread向H发送DESTROY_ACTIVITY类型的消息,H接收到这个消息会执行handleDestroyActivity方法,handleDestroyActivity方法又调用了performDestroyActivity方法,如下所示。Singleton类如下所示,后面会用到。

2401_84152825的博客 902

数据分析实战:如何用皮尔逊相关系数避免伪相关陷阱(附Python代码)

本文通过实战案例与Python代码,深入解析了皮尔逊相关系数在数据分析中的应用与常见陷阱。文章重点揭示了如何识别与避免由混杂变量导致的伪相关,并介绍了通过可视化、敏感性分析、分层分析与偏相关分析等方法来检验相关性的可靠性,帮助分析师建立严谨的相关性思维。

weixin_29051245的博客 411

【Android 插件化】Hook 插件化框架 ( hook 插件化原理 | 插件包管理 )

Android 插件化系列文章目录、 前言、 一、hook 插件化原理、 二、插件化依赖库创建、 三、插件包管理、 四、插件包管理完整代码示例、 五、博客资源、

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 2958

海康威视摄像头CVE-2017-7921漏洞复现:从零到一的实战指南(含解密工具下载)

本文详细解析了海康威视摄像头CVE-2017-7921漏洞的技术原理与复现方法,提供从环境搭建到实战利用的完整指南。通过认证绕过漏洞,攻击者可获取敏感信息并执行高权限操作,文章还包含解密工具下载与防御建议,助力安全研究人员深入理解物联网设备安全风险。

weixin_29325515的博客 813

安卓插件化框架Shadow原理分析

Shadow核心原理: Shadow号称无Hook点。核心原理是运用代理的方式,把原本的acitivty编译期间改成一个代理类,去代理宿主activity的所有生命周期。 Demo流程: 原理流程: 改造点:

分享+记录 1万+

记一次Nacos线程数飙升排查

Nacos线程飙升问题排查

叭嘣的博客 2631

MobaXterm详细使用教程

当然MobaXterm的功能远不止这些,但这些是最基本,最常用的,因此先拿出来讲。点击确定后,输入密码(输入密码时并不会显示,只管输入后按确定即可,第一次登陆成功后会提示保存密码,一般选择同意),就可以连接上虚拟机了。(2) 我们可以直接apt-get安装vim等常用功能(或者官网下载插件),实现在windows下模拟linux环境,这样就可以使用vi命令准备INCAR文件,也可以使用Gnuplot进行绘图。支持 Unix 命令(bash,ls,cat,sed,grep,awk,rsync,…

博客 4万+

深入浅出Activity插件化相关原理

转载请标明出处:【顾林海的博客】 本篇文章已授权微信公众号 顾林海 独家发布 Activity的插件化解决的一个根本性问题就是插件中的Activity并没有在宿主的AndroidManifest.xml中进行注册,也就是说我们需要启动一个未注册的Activity,因此需要对Activity的启动过程有个了解。 启动Activity时会请求AMS创建Activity,这里的AMS指的是Acti...

顾林海 3万+

MIC(Maximal Information Coefficient)最大互信息系数

MIC(Maximal Information Coefficient)最大互信息系数是一种用于衡量两个变量之间相关性的统计量。它扩展了传统的互信息(MI)和条件互信息(CMI)的概念,具有更广泛的应用范围。MIC能够捕获包括功能性和非功能性的广泛的关联,具有普遍性和公平性。普遍性意味着在足够的样本量下,MIC能够捕捉到广泛的相关关系,而不仅仅局限于特定的功能类型;公平性则意味着MIC对不同类型的包含同等噪声的关系给出了相似的分数。需要注意的是,虽然MIC具有很多优点,但它也有一些局限性。

qlkaicx的博客 4605

Android插件化技术

一、Android插件化技术 我们在平时的开发过程中,会经常遇到产品需求的变更或者出现bug,在传统的模式中,我们需要首先需要修改代码,然后重新打包Apk,再交给公司的运营去官网或者应用商店上线,用户在打开应用的时候就会进行更新了。但是这种模式有几个缺点,一是上线周期长,从修改代码到用户更新需要较长的时间;二是用户更新代价较大,每次用户更新都需要下载整个Apk包,整个Apk包包括了...

xiao_nian的博客 7889

小米路由器R1D进阶玩法:MIXBOX工具箱安装与插件管理指南

本文详细介绍了为小米路由器R1D安装MIXBOX工具箱的完整指南。通过开启SSH、执行一键安装脚本,用户可以轻松解锁路由器的进阶功能,如安装离线下载、内网穿透等插件,从而将旧设备转变为强大的家庭网络服务中心。

weixin_29292431的博客 1114

【数字IC刷题】华为海思数字IC笔试题(1)详细解析版

【数字IC刷题】华为海思数字IC笔试题(1)详细解析版

MoorePlus的博客 4221

安卓插件化课程-第二篇:加载插件apk中的activity

本文是安卓插件化课程的第二篇,本篇主要讲的是如何启动一个插件中的Activity(有前提:前提是activity没有引用插件中的资源,并且提前已经在宿主中注册了)。

分享+记录 1908

23、OpenMP常见用例与标准循环级示例详解

本文详细介绍了OpenMP的三种典型用例:循环级OpenMP、高级OpenMP和MPI+OpenMP,适用于不同层次的并行需求。重点讲解了循环级OpenMP在向量加法、流三元组和模板计算中的应用示例,并探讨了首次触摸优化、线程绑定、NUMA架构影响等关键性能优化策略。通过实际代码演示和性能分析,帮助开发者快速掌握OpenMP的基础并行化方法,并为后续高性能优化提供指导。

fanta的博客 87

安卓插件化课程-第七篇:解决插件中activity跳转的问题

1.本文是安卓插件化课程的第七篇,完整课程链接参见下面链接: 2.前面几篇我们讲了,我们宿主启动插件Activity,使用的是启动HostActivity的方式。在宿主中给启动插件的Activity,我们可以改为启动HostActivity的方式,但是插件中怎么办呢?插件中的启动方式是startActivity已经是固定不变的了。 所以在插件中使用老的方式启动,仍然会遇到activity not found的问题,而本文就是解决这个问题。

分享+记录 2644

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

上一篇: StartActivity原理(二),Android9.0 Activity启动原理
下一篇: ViewStub原理,解决约束布局( constraintLayout)ViewStub约束失效问题,解决ViewStub约束失效约束不起作用的问题
cry kid
博客等级 码龄10年 144粉丝 24原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值