插件化入門篇-如何啟動(dòng)一個(gè)未注冊(cè)過的Activity

幾乎所有的插件化都會(huì)要的一個(gè)需求,啟動(dòng)一個(gè)未注冊(cè)的Activiy,即加載插件包中的Activity,并且主應(yīng)用并不知道插件應(yīng)用中會(huì)有什么Activity,這是各個(gè)插件化框架主力解決的問題之一。

今天我們學(xué)習(xí)一下占坑式插件化框架的啟動(dòng)Activity原理。

關(guān)于動(dòng)態(tài)代理的知識(shí),了解過Retrofit的源碼的或者看過Java設(shè)計(jì)模式之代理模式的高級(jí)使用的,應(yīng)該都了解了。本章不做介紹,主介紹hook+反射

Hook是什么?

Hook直白點(diǎn)說就是攔截方法,自己對(duì)其參數(shù)等進(jìn)行修改,或者替換返回值,達(dá)到自己不可告人的目的的一件事。

尋找Hook點(diǎn)

對(duì)于啟動(dòng)Activity,老實(shí)說光startActivity便有很多要說,很多文章會(huì)帶著你一直追到ActivityManagerService中的若干個(gè)方法,最后再調(diào)用本地的ActivityThread里面的方法去啟動(dòng)本進(jìn)程的Activity。

所以光上面的流程我們看出,我們把要啟動(dòng)的Activity信息發(fā)給AMS,其做了各種檢查各種操作后真正讓Activity啟動(dòng)的還是我們的ActivityThread

startActivity流程

我們startActivity是context的方法,去找Context實(shí)現(xiàn)類class ContextImpl extends Context

    @Override
    public void startActivities(Intent[] intents) {
        warnIfCallingFromSystemProcess();
        startActivities(intents, null);
    }
        /** @hide */
    @Override
    public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
        if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
            throw new AndroidRuntimeException(
                    "Calling startActivities() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivitiesAsUser(
            getOuterContext(), mMainThread.getApplicationThread(), null,
            (Activity)null, intents, options, userHandle.getIdentifier());
    }

看到最后調(diào)用的是mMainThread.getInstrumentation().execStartActivitiesAsUser方法,不用著急,直接ctrl鼠標(biāo)左擊進(jìn)去。是Instrumentation類。

    public void execStartActivitiesAsUser(Context who, IBinder contextThread,
            IBinder token, Activity target, Intent[] intents, Bundle options,
            int userId) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    if (am.match(who, null, intents[0])) {
                        am.mHits++;
                        if (am.isBlocking()) {
                            return;
                        }
                        break;
                    }
                }
            }
        }
        try {
            String[] resolvedTypes = new String[intents.length];
            for (int i=0; i<intents.length; i++) {
                intents[i].migrateExtraStreamToClipData();
                intents[i].prepareToLeaveProcess();
                resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver());
            }
            int result = ActivityManagerNative.getDefault()
                .startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes,
                        token, options, userId);
            checkStartActivityResult(result, intents[0]);
        } catch (RemoteException e) {
        }
    }

這邊我們看到了,是調(diào)用ActivityManagerNative的方法啟動(dòng)activity了。進(jìn)去這個(gè)類我們只能看到一堆的binder通信,調(diào)用AMS的方法,不過此時(shí)我們不用關(guān)心了,因?yàn)槲覀冎澜酉聛硎?br> AMS的事情。AMS是活在另一個(gè)PID的玩意兒,我們只關(guān)心我們自己的pid,另一個(gè)進(jìn)程的東西我們沒權(quán)限干壞事。

不過這邊我們需要注意,ActivityManagerNative居然是個(gè)單類,那么我們hook它會(huì)安全很多,畢竟這個(gè)對(duì)象是單類。

    static public IActivityManager getDefault() {
        return gDefault.get();
    }

    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };

說是說AMS的事情不用關(guān)心,但是我們得關(guān)心AMS什么時(shí)候回調(diào)回來,讓我們啟動(dòng)Activity。

ActivityThread看,一搜里面有個(gè)handleLaunchActivity方法,是在Handler里面被調(diào)用的,而且ActivityThread也是我們喜歡的對(duì)象,因?yàn)檫@個(gè)對(duì)象存在于整個(gè)應(yīng)用生命周期中。

        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {   //這個(gè)值是常量100
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    ActivityClientRecord r = (ActivityClientRecord)msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;

看了這么多,我們可算是知道啟動(dòng)Activity的入口和出口了,下面我們需要進(jìn)行欺騙。

實(shí)現(xiàn)欺騙

欺騙系統(tǒng)就欺騙兩個(gè)地方,我們?cè)?code>AndroidManifest里面申明一個(gè)假Activity,然后在啟動(dòng)真實(shí)Activity的地方,將Intent里面的Activity替換成我們已經(jīng)注冊(cè)過的。再在ActivityThread launch Activity的時(shí)候,替換成我們需要啟動(dòng)的便實(shí)現(xiàn)了啟動(dòng)一個(gè)未注冊(cè)過的Activity的效果。

