13.源碼閱讀(啟動一個沒有注冊的Activity為何會拋出異常-have you declared this activity in your AndroidManifest.xml?--android api 23)

app中每一個activity都要在AndroidManifest文件中配置,否則啟動會拋出異常

Unable to find explicit activity class ..; have you declared this activity in your AndroidManifest.xml?

那么我們是否可以啟動一個沒有注冊的activity呢?這就是今天看源碼的目的

系統如何檢查AndroidManifest中是否注冊有一個activity?

在文章03.源碼閱讀(Activity啟動流程--android api 23)中我們已經通過源碼知道,啟動一個activity調用startActivity后,會進入Instrumentation的方法中

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
            ......
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            //通過result值進行一些列的異常拋出,上邊的那個就是這里拋出的,
            checkStartActivityResult(result, intent);
            ......
    }

已經知道ActivityManagerNative.getDefault()(api26叫ActivityManager.getService())這行代碼已經看過多次,但是這次還是要貼出來說一說,因為很重要,它得到的是IActivityManager這個接口的一個實現類----ActivityMangagerService,最終還是要去看ActivityMangagerService中的startActivity

/**
     * Retrieve the system's default/global activity manager.
     */
    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;
        }
    };

public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

IActivityManager這個接口是hook攔截activity創建的關鍵,這里先放著
進入ActivityManagerService的startActivity中,一路點進入會來到ActivityStackSupervisor類中的startActivityLocked方法

final int startActivityLocked(IApplicationThread caller,
            Intent intent, String resolvedType, ActivityInfo aInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode,
            int callingPid, int callingUid, String callingPackage,
            int realCallingPid, int realCallingUid, int startFlags, Bundle options,
            boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,
            ActivityContainer container, TaskRecord inTask) {
            //activity啟動的返回結果
            int err = ActivityManager.START_SUCCESS;
            ......
            if (err == ActivityManager.START_SUCCESS && aInfo == null) {
                    // 就是在這個返回值的情況下會拋出activity未注冊的異常
                    err = ActivityManager.START_CLASS_NOT_FOUND;
            }
            ......
}

看一下什么情況下會返回這個,err == ActivityManager.START_SUCCESS 一上來就設置了,所以關鍵看aInfo,它什么時候會為null,那么現在要回退過去了,看看aInfo是何時賦值的,找到ActivityStackSupervisor中的方法startActivityMayWait

final int startActivityMayWait(IApplicationThread caller, int callingUid,
            String callingPackage, Intent intent, String resolvedType,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int startFlags,
            ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,
            Bundle options, boolean ignoreTargetSecurity, int userId,
            IActivityContainer iContainer, TaskRecord inTask) {
            ......
            ActivityInfo aInfo =
                resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);
            ......
}
ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags,
            ProfilerInfo profilerInfo, int userId) {
        ......
        ActivityInfo aInfo;
        try {
            ResolveInfo rInfo =
                AppGlobals.getPackageManager().resolveIntent(
                        intent, resolvedType,
                        PackageManager.MATCH_DEFAULT_ONLY
                                    | ActivityManagerService.STOCK_PM_FLAGS, userId);
            //ActivityInfo是從ResolveInfo中的activityInfo賦值得到的,所以需要找到ResolveInfo如何獲取的
            aInfo = rInfo != null ? rInfo.activityInfo : null;
        } catch (RemoteException e) {
            aInfo = null;
        }
        ......
    }

AppGlobals.getPackageManager()最終得到的是IPackageManager的實現類PackageManagerService,來到這里的resolveIntent方法->chooseBestActivity->findPreferredActivity->getActivityInfo

@Override
    public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
        if (!sUserManager.exists(userId)) return null;
        //檢查users-permission 
        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "get activity info");
        synchronized (mPackages) {
            PackageParser.Activity a = mActivities.mActivities.get(component);

            if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
            if (a != null && mSettings.isEnabledLPr(a.info, flags, userId)) {
                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
                if (ps == null) return null;
                return PackageParser.generateActivityInfo(a, flags, ps.readUserState(userId),
                        userId);
            }
            if (mResolveComponentName.equals(component)) {
                return PackageParser.generateActivityInfo(mResolveActivity, flags,
                        new PackageUserState(), userId);
            }
        }
        return null;
    }

