天天看點

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

具體實作見下一篇部落格

繼續閱讀