根據類加載機制探究項目插件化過程(二)

上一篇我們根據類加載的機制,在主應用中調用了插件中的方法。這次呢我們挑戰下是否能夠在主應用中調用到插件的頁面,我們知道,要想調用一個頁面必須要先聲明這個頁面,那我們能不能不聲明這個頁面調用到他呢,下面我們做下嘗試。

在插件中新建一個簡單的頁面,然後我們用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就關注下公衆號點贊轉發走起來吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章