13.原始碼閱讀(啟動一個沒有註冊的Activity為何會丟擲異常-haveyoudeclaredthisactivityinyourAndroidManifest.xml?–androidapi23)

黑夜路口發表於2018-04-18

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的時候再將這個未註冊的替換過去,達到偷樑換柱的目的

具體實現見下一篇部落格


相關文章