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

 序:

1.本文是安卓插件化课程的第三篇,完整课程链接参见下面链接:

安卓插件化课程-序章_分享+记录-CSDN博客前言:目前安卓领域,插件化十分的流行,本以为这一类的文章会有很多,但是百度一搜,基本上讲的插件化都是皮毛,没有涉及到核心。所以就想写一系列的文章来一步一步深入的讲解插件化,通过实现逐渐增加难度的需求,最终实现插件化方案。章节:插件化系列文章主要包含以下几个篇章:1.加载插件apk中的类2.启动插件apk中的activity3.使用插桩的方式启动apk中的activity4.使用插件apk中的资源5.使用插件apk中的so文件6.插件化加载移动apk。7.插件化目前所遇到的https://blog.csdn.net/rzleilei/article/details/122453035?spm=1001.2014.3001.5502

2.主要内容

本篇主要讲的是如何启动一个插件中的Activity。这里仍人是有前提的,前提条件变成插件activity中不能引用插件包中的资源,但是不需要提前在宿主mainfest中注册插件activity了。

一:原理简述


1.activity启动流程简述

启动流程不是本篇介绍的主要核心,所以这里简略的说一下启动流程(这里和上一章相同):

1.Activity.startActivity -> Instrumetation.execStartActivity -> 通过binder机制通知AMS。

2.AMS收到通知后,进行一系列的检查,最后也通过binder通知APP去执行创建等一系列的操作。APP端的binder实体类就是ActivityThread中的ApplicationThread。

这一系列的检查中,就包含mainfest的声明检查,本篇中我们先不hook,下一篇再讲。

3.ApplicationThread收到通知后,调用ActivityThread中的方法去完成Activity的创建。

2.系统是如何生成activity的

1.而处理创建activity的方法是handleLaunchActivity,它交给performLaunchActivity来完成最终的创建。

2.我们看一下最终创建的代码,传入classLoader,交给Instrumetation完成Activity的创建。

java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);

3.Instrumetation中newActivity方法

这里又通过Factory工厂类去完成创建

public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        String pkg = intent != null && intent.getComponent() != null
                ? intent.getComponent().getPackageName() : null;
        return getFactory(pkg).instantiateActivity(cl, className, intent);
    }

3.如何绕过manifest检查

第一步我们了解到,检查是系统层去做的,确实很难绕过。但是加载activity是app层去做的,这里我们是可以做一些操作的,目前比较流程的方式就是埋桩。即告诉系统启动的是activityA,但是真正生成的时候,启动的是activityB(插件中的)。

我们可以提前在宿主中声明一个activityA,启动插件APP中的activity时,改为启动A。这样的话,系统检查肯定是可以过的,因为本身activityA就是有注册。但是真正的生成时,我们在第1.2.3步,重写newActivity方法,返回一个插件中的activityB就好了。

4.如何Hook newActivity方法

hook newActivity方法的核心就是替换掉Instrumentation,用我们自定义的Instrumentation去替代原生的。这样我们就可以重写newActivity这个方法了。

我们看一下ActivityThread的源码,可以知道Instrumentation其实也是ActivityThread的一个成员变量,变量名为:mInstrumentation。所以我们只要通过反射,拿到ActivityThread对象,然后替换掉这个对象中的mInstrumentation变量就可以了。

二:代码编写


1.插件项目中创建Plugin2Activity

因为没有实现资源的加载,所以这里的activity没有引用任何资源。

package com.xt.appplugin;

import android.app.Activity;
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.TextView;

public class Plugin2Activity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        LinearLayout linearLayout = new LinearLayout(getApplicationContext());
        TextView textView = new TextView(getApplicationContext());
        textView.setText("这是插件类Plugin2Activity");
        linearLayout.addView(textView);
        setContentView(linearLayout);
    }
}

2.编译项目,拷贝apk到app的assets目录

3.在宿主中埋桩

