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


    前言

    上一篇我们学习了类加载机制原理以及如何调用插件apk的dex方法Android插件化学习之初识类加载机制,这一篇我们紧接着上一篇的内容,尝试去启动插件apk中未注册的activity。

    实现思路

    Activity启动流程

    既然想要实现启动插件中未注册的activity,那么就必须对AMS启动activity流程有深刻的认知;AMS启动activity详细流程分析可参考:Android启动流程
    这里我们简单看下activity的启动流程图:
    Activity启动流程示意图

    实现思路

    实现思路流程
    我们想要启动插件中的activity,由于插件中activity没有在清单文件中注册,就必须绕开AMS中activity的注册检查,因此我们的思路是:
    1.Hook AMS:在调用startActivity后,首先替换成我们在宿主apk中定义的ProxyActivity,由于ProxyActivity是在宿主中已注册过的,因此AMS做检查时,就可以轻而易举绕开;
    2.Hook Hander H:我们知道startActivity最终会交给ApplicationThreadHandler H去处理启动activity的消息,在这里,我们再将ProxyActivity替换成我们要启动的插件activity,完成最终activity启动;
    经过上述流程分析下来,是不是比较简单,在这里我实现的技术主要用到动态代理反射,对这一块不熟悉的小伙伴可以先学习下;

    代码流程

    • 定义插件PluginActivity并打包插件apk,存放至sdcard路径下【这里为了图方便,实现开发应存放至/data/data/package/目录下】

    PluginActivity,代码比较简单,注意注释下加载布局,先不加载资源,否则会出错;

    class PluginActivity : AppCompatActivity() {
       override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           //先不加载资源
           //setContentView(R.layout.activity_plugin)
          Log.e(PluginActivity::class.java.simpleName,"onCreate:启动插件Activity")
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    插件apk存放图

    • 定义宿主apk中的启动插件PluginActivity方法以及ProxyActivity【均需要在清单文件中注册】

    启动插件Activity

    class JumpActivity : AppCompatActivity() {
       override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           setContentView(R.layout.activity_plugin)
           findViewById<Button>(R.id.btn).setOnClickListener {
               //跳转至插件里的类
               val intent = Intent()
               intent.component = ComponentName("com.dongxian.plugin","com.dongxian.plugin.PluginActivity")
               startActivity(intent)
           }
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    ProxyActivity

    class ProxyActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            Log.e(ProxyActivity::class.java.simpleName, "onCreate")
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 合并插件apk dex文件至宿主apk中
    public class LoadUtils {
    
        public static void loadPluginDexFile(Context context) {
            try {
                Class<?> baseDexClassLoaderClazz = Class.forName("dalvik.system.BaseDexClassLoader");
                Field pathListField = baseDexClassLoaderClazz.getDeclaredField("pathList");
                pathListField.setAccessible(true);
    
                Class<?> dexPathListClazz = Class.forName("dalvik.system.DexPathList");
                Field dexElementsField = dexPathListClazz.getDeclaredField("dexElements");
                dexElementsField.setAccessible(true);
    
                //获取宿主dexElements
                ClassLoader hostClassLoader = context.getClassLoader();
                Object hostPathList = pathListField.get(hostClassLoader);
                Object[] hostDexElements = (Object[]) dexElementsField.get(hostPathList);
    
                //获取插件dexElements
                DexClassLoader pluginDexClassLoader = new DexClassLoader("/sdcard/plugin.apk", context.getCacheDir().getAbsolutePath(), null, hostClassLoader);
                Object pluginPathList = pathListField.get(pluginDexClassLoader);
                Object[] pluginDexElements = (Object[]) dexElementsField.get(pluginPathList);
    
                //合并两个dex Element数组
                Object[] resultDexElements = (Object[]) Array.newInstance(hostDexElements.getClass().getComponentType(),hostDexElements.length + pluginDexElements.length);
                System.arraycopy(hostDexElements, 0, resultDexElements, 0, hostDexElements.length);
                System.arraycopy(pluginDexElements, 0, resultDexElements,  hostDexElements.length, pluginDexElements.length);
                //将合并的结果复制给宿主dex Elements
                dexElementsField.set(hostPathList, resultDexElements);
            } catch (Exception e) {
                e.printStackTrace();
                Log.e("LoadUtils", e.toString());
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • Hook AMS流程【需要注意版本适配】
        private static final String HOOK_INTENT_DATA = "hook_intent_data";
    
       /**
        * hook  startActivity 到 AMS流程,重点是替换intent中的启动的目标activity
        * 关键代码,ActivityTaskManager.getService()是接口,可以考虑通过动态代理方式 代理startActivity方法,进行hook
        * int result = ActivityTaskManager.getService().startActivity(whoThread,
        * who.getBasePackageName(), who.getAttributionTag(), intent,
        * intent.resolveTypeIfNeeded(who.getContentResolver()), token,
        * target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
        */
       public static void hookAMS(Context context) {
           Field singletonFiled = null;
           try {
               // 小于8.0
               if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
                   Class<?> clazz = Class.forName("android.app.ActivityManagerNative");
                   singletonFiled = clazz.getDeclaredField("gDefault");
                   //小于10.0
               } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
                   Class<?> activityTaskManagerClazz = Class.forName("android.app.ActivityManager");
                   singletonFiled = activityTaskManagerClazz.getDeclaredField("IActivityManagerSingleton");
               } else {
                   Class<?> activityTaskManagerClazz = Class.forName("android.app.ActivityTaskManager");
                   singletonFiled = activityTaskManagerClazz.getDeclaredField("IActivityTaskManagerSingleton");
               }
    
               singletonFiled.setAccessible(true);
               //由于private static final Singleton IActivityTaskManagerSingleton 是静态的 ,可以直接获取对应值
               Object singleton = singletonFiled.get(null);
    
               Class<?> singletonClass = Class.forName("android.util.Singleton");
               Field mInstanceField = singletonClass.getDeclaredField("mInstance");
               mInstanceField.setAccessible(true);
               //通过activityTaskManagerSingleTon对象的mInstance属性就可以获取ActivityTaskManager.getService()的值,正是我们要代理的对象
    
               Object mInstance = mInstanceField.get(singleton);
               Class<?> iActivityManagerClass = null;
               if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
                   //IActivityManager#startActivity   @UnsupportedAppUsage(  maxTargetSdk = 29) 受限制的灰名单,直接反射 Class.forName 无法代理到startActivity方法
                   iActivityManagerClass = Class.forName("android.app.IActivityManager");
               } else {
                   iActivityManagerClass = Class.forName("android.app.IActivityTaskManager");
               }
               ClassLoader parent = context.getClassLoader().getParent();
               Object proxyInstance = Proxy.newProxyInstance(parent, new Class[]{iActivityManagerClass}, new InvocationHandler() {
                   @Override
                   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                       //循环遍历找到intent
                       if ("startActivity".equals(method.getName())) {
                           int intentIndex = -1;
                           for (int i = 0; i < args.length; i++) {
                               if (args[i] instanceof Intent) {
                                   intentIndex = i;
                                   break;
                               }
                           }
                           //原有的intent数据,需要保存起来,便于后面替换回来
                           Intent targetIntent = (Intent) args[intentIndex];
                           Intent proxyIntent = new Intent();
                           proxyIntent.setClassName("com.dongxian.studytotaldemo", "com.dongxian.studytotaldemo.ProxyActivity");
                           proxyIntent.putExtra(HOOK_INTENT_DATA, targetIntent);
                           args[intentIndex] = proxyIntent;
                       }
    
                       //返回值表示是否执行原有方法,这里保持原有方法不变
                       return method.invoke(mInstance, args);
                   }
               });
               //把代理对象复制给原来的对象
               mInstanceField.set(singleton, proxyInstance);
           } catch (Exception e) {
               e.printStackTrace();
               Log.e(HookUtils.class.getSimpleName(), e.toString());
           }
    
    
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • Hook Hander【需要注意版本适配】
      /**
         * hook ActivityThread 流程 AMS 到launchActivity,把intent替换回来
         */
        public static void hookHandler() {
            try {
                Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
                Field currentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
                currentActivityThreadField.setAccessible(true);
                Object sCurrentActivityThread = currentActivityThreadField.get(null);
                Field mHField = activityThreadClazz.getDeclaredField("mH");
                mHField.setAccessible(true);
                Handler mH = (Handler) mHField.get(sCurrentActivityThread);
    
                Field mCallbackField = Handler.class.getDeclaredField("mCallback");
                mCallbackField.setAccessible(true);
                Handler.Callback callback = new Handler.Callback() {
                    @Override
                    public boolean handleMessage(@NonNull Message msg) {
                        //表示启动activity
                        switch (msg.what) {
                            case 100:
                                try {
                                    //msg.obj  == ActivityClientRecord [ActivityClientRecord中存在intent对象]
                                    Field intentField = msg.obj.getClass().getDeclaredField("intent");
                                    intentField.setAccessible(true);
                                    Intent intent = (Intent) intentField.get(msg.obj);
                                    if (intent != null && intent.getExtras() != null && intent.getExtras().get(HOOK_INTENT_DATA) instanceof Intent) {
                                        Intent target = (Intent) intent.getExtras().get(HOOK_INTENT_DATA);
                                        intentField.set(msg.obj, target);
                                    }
                                } catch (Exception e) {
                                    e.printStackTrace();
                                    Log.e(HookUtils.class.getSimpleName(), e.toString());
                                }
                                break;
                            case 159:
                                //android10适配,启动流程有所变化
                                Log.e("HookUtils", "this is Android High Version");
                                try {
                                    //获取 List mActivityCallbacks对象
                                    Field mActivityCallbacksField = msg.obj.getClass().getDeclaredField("mActivityCallbacks");
                                    mActivityCallbacksField.setAccessible(true);
                                    //获取mActivityCallbacks对象对应的 List
                                    List<ClientTransactionItem> items = (List<ClientTransactionItem>) mActivityCallbacksField.get(msg.obj);
                                    for (ClientTransactionItem item : items) {
                                        //找到启动activity对应的item对象
                                        if ("android.app.servertransaction.LaunchActivityItem".equals(item.getClass().getName())) {
                                            //修改其中的intent数据
                                            Field intentField = item.getClass().getDeclaredField("mIntent");
                                            intentField.setAccessible(true);
                                            Intent targetIntent = (Intent) intentField.get(item);
                                            if (targetIntent != null && targetIntent.getExtras() != null && targetIntent.getExtras().get(HOOK_INTENT_DATA) instanceof Intent) {
                                                Intent target = (Intent) targetIntent.getExtras().get(HOOK_INTENT_DATA);
                                                intentField.set(item, target);
                                            }
                                        }
                                    }
                                } catch (Exception e) {
                                    Log.e(HookUtils.class.getSimpleName(), e.toString());
                                    e.printStackTrace();
                                }
                                break;
                            default:
                                break;
                        }
    
                        //结果一点要反正false
                        return false;
                    }
                };
    
                //结果替换
                mCallbackField.set(mH, callback);
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(HookUtils.class.getSimpleName(), e.toString());
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 代码中调用
    class MyApplication : Application() {
    
       override fun onCreate() {
           super.onCreate()
           LoadUtils.loadPluginDexFile(this)
           HookUtils.hookAMS(this)
           HookUtils.hookHandler()
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    总结

    其实我们发现最大的难点是做各个版本适配,因为Android启动流程每个版本的更新,那就需要我们每更新一个版本就需要去阅读源码进行相关适配,这是一个让人比较头疼的问题,因此插件化方案也在慢慢被抛弃,但我们通过学习阅读源码,去尝试实现各种黑科技,理解插件化方案实现的思想,这又何尝不是一种进步呢;

    结语

    如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

  • 相关阅读:
    Python基于OpenCV的交通路口红绿灯控制系统设计
    Linux_用户管理
    信奥中的数学:斐波那契数列
    基于S2SH的保险业务管理系统【数据库设计、源码、开题报告】
    【教3妹学算法-每日1题】竞赛题:矩阵中的局部最大值
    Linux驱动调试之printk的使用
    Spring 6 提前编译:AOT
    静态博客搭建工具汇总
    vertx hello gradle 打包jar
    数据mock和node.js的安装与下载
  • 原文地址:https://blog.csdn.net/a734474820/article/details/126095294