代碼實(shí)現(xiàn)

  • 寫一個(gè)占坑Activity,在AndroidManifest注冊(cè)
        /**
         * 占坑專用
         */
        public class TmpActivity extends Activity {

            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_tmp);
            }
        }

        <!--占坑專用Activity-->
        <activity android:name=".TmpActivity"/>
  • attachBaseContext中欺騙應(yīng)用

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);

        try {
            /**
             * 欺騙ActivityManagerNative,將要啟動(dòng)的Activity替換成我們的占坑Activity
             */
            Class<?> activityManagerNativeClass = Class.forName("android.app" +
                    ".ActivityManagerNative");

            Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
            gDefaultField.setAccessible(true);

            Object gDefault = gDefaultField.get(null);

            // gDefault是一個(gè) android.util.Singleton對(duì)象; 我們?nèi)〕鲞@個(gè)單例里面的字段
            Class<?> singleton = Class.forName("android.util.Singleton");
            Field mInstanceField = singleton.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);

            // ActivityManagerNative 的gDefault對(duì)象里面原始的 IActivityManager對(duì)象
            final Object rawIActivityManager = mInstanceField.get(gDefault);

            // 創(chuàng)建一個(gè)這個(gè)對(duì)象的代理對(duì)象, 然后替換這個(gè)字段, 讓我們的代理對(duì)象幫忙干活
            Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
            Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class<?>[] {iActivityManagerInterface}, new InvocationHandler() {


                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws
                                                                                         Throwable {
                            if ("startActivity".equals(method.getName())) {
                                Intent raw;
                                int index = 0;

                                for (int i = 0; i < args.length; i++) {
                                    if (args[i] instanceof Intent) {
                                        index = i;
                                        break;
                                    }
                                }
                                raw = (Intent) args[index];

                                Intent newIntent = new Intent();

                                // 替身Activity的包名, 也就是我們自己的包名
                                String stubPackage = "com.jerey.activityplugin";

                                // 這里我們把啟動(dòng)的Activity臨時(shí)替換為 StubActivity
                                ComponentName componentName = new ComponentName(stubPackage,
                                        TmpActivity.class
                                                .getName());
                                newIntent.setComponent(componentName);

                                // 把我們?cè)家獑?dòng)的TargetActivity先存起來
                                newIntent.putExtra(EXTRA_TARGET_INTENT, raw);

                                // 替換掉Intent, 達(dá)到欺騙AMS的目的
                                args[index] = newIntent;

                                Log.d(TAG, "hook succes{s");
                                return method.invoke(rawIActivityManager, args);
                            }
                            return method.invoke(rawIActivityManager, args);
                        }
                    });
            mInstanceField.set(gDefault, proxy);


            /**
             * 欺騙ActivityThread
             */

            // 先獲取到當(dāng)前的ActivityThread對(duì)象
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Field currentActivityThreadField = activityThreadClass.getDeclaredField
                    ("sCurrentActivityThread");
            currentActivityThreadField.setAccessible(true);
            Object currentActivityThread = currentActivityThreadField.get(null);

            // 由于ActivityThread一個(gè)進(jìn)程只有一個(gè),我們獲取這個(gè)對(duì)象的mH
            Field mHField = activityThreadClass.getDeclaredField("mH");
            mHField.setAccessible(true);
            final Handler mH = (Handler) mHField.get(currentActivityThread);

            // 設(shè)置它的回調(diào), 根據(jù)源碼:
            // 我們自己給他設(shè)置一個(gè)回調(diào),就會(huì)替代之前的回調(diào);

            //        public void dispatchMessage(Message msg) {
            //            if (msg.callback != null) {
            //                handleCallback(msg);
            //            } else {
            //                if (mCallback != null) {
            //                    if (mCallback.handleMessage(msg)) {
            //                        return;
            //                    }
            //                }
            //                handleMessage(msg);
            //            }
            //        }

            Field mCallBackField = Handler.class.getDeclaredField("mCallback");
            mCallBackField.setAccessible(true);

            mCallBackField.set(mH, new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    if (msg.what == 100) {
                        Object obj = msg.obj;
                        // 根據(jù)源碼:
                        // 這個(gè)對(duì)象是 ActivityClientRecord 類型
                        // 我們修改它的intent字段為我們?cè)瓉肀4娴募纯?
                        // switch (msg.what) {
                        //      case LAUNCH_ACTIVITY: {
                        //          Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                        //          final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                        //          r.packageInfo = getPackageInfoNoCheck(
                        //                  r.activityInfo.applicationInfo, r.compatInfo);
                        //         handleLaunchActivity(r, null);
                        try {
                            // 把替身恢復(fù)成真身
                            Field intent = obj.getClass().getDeclaredField("intent");
                            intent.setAccessible(true);
                            Intent raw = (Intent) intent.get(obj);

                            Intent target = raw.getParcelableExtra(EXTRA_TARGET_INTENT);
                            raw.setComponent(target.getComponent());

                        } catch (NoSuchFieldException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                        mH.handleMessage(msg);
                    }
                    return true;
                }
            });


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

上面的代碼,我們先反射拿到ActivityManagerNative,然后動(dòng)態(tài)代理IActivityManager,Hook其startActivity方法,在里面替換掉intent,并將真實(shí)的Intent存放在假Intent的參數(shù)里面。

在系統(tǒng)最后調(diào)用打開假Intent的時(shí)候,我們從Intent中取出參數(shù),并打開真正想打開的Activity。

  • 打開Activity
    我們和正常使用一樣,startActivity就能打開我們未注冊(cè)的Activity了。
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        startActivity(new Intent(MainActivity.this, UnregisterActivity.class));
    }
});

Demo路徑:https://github.com/Jerey-Jobs/AppPluginDemos

總結(jié)

上面只是一個(gè)Demo,不能支持support包的AppCompatActivity,真正的完整的插件化庫任務(wù)是艱巨的!
還要支持其他組件,都是很麻煩的事情。


本文作者:Anderson/Jerey_Jobs

博客地址 : http://jerey.cn/
簡(jiǎn)書地址 : Anderson大碼渣
github地址 : https://github.com/Jerey-Jobs

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,690評(píng)論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評(píng)論 0 290
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,438評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,667評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,845評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評(píng)論 1 295
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,384評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容