上一篇我們根據類加載的機制,在主應用中調用了插件中的方法。這次呢我們挑戰下是否能夠在主應用中調用到插件的頁面,我們知道,要想調用一個頁面必須要先聲明這個頁面,那我們能不能不聲明這個頁面調用到他呢,下面我們做下嘗試。
在插件中新建一個簡單的頁面,然後我們用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就關注下公衆號點贊轉發走起來吧。