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

本文深入探讨了Android插件化中如何启动未注册Activity,通过Hook AMS和Handler,实现了绕过注册检查启动插件Activity。主要涉及动态代理、反射以及版本适配等技术,详细分析了启动流程,并提供了具体代码实现。虽然插件化方案面临版本适配挑战,但其背后的原理和实践对于开发者来说仍具有学习价值。
摘要由CSDN通过智能技术生成

前言

上一篇我们学习了类加载机制原理以及如何调用插件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")
   }
}

插件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)
       }
   }
}

ProxyActivity

class ProxyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.e(ProxyActivity::class.java.simpleName, "onCreate")
    }
}
  • 合并插件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());
        }
    }
}
  • 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<IActivityTaskManager> 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());
       }


   }
  • 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<ClientTransactionItem> mActivityCallbacks对象
                                Field mActivityCallbacksField = msg.obj.getClass().getDeclaredField("mActivityCallbacks");
                                mActivityCallbacksField.setAccessible(true);
                                //获取mActivityCallbacks对象对应的 List<ClientTransactionItem>
                                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());
        }
    }
  • 代码中调用
class MyApplication : Application() {

   override fun onCreate() {
       super.onCreate()
       LoadUtils.loadPluginDexFile(this)
       HookUtils.hookAMS(this)
       HookUtils.hookHandler()
   }
}

总结

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

结语

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值