天天看點

Android 插件化開發——啟動沒有在ActivityManifest中聲明的Activity(二)

要解決這個問題,首先回顧一下啟動一個新的Activity的流程:

  1. 要啟動ActivityB,将要啟動的ActivityB資訊告訴AMS;
  2. AMS收到資訊後,記錄下ActivityB的相關資訊,同時檢查Manifest中是否已經注冊了ActivityB,如果一切正常,則回傳消息給ActivityThread:我接收到了!你可以paused了!
  3. ActivityA進入Paused狀态,再告訴AMS:我已經休眠了!
  4. AMS開始啟動的相關準備操作,檢視ActivityA和ActivityB是否在同一程序,然後取出之前儲存的ActivityB的相關資訊,發送給ActivityThread,說:你建立并啟動ActivityB。
  5. ActivityThread接收到資訊之後,通過Instrumentation反射建立ActivityB,并且啟動ActivityB。

要啟動沒有在Manifest中聲明的Activity有一個難點:

要怎樣規避上述步驟2中的AMS檢查Manifest中是否有注冊?

對于這個問題,可能有的會說:hook AMS可以麼? 答案肯定是不行的,因為AMS如果可以進行Hook,那麼Android系統早就崩潰了,要知道所有App底層都是公用的AMS。 既然這樣,隻能從上層動手,也就是上面的步驟中想解決辦法。

“欺騙”AMS方案

對于“欺騙”AMS方案,可以這樣了解,因為AMS是被動接收資料的,也就是說AMS負責校驗ActivityThread發送過來的資料,想一下:如果我發送過來的确确實實是已經在Manifest中已經注冊過的ActivityC呢? 然後我把ActivityB的相關資訊都儲存在ActivityC的啟動資訊中,這樣AMS是不是就被騙了?答案是肯定的!

上述步驟1,就順利的執行了,等到了步驟5, 我們又可以做手腳了,我們通過hook,攔截Instrumentation的啟動ActivityC的方法,改為建立ActivityB,也就是偷梁換柱。

說白了我們“欺騙”AMS的方案就兩步驟:

  1. 攔截上述步驟1,把要啟動的ActivityB的相關資訊更換為已經在Manifest中注冊的ActivityC,并且把ActivityB相關資訊儲存起來,塞入ActivityC的Intent中,因為之後我們還要啟動ActivityB,這樣一來就成功的“欺騙”了AMS。
  2. 攔截上述步驟5, 取出ActivityB的相關資訊,并且建立ActivityB。

下面是流程圖:

Android 插件化開發——啟動沒有在ActivityManifest中聲明的Activity(二)

Hook步驟1: 替換Activity

對于攔截替換Activity,上一篇部落格有講述,我們可以選擇一勞永逸的方法:Hook ActivityManager。

Android 插件化開發——對startActivity方法進行hook(一)

這種方法同時适用于Activity和Content的startActivity。

/**
     * hookManifest
     */
    private void hookManifest() {
        //擷取IActivityManagerSingleton對象
        Object iActivityManagerSingleton =
                ReflexUtil.getStaticFieldValue("android.app.ActivityManager", "IActivityManagerSingleton");
        //擷取單例對象中的mInstance字段
        Object mSingletonInstance =
                ReflexUtil.getFieldValue("android.util.Singleton", iActivityManagerSingleton, "mInstance");
        Class<?> classInterface = null;
        try {
            classInterface = Class.forName("android.app.IActivityManager");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //建立自己的代理對象
        Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[]{classInterface},
                new HookManifest(mSingletonInstance));
        //将單例對象中的字段替換
        ReflexUtil.setFieldValue("android.util.Singleton", iActivityManagerSingleton, "mInstance", proxyInstance);
    }
           
/**
 * author: liumengqiang
 * Date : 2019/7/26
 * Description : 替換Activity,欺騙AMS
 */
public class HookManifest implements InvocationHandler {

    private Object mBase;

    public HookManifest(Object mBase) {
        this.mBase = mBase;
    }

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        if("startActivity".equals(method.getName())) {
            Log.e("--------:", "hook successful ");
            Intent replaceIntent = null;
            int i = 0;
            for(int index = 0; index < objects.length; index ++) {
                if(objects[index] instanceof Intent) {
                    i = index;
                    replaceIntent = (Intent) objects[index];
                    break;
                }
            }

            Intent newIntent = new Intent();
            String stubPackage = replaceIntent.getComponent().getPackageName();
            ComponentName componentName = new ComponentName(stubPackage, SecondActivity.class.getName());
            newIntent.setComponent(componentName);

            newIntent.putExtra("Extra_data", replaceIntent);

            objects[i] = newIntent;

            return method.invoke(mBase, objects);
        }

        return method.invoke(mBase, objects);

    }
}
           

對于HookManifest類的作用,也就是替換Activity,替換成已經在Manifest中已經注冊的SecondActivity.

Hook步驟2: 替換我們真正要啟動的Activity

對于步驟2 , 我們需要明白一點:在之前Activity啟動流程分析中,我們知道,Activity的建立是在Instrumentation中建立的,并且是:

public Activity newActivity(ClassLoader cl, String className, Intent intent)
           

是以,我們可以攔截Instrumentation的newActivity 方法,包裝一下:取出我們要啟動的ThreadActivity, 最後執行真正Instrumentation的newActivity方法,

/**
     *
     */

    private void hookActivityThread() {
        Object currentActivityThread = ReflexUtil.invokeStaticMethod("android.app.ActivityThread",
                "currentActivityThread");
        Instrumentation mInstrumentation =
                (Instrumentation) ReflexUtil.getFieldValue("android.app.ActivityThread",
                        currentActivityThread, "mInstrumentation");
        HookNextHelper hookNextHelper = new HookNextHelper(mInstrumentation);
        ReflexUtil.setFieldValue("android.app.ActivityThread",
                currentActivityThread, "mInstrumentation",hookNextHelper);
    }
           
/**
 * author: liumengqiang
 * Date : 2019/7/26
 * Description :
 */
public class HookNextHelper {

    private Instrumentation mBase;

    public HookNextHelper(Instrumentation mBase) {
        this.mBase = mBase;
    }

    public Activity newActivity(ClassLoader cl, String className,
                                Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        Intent newIntent = intent.getParcelableExtra("Extra_data");
        if(newIntent != null) {
            String newClassName = newIntent.getComponent().getClassName();
            return mBase.newActivity(cl, newClassName, newIntent);
        }
        return mBase.newActivity(cl, className, intent);
    }
}
           

這樣就啟動了沒有在ActivityManifest中聲明的Activity!

相關博文:

  1. Android 插件化開發——基礎底層知識(Binder,AIDL)
  2. Android 插件化開發——基礎底層知識(Context家族史,Activity啟動流程)
  3. Android 插件化開發——基礎底層知識(Service)
  4. Android 插件化開發——基礎底層知識(反射)
  5. Android 插件化開發——基礎底層知識(代理模式)
  6. Android 插件化開發——對startActivity方法進行hook(一)

繼續閱讀