進入PackageParser中,可以看到,只要Activity a不為null,ActivityInfo就不會為null,那么到了這里又要回退過去看看Activity什么時候為null的

public static final ActivityInfo generateActivityInfo(Activity a, int flags,
            PackageUserState state, int userId) {
        if (a == null) return null;
        if (!checkUseInstalledOrHidden(flags, state)) {
            return null;
        }
        if (!copyNeeded(flags, a.owner, state, a.metaData, userId)) {
            return a.info;
        }
        // Make shallow copies so we can store the metadata safely
        ActivityInfo ai = new ActivityInfo(a.info);
        ai.metaData = a.metaData;
        ai.applicationInfo = generateApplicationInfo(a.owner, flags, state, userId);
        return ai;
    }

來到PackageManagerService類中,可以看到activity是從集合中取出來的

@Override
    public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
        if (!sUserManager.exists(userId)) return null;
        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "get activity info");
        synchronized (mPackages) {
            PackageParser.Activity a = mActivities.mActivities.get(component);

            if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
            if (a != null && mSettings.isEnabledLPr(a.info, flags, userId)) {
                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
                if (ps == null) return null;
                return PackageParser.generateActivityInfo(a, flags, ps.readUserState(userId),
                        userId);
            }
            if (mResolveComponentName.equals(component)) {
                return PackageParser.generateActivityInfo(mResolveActivity, flags,
                        new PackageUserState(), userId);
            }
        }
        return null;
    }

PackageParser.Activity a = mActivities.mActivities.get(component);這個集合以ComponentName為key,以這個Activity為value,ComponentName肯定是類的全類名,包名+類名的形式,那么能不能從這個集合中取出這個activity關鍵就是看這個activity有沒有被加入集合了

private final ArrayMap<ComponentName, PackageParser.Activity> mActivities
                = new ArrayMap<ComponentName, PackageParser.Activity>();

可以在PackageManagerService類中找到這個方法

public final void addActivity(PackageParser.Activity a, String type) {
            final boolean systemApp = a.info.applicationInfo.isSystemApp();
            mActivities.put(a.getComponentName(), a);
            ......
        }

這里可以猜測一下,app啟動后(或者app安裝時就已經掃描保存了)應該會掃描AndroidManifest文件,然后將activity都加入到這個集合,然后啟動一個activity的時候如果從這個集合中找不到這個activity,就拋出異常activity未在AndroidManifest中注冊,所以肯定有一個掃描AndroidManifest文件的過程,這個過程我們放到下一篇博客中說,今天看到這里基本上已經達到我們的目的了,總結一下

所有在AndroidManifest中注冊的activity會被加入一個集合中,這個集合以這個activity的包名+類型為key值存儲這個activity,當調用startActivity啟動一個activity的時候,會根據這個activity的全類名去這個集合中查找,如果查找不到就表明這個activity沒有在AndroidManifest中注冊,拋出異常(這里省略了系統掃描AndroidManifest配置文件的過程,以app啟動時,activity已經被加入集合為前提)

再次回到我們最初的問題,我們是否可以啟動一個沒有注冊的activity?通過看源碼也了解到了,之所以沒有注冊的activity啟動會被拋出異常,是因為有一個監測的過程,如果能繞過這個監測過程,豈不是就可以啟動這個activity了

這里采用偷梁換柱的方式來實現,通過動態代理hook Activity的啟動過程,啟動activity時,系統通過Intent中傳遞的Activity的ComponentName去集合中查找,只要查找得到就會躲過檢查,那么我們的思路是這樣的,當我們要啟動一個未注冊的activity時,傳遞一個已經注冊的activity的componentname作為傀儡,當監測通過的時候,真正要啟動這個activity的時候再將這個未注冊的替換過去,達到偷梁換柱的目的

具體實現見下一篇博客

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,639評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內容