Android插件化原理(二)—启动插件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插件化原理(一)—— 插件类加载、类加载原理、(双亲委托机制)


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


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

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Android 插件是指将一个应用的功能分离为多个独立的模块,每个模块可以在运行时独立加载和卸载。这样可以优应用的体积和性能,同时也可以实现模块的动态更新和管理。插件在某些场景下被广泛运用,比如一个大型的应用需要提供不同的功能模块供用户选择,或者在多个应用中共享某些通用功能。插件的实现方式有很多种,其中最常用的是利用 Android插件框架和技术来实现。 通常情况下,一个 Android 应用的所有组件(Activity、Service、BroadcastReceiver、ContentProvider 等)都被编译打进同一个 APK 文件中,这也就意味着只有在应用安装和更新的时候才能进行组件的更新和修改,无法实现动态更新。而插件技术则打破了这种限制,它将应用的不同组件打成不同的插件(APK)文件,然后在运行时动态加载和卸载。这样一来,我们可以实现在不停止应用的情况下对部分组件进行更新、拓展、甚至是删除。 为了实现插件,我们需要解决一些技术难点。其中最主要的是解决插件和宿主的交互问题。在插件和宿主中间需要进行很多数据传递、资源访问和类加载等操作,如果没有基础的交互机制,插件是无法成功实现的。因此,在 Android 中,插件相关的机制主要括四个方面:类加载器(ClassLoader)、组件的注册和管理、资源访问和切换主题(Theme)等。 总体来看,插件技术为 Android 应用带来了更大的可拓展性和灵活性,这对于一些复杂或大型的应用非常重要。同时,基于插件技术,也催生出了一些全新的业态,比如插件市场和插件开发社区,为 Android 生态系统带来了更多的创新力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值