上一篇我们根据类加载的机制,在主应用中调用了插件中的方法。这次呢我们挑战下是否能够在主应用中调用到插件的页面,我们知道,要想调用一个页面必须要先声明这个页面,那我们能不能不声明这个页面调用到他呢,下面我们做下尝试。
在插件中新建一个简单的页面,然后我们用assembleDebug命令将插件打成apk包,然后呢我们还放到sdcard目录下,在主应用的点击事件中增加跳转应用页面代码如下:
Intent intent = new Intent();
intent.setComponent(new ComponentName("plugindemo.demo.com.plugintest","plugindemo.demo.com.plugintest.PluginMainActivity"));
startActivity(intent);
setComponent第一个参数是插件的包名,第二个参数是插件主Activity名。
点击页面按钮发现异常报错了,如下:
这个异常的意思是提示我们未在AndroidMainfest.xml中声明我们要跳转的页面PluginMainActivity,平时我们做的应用各个页面都要在此声明,否则无法访问,那这个问题我们怎么解决呢?
我们想要在主应用中访问另外一个App的页面,没有办法直接访问,必须要告诉AMS,让AMS帮我们打开,比如我要访问PluginMainActivity,告诉AMS我知道PluginMainActivity的地址路径和名称,你帮我打开吧,然后AMS经过一系列骚操作,最终帮你打开了你想要的页面,问题来了,那么我能不能编个假名字骗过AMS,然后我在假装就是假名字的本人,让AMS打开呢,下面我们先复习下Activity的启动流程,看看AMS做了啥骚操作帮你打开页面的,代码就不放了太多了,可以跟着我的时序图走一遍源码:
既然提示我们未声明要跳转的页面,那我们就给他声明一个,比如说我给他声明了一个PluginActivity的页面,具体如下:
<activity android:name=".PluginActivity" />
我们要用定义的这个PluginActivity来替换我们要访问的插件页面PluginMainActivity
我们要想骗过AMS,必须在访问AMS之前做操作,下面我们模拟下,直接上代码了,注释都写的很清楚了:
try {
//2.1获取到ActivityManager这个类
Class<?> clazz = Class.forName("android.app.ActivityManager");
//2.2从ActivityManager这个类中获取到IActivityManagerSingleton私有属性(
// (IActivityManager)ActivityManager.getService()
// =====>(Singleton<IActivityManager>)IActivityManagerSingleton.get()
// =========>public abstract class Singleton<T>--->public final T get()----->return mInstance----->private T mInstance
// )
Field singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
//2.3设置属性的作用域
singletonField.setAccessible(true);//作用域
//2.4获取到原来的对象singleton
Object singleton = singletonField.get(null);
//1.1获取Singleton类
Class<?> singletonClass = Class.forName("android.util.Singleton");
//1.2从Singleton类中获取mInstance私有属性
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
//1.3设置作用域
mInstanceField.setAccessible(true);
//2.5从原来的singleton对象中获取mInstance对象
final Object mInstance = mInstanceField.get(singleton);
Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
//1.先获取到proxyInstance这个对象
// IActivityManager 的Class 对象 第一步先获取到proxyIn这个对象 第二步反射替换这个对象
Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{iActivityManagerClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
int index = 0;
// 将插件的Intent 替换为 代理的Intent
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
// 获取到插件的intent
Intent intent = (Intent) args[index];
// 替换成代理的Intent
Intent proxyIntent = new Intent();
proxyIntent.setClassName("plugindemo.com.plugindemoone",
PluginActivity.class.getName());
// 保存插件的Intent
proxyIntent.putExtra(TARGET_INTENT, intent);
args[index] = proxyIntent;
}
// IActivityManager 对象 --- 通过反射
return method.invoke(mInstance, args);
}
});
// IActivityManager对象 = proxyInstance
// new IActivityManager();// 这个是系统的 == new IActivityManagerProxy();
// 找到singleton中的get方法的mInstance
//2.用proxyInstance对象替换原来的对象singleton达到替换效果,在这之前必须获取到原来的singleton
mInstanceField.set(singleton, proxyInstance);
} catch (Exception e) {
e.printStackTrace();
}
然后我们看下运行效果
看到这个提示了吧,我们主应用是访问的啥,是PluginMainActivity,现在我们成功的将要访问的页面通过偷梁换柱,改成了PluginActivity,为啥要换成这个页面呢,因为我们在xml中注册了这个页面,他是合法的,可以通过AMS的验证,到这里我们就成功了一半了,我们的目的是在通过AMS的验证之后,在想办法将PluginActivity换成PluginMainActivity,继续上第二步代码:
try {
//获取ActivityThread类
Class<?> clazz = Class.forName("android.app.ActivityThread");
//从ActivityThread类clazz中获取sCurrentActivityThread属性成员(private static volatile ActivityThread sCurrentActivityThread;)
Field sCurrentActivityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
//拿到activityThread对象
Object activityThread = sCurrentActivityThreadField.get(null);
//从ActivityThread类clazz中拿到mh属性成员(final H mH = new H();)
Field mHField = clazz.getDeclaredField("mH");
//
mHField.setAccessible(true);
//拿到mH对象
Object mH = mHField.get(activityThread);
//拿到Handler类
Class<?> handlerClass = Class.forName("android.os.Handler");
//从handlerClass中获取mCallback属性成员(final Callback mCallback;)
Field mCallbackField = handlerClass.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
// Handler 对象
// 创建一个 Callback 替换系统的 Callback 对象
//new 一个mCallback等于空发送handlerMessage(msg)替换系统的callbank对象 Handler的dispatchMessage方法
mCallbackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
// 替换Intent
switch (msg.what) {
//100值的说明:1.sendMessage(H.LAUNCH_ACTIVITY, r);
// 2.public static final int LAUNCH_ACTIVITY = 100(private class H extends Handler);
case 100://
// ActivityClientRecord == msg.obj
try {
// 获取PluginActivity的intent
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent proxyIntent = (Intent) intentField.get(msg.obj);
//获取插件的Intent
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
// 判断调用的是否是插件的,如果不是插件的,intent就会为空
if (intent != null) {
intentField.set(msg.obj, intent);
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
}
代码跑起来,我们在看下运行效果图:
老铁们,6不6,要是6就关注下公众号点赞转发走起来吧。