这里注册的activity,以后让系统进行检查的也是这个,但是最终生成时我们替换掉。

        <activity android:name="com.xt.client.HostActivity" />


4.自定义Instrumentation

自定义的Instrumentation中,获取即将要被启动的activity,如果是我们埋桩,则获取真正要启动的类名,然后通过插件的classLoader进行加载,

public class MyInstrumentation extends Instrumentation {

    public static final String ClassLoader = "classLoader";
    public static final String ClassName = "pluginClassName";

    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        //拦截指定的className
        if (!className.equals("com.xt.client.HostActivity")) {
            return super.newActivity(cl, className, intent);
        }
        String pluginClassName = intent.getStringExtra(ClassName);
        //插件的classLoader
        Object classLoader = ObjectCache.getInstance().getParams(ClassLoader);
        if (classLoader instanceof ClassLoader) {
            Class<?> aClass = ((ClassLoader) classLoader).loadClass(pluginClassName);
            Object o = aClass.newInstance();
            return (Activity) o;
        }

        return super.newActivity(cl, className, intent);
    }
}

5.替换Instrumentation

if (position == 3) {
            /**
             * 启动acitivyt,通过插桩的方式进行
             * hook Instrumentation
             */
            val myInstrumentation =
                MyInstrumentation()
            //替换Acitivty中的mInstrumentation

            val classLoader = javaClass.classLoader
            val loadedApkClass = classLoader.loadClass("android.app.LoadedApk")
            val activityThreadClass = classLoader.loadClass("android.app.ActivityThread")


            val activityThreadField =
                activityThreadClass.getDeclaredField("sCurrentActivityThread")
            activityThreadField.isAccessible = true
            val activityThreadGet = activityThreadField.get(null)

            val instrumentationField = activityThreadClass.getDeclaredField("mInstrumentation")
            instrumentationField.isAccessible = true

            instrumentationField.set(activityThreadGet, myInstrumentation)

            //hook Instrumentation之后,就可以启动activity
            val intent = Intent(context, HostActivity::class.java)
            intent.putExtra(MyInstrumentation.ClassName, "com.xt.appplugin.Plugin2Activity")
            startActivity(intent)
            return
        }


6.测试验证

1.我们启动的插件activity的方式改了下,改成如下的方式。

val intent = Intent(context, HostActivity::class.java)
            intent.putExtra(MyInstrumentation.ClassName, "com.xt.appplugin.Plugin2Activity")
            startActivity(intent)

2.先点击加载插件,这时候会加载插件中的apk。并且生成对应的classLoader并且加入到缓冲中。

val apkPath = context.filesDir.absolutePath + File.separator + APK_NAME
            val odexPath = context.filesDir.absolutePath + File.separator + APK_ODEX
            selfClassLoader = DexClassLoader(apkPath, odexPath, null, javaClass.classLoader)
            ObjectCache.getInstance().setParams(MyInstrumentation.ClassLoader, selfClassLoader)

3.点击启动apk插件中activity(无需manifest注册)

 这时候,我们发现插件成功启动了。

三:要点总结

1.与第二章不同的是,本章我们hook的对象是Instrumentation,而不是classLoader。所以其实实现插件化有很多方式很多点可以hook。

2.由于是提前埋桩,所以在manifest中声明的一些属性会丢失,所以我们要把这部分的属性挪到activity中去实现。这样对插件化的便捷使用还是有一些影响的。

3.如果是加载多个插件的情况,那对应的就会有多个classLoader,我们可以根据包名来判断到底使用哪个classLoader来进行加载。

四。代码地址:


项目地址:

https://github.com/aa5279aa/android_all_demo

插件项目位置:https://github.com/aa5279aa/android_all_demo/tree/master/DemoClient/appplugin

调用类位置:https://github.com/aa5279aa/android_all_demo/blob/master/DemoClient/app/src/main/java/com/xt/client/fragment/DynamicFragment.kt

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值