赞
踩
首选,当我们启动一个activity,在没有在配置文件注册的前提下,会抛出以下异常:
以下分析基于9.0源码,兼容代码可自行下载完整代码。
那么首先我们需要分析这个异常是在哪里出现的?源码跟踪如下:
在checkStartActivityResult()方法中就能看到上面的异常信息。那么如何才能不让系统报这个异常呢,那就需要在这个方法执行之前做些手脚了。查看系统源码,在执行checkStartActivityResult()方法之前,会执行AMS检查的方法,并把检查结果传给了这个方法,如下:
有了上面的异常分析,我们得到这样的结论,只要AMS检查通过,就不会有下面的异常,怎么样才能让它检查通过呢?继续分析源码
对于不同该系统版本,被定义的文件类型不一样,在26版本之前,它是一个java文件。26及之后版本它是一个aidl文件,无论如何它都是一个接口,里面都会有startActivity()方法。
最重要的一个参数就是第3个参数,携带我们的跳转信息。那么我们就可以在执行检查的时候拦截原来没有注册的activity替换成已经注册的代理activity,重新封装成intent,作为参数。这样就会检查通过。那么只有动态代理才能在系统执行之前,进行方法拦截。
当完成了ams的检查,下一步就是在系统启动activity之前,我们要把intent信息替换成我们目标intent。这样才能跳转到我们想要的界面。继续分析源码
ActivityThread源码1806行就是我们activity即将要启动的时刻,我们不能在方法中拦截,而是在方法之前拦截,这是一个handler消息回调方法。所以可以在消息分发的地方进行拦截我们需要的EXECUTE_TRANSACTION消息。
进入Hander源码97行开始,最终发现是通过mCallback进行消息分发的,也就是说只要我们将mCallback替换成自己的Callback,就可以拦截系统的消息,拿到系统消息后进一步做处理。
private void hookAMS_up_26() throws Exception { //1-------------拿到IActivityManager类 需要hook的类 Class<?> IActivityManagerClass = Class.forName("android.app.IActivityManager"); //2-------------拿到ActivityManager Class<?> activityManagerClass = Class.forName("android.app.ActivityManager"); //以下为了拿到IActivityManager对象 ,因为后续仍然需要它进行invoke public static IActivityManager getService() Method getServiceMethod = activityManagerClass.getMethod("getService"); getServiceMethod.setAccessible(true); final Object activityManagerObj = getServiceMethod.invoke(null); //本质就是IActivityManager Object activityManagerProxy = Proxy.newProxyInstance(this.getClassLoader(), new Class[]{IActivityManagerClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("startActivity".equals(method.getName())) { //跳转到代理activity Intent intent = new Intent(); intent.setClass(BaseApp.this, ProxyActivity.class); //并且将TestActivity信息携带过去 Intent preIntent = (Intent) args[2]; intent.putExtra("preIntent", preIntent); //第三个参数就是intent args[2] = intent; } //继续执行系统的方法 return method.invoke(activityManagerObj, args); } }); //拿到ActivityManager类 Class<?> activityManagerClass2 = Class.forName("android.app.ActivityManager"); //3-------------拿到类中的IActivityManagerSingleton属性 Field iActivityManagerSingleton = activityManagerClass2.getDeclaredField( "IActivityManagerSingleton"); iActivityManagerSingleton.setAccessible(true); //拿到属性所属的类对象---》Singleton Object singletonObj = iActivityManagerSingleton.get(null); //4------------拿到Singleton类 Class<?> singletonClass = Class.forName("android.util.Singleton"); //拿到类中的mInstance属性,mInstance就是系统的IActivityManager对象 Field mInstanceField = singletonClass.getDeclaredField("mInstance"); //授权 mInstanceField.setAccessible(true); //5-------------最终是将IActivityManagerClass类对象替换成我们的代理对象 //第一个参数,要操作的类对象 第二个参数替换的类对象 mInstanceField.set(singletonObj, activityManagerProxy); }
2、hook系统消息回调
/** * 8.0及以上 */ private class Callback_Up_26 implements Handler.Callback { private Handler mH; public Callback_Up_26(Handler mH) { this.mH = mH; } @Override public boolean handleMessage(Message msg) { switch (msg.what) { case EXECUTE_TRANSACTION: try { /* 1、mIntent所属对象在ClientTransaction 源码: private List<ClientTransactionItem> mActivityCallbacks; */ Class<?> clientTransactionClass = Class.forName("android.app" + ".servertransaction" + ".ClientTransaction"); Field mActivityCallbacksField = clientTransactionClass.getDeclaredField( "mActivityCallbacks"); mActivityCallbacksField.setAccessible(true); /* 2、拿到mActivityCallbacks所属对象,就是msg.obj; 源码:final ClientTransaction transaction = (ClientTransaction) msg.obj; */ Object transactionObj = msg.obj; //数组中的第1个就是LaunchActivityItem对象 List mActivityCallbacksObj = (List) mActivityCallbacksField.get(transactionObj); if (mActivityCallbacksObj == null || mActivityCallbacksObj.size() == 0) { return false; } Class<?> launchActivityItemClass = Class.forName("android.app" + ".servertransaction" + ".LaunchActivityItem"); Field mIntentField = launchActivityItemClass.getDeclaredField("mIntent"); mIntentField.setAccessible(true); //判断是不是launchActivityItem类型 if (!launchActivityItemClass.isInstance(mActivityCallbacksObj.get(0))) { return false; } //3、要替换的intent对象 Intent intentNow = (Intent) mIntentField.get(mActivityCallbacksObj.get(0)); //我们的目标intent Intent preIntent = intentNow.getParcelableExtra("preIntent"); if (preIntent != null) { mIntentField.set(mActivityCallbacksObj.get(0), preIntent); } } catch (Exception e) { e.printStackTrace(); } break; } return false; } }
最后 return false 的意义是继续交给系统进行消息分发,不能返回true。完成以上两步骤,运行发现,跳转到本类没有在配置文件中注册的activity是没有任何问题的。当我们尝试跳转到插件的activity,发现抛出如下异常:
进入ActivityThread源码2829行开始,伪代码如下:
这里还有两个操作,首先拿到类加载器,然后通调用newActivity方法,通过类加载器创建activity对象。
(1)类加载器
跟踪getClassLoader()方法,最终可在源码ClassLoader,224行返回PathClassLoader()对象。
(2)newActivity()
跟踪该方法:
最终进入父类的findClass方法,代码如下:
没错,这里就发现了我们之前的异常。当pathList中没有找到这个类,就会抛出异常,也就是DexPathList才是问题的关键。
它里面有一个dexElements数组,类里面会调用findClass()
遍历dexElements,没有找到就返回null。记住这是宿主的dexElements。每一个apk都会有dexElements数组,这是因为java虚拟机会将java文件编译成class文件,并且打包成dex文件。那么当我们需要加载类文件的时候就需要去执行dex文件,所以dexElements就是dex文件在内存中表现形式,真正可执行的文件是Element元素中的DexFile文件。既然知道了报错的原因是dexElements没有插件的dexElements。那么我们可不可以将插件的也添加进来,和宿主的组合成新的dexElements。
/** * 合并宿主和插件的dex文件 * 源码分心dexElements中包含插件和宿主的dex即可 */ private void combinedDex() throws Exception { //1、拿到宿主的dexElements [] //最终调用ContextImpl的getClassLoader,返回PathClassLoader PathClassLoader classLoader = (PathClassLoader) getClassLoader(); //拿到 private final DexPathList pathList; Class mBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader"); Field pathListClass = mBaseDexClassLoaderClass.getDeclaredField("pathList"); pathListClass.setAccessible(true); Object pathListObj = pathListClass.get(classLoader); //拿到DexPathList中的Element[]数组 private Element[] dexElements; Field appElementsField = pathListObj.getClass().getDeclaredField("dexElements"); appElementsField.setAccessible(true); Object appElementsObj = appElementsField.get(pathListObj); //2、拿到插件的dexElements [] /* 通过DexClassLoader加载插件apk的dex文件 参数1:插件包路径 参数2:使用DexClassLoader记载需要一个缓存路径 /data/data/插件包名/dexDir 参数3:需要的支持库 参数4:加载器 */ String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "q1.apk"; File file = this.getDir("dexDir", Context.MODE_PRIVATE); DexClassLoader pluginClassLoader = new DexClassLoader(apkPath, file.getAbsolutePath(), null, getClassLoader()); //拿到 private final DexPathList pathList; Class mPluginBaseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader"); Field dexPathListClass = mPluginBaseDexClassLoaderClass.getDeclaredField("pathList"); dexPathListClass.setAccessible(true); Object dexPathListObj = dexPathListClass.get(pluginClassLoader); //拿到DexPathList中的Element[]数组 private Element[] dexElements; Field dexElementsField = dexPathListObj.getClass().getDeclaredField("dexElements"); dexElementsField.setAccessible(true); Object dexElementsObj = dexElementsField.get(dexPathListObj); //3、合并成新的Elements[] int appElementLength = Array.getLength(appElementsObj); int pluginElementLength = Array.getLength(dexElementsObj); int totalLength = appElementLength + pluginElementLength; Object newElement = Array.newInstance(appElementsObj.getClass().getComponentType(), totalLength); for (int i = 0; i < totalLength; i++) { if (i < appElementLength) { //填充宿主的element Array.set(newElement, i, Array.get(appElementsObj, i)); } else { //填充插件的element Array.set(newElement, i, Array.get(dexElementsObj, i - appElementLength)); } } //4、将新的数组设置给宿主的Elements[] dexElementsField.set(pathListObj, newElement); //5、将插件资源添加到AssetManager中 loadAssetAndResource(apkPath); }
上面已经完成了插件class文件的加载,但是还需要加载插件的资源文件。才能在资源管理中找到插件的布局文件
对外提供getResource()方法:
并在插件module的基类BaseActivity中重写getResources()方法
这样就完成了插件的加载。
测试的话需要将插件apk放在sdCard目录下,并且不要忘记在宿主配置文件中添加访问权限。
代码做了5.0~9.0的兼容
hook式插件兼容代码传送门
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。