13.原始碼閱讀(啟動一個沒有註冊的Activity為何會丟擲異常-haveyoudeclaredthisactivityinyourAndroidManifest.xml?–androidapi23)
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的時候再將這個未註冊的替換過去,達到偷樑換柱的目的
具體實現見下一篇部落格
相關文章
- 14.原始碼閱讀(啟動一個沒有在AndroidManifest中註冊的Activity)原始碼Android
- oracle主動丟擲異常Oracle
- 原始碼閱讀之Activity啟動與App啟動流程 - Android 9.0原始碼APPAndroid
- 原始碼閱讀之Activity啟動與App啟動流程 – Android 9.0原始碼APPAndroid
- Swift 中 throws 異常丟擲Swift
- 啪,還敢丟擲異常
- 擷取Spring框架自動丟擲異常Spring框架
- WCF服務端丟擲的異常會跑到客戶端服務端客戶端
- java中異常丟擲後程式碼還會繼續執行嗎Java
- 建構函式中丟擲的異常函式
- Golang 迴圈異常丟擲不影響整個請求Golang
- [原始碼閱讀] 阿里SOFA服務註冊中心MetaServer(3)原始碼阿里Server
- 閱讀原始碼很重要,以logback為例,分享一個小白都能學會的讀原始碼方法原始碼
- 高效Java:丟擲適合抽象的異常 - Kyle CarterJava抽象
- 原始碼分析 — Activity的清單註冊校驗及動態注入原始碼
- Java異常十一:使用throw丟擲異常物件;throw和throws的區別Java物件
- hook 系統api啟動未註冊ActivityHookAPI
- containerd 原始碼分析:啟動註冊流程AI原始碼
- 你真的會閱讀Java的異常資訊嗎?Java
- 為什麼建議你常閱讀原始碼?原始碼
- Android黑科技:如何啟動未註冊的ActivityAndroid
- JAVA操作MySQL tImestamp列值為0時丟擲異常的處理~JavaMySql
- XCode除錯時丟擲異常,定位到某一行程式碼XCode除錯行程
- 原始碼面前沒有祕密,推薦 9 個帶你閱讀原始碼的開源專案原始碼
- C++程式丟擲異常後執行順序C++
- hibernate open session in view 丟擲異常解決方法SessionView
- 易優cms404頁面 丟擲HttpException異常HTTPException
- Activity啟動流程原始碼分析原始碼
- 閱讀 Composer 原始碼的一個分享原始碼
- 程式碼安全測試第三十期:丟擲通用異常缺陷
- scope="session"和scope="request"--丟擲異常非常的bug+垃圾Session
- 【原始碼閱讀】Glide原始碼閱讀之with方法(一)原始碼IDE
- 【Android原始碼】Activity的啟動流程Android原始碼
- Scrapy原始碼閱讀分析_2_啟動流程原始碼
- spring原始碼閱讀--容器啟動過程Spring原始碼
- SOFAJRaft原始碼閱讀-模組啟動過程Raft原始碼
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- nacos原理三-註冊中心原理&原始碼啟動.md原始碼