Activity註冊清單校驗及動態注入
一、概述
一般情況下,Activity的啟動都需要先在清單檔案
AndroidManifest.xml
中註冊後,才能使用。而目前流行的外掛化則是通過在底座清單檔案中提前佔坑的方式來達到啟動外掛中Activity的目的。但是,外掛中的Activity並沒有在底座的清單檔案中註冊。那麼,他們是如何達到這個目的的呢?
本文涉及到的技術點: PackageManagerService(二)之resolveIntent()
二、示例
new TextView(context).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// MainActivity註冊過,SecondActivity沒有註冊過;
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
});
複製程式碼
結果:
Process: com.example.demo, PID: 3773
android.content.ActivityNotFoundException:
Unable to find explicit activity class {com.example.demo/com.example.demo.SecondActivity};
have you declared this activity in your AndroidManifest.xml?
...略...
三、時序圖
四、原始碼分析
// Activity.class
// 呼叫startActivity(intent),經多次跳轉後,最終呼叫此方法;
public void startActivityForResult(
@RequiresPermission Intent intent,
int requestCode, @Nullable Bundle options) {
options = transferSpringboardActivityOptions(options);
// 注意:Instrumentation該類是Activity的實際操作類
Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
// ...程式碼省略...
}
// Instrumentation.class
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// ...程式碼省略...
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
// ActivityManagerNative涉及到IBinder通訊機制 (看下面小結)
// 結論:最終返回 result = = ActivityManager.START_CLASS_NOT_FOUND;
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
// 校驗:根據返回的result值校驗當前Activity是否有異常;
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
// Instrumentation.class
public static void checkStartActivityResult(int res, Object intent) {
// 啟動成功,就直接return
if (res >= ActivityManager.START_SUCCESS) {
return;
}
switch (res) {
case ActivityManager.START_INTENT_NOT_RESOLVED:
case ActivityManager.START_CLASS_NOT_FOUND:
if (intent instanceof Intent && ((Intent)intent).getComponent() != null) {
// 丟擲這一行異常
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+ "; have you declared this activity
+ in your AndroidManifest.xml?");
}
// ...程式碼省略...
}
}
複製程式碼
小結:
- 涉及三個類:
ActivityManagerNative、ActivityManagerProxy、ActivityManagerService(AMS)
,這三個類都實現了IActivityManager介面; - 記住:呼叫
ActivityManagerNative.getDefault().startActivity()
,最終會呼叫Service(AMS)端的startActivity()方法;
/*
* 由上面的小結可知,ActivityManagerNative.getDefault().startActivity()最終呼叫了
* ActivityManagerService.startActivity()方法;
*/
// ActivityManagerService.class
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options) {
//呼叫startActivityAsUser方法
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags, profilerInfo, options,
UserHandle.getCallingUserId());
}
// ActivityManagerService.class
public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivity");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
false, ALLOW_FULL_ONLY, "startActivity", null);
// 呼叫ActivityStackSupervisor.startActivityMayWait()方法
return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
profilerInfo, null, null, options, false, userId, null, null);
}
// ActivityStackSupervisor.class
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) {
// ...程式碼省略...
// Collect information about the target of the Intent.
// 這一步是關鍵,去解析目標Activity的相關Intent資訊;(由下面一部分的程式碼可知,aInfo=null)
ActivityInfo aInfo = resolveActivity(intent, resolvedType,
startFlags, profilerInfo, userId);
// ...程式碼省略...
synchronized (mService) {
// ...省略一大段程式碼...
// 將aInfo=null傳入startActivityLocked(),最終返回 res = ActivityManager.START_CLASS_NOT_FOUND;
int res = startActivityLocked(caller, intent, resolvedType, aInfo,
voiceSession, voiceInteractor, resultTo, resultWho,
requestCode, callingPid, callingUid, callingPackage,
realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity,
componentSpecified, null, container, inTask);
// ...省略一大段程式碼...
return res; //res = ActivityManager.START_CLASS_NOT_FOUND
}
}
// ActivityStackSupervisor.class
ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags,
ProfilerInfo profilerInfo, int userId) {
// Collect information about the target of the Intent.
ActivityInfo aInfo;
try {
/*
* 這裡AppGlobals.getPackageManager()獲取的是IPackageManager例項物件,
* 真正的實現類是PackageManagerService;
*
* 關於IPackageManager.resolveIntent(),
* 請參考《PackageManagerService(二)之resolveIntent()》
* http://blog.csdn.net/love667767/article/details/79585080
*/
ResolveInfo rInfo = AppGlobals.getPackageManager()
.resolveIntent(intent, resolvedType,
PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS,
userId);
// 由於Activity沒有在清單檔案註冊過,因此最終返回的aInfo=null
aInfo = rInfo != null ? rInfo.activityInfo : null;
} catch (RemoteException e) {
aInfo = null;
}
// ...省略一大段程式碼...
return aInfo;
}
// ActivityStackSupervisor.class
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) {
// 初始狀態是:START_SUCCESS
int err = ActivityManager.START_SUCCESS;
// ...省略一大段程式碼...
// intent.getComponent()是有值的
if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
// We couldn't find a class that can handle the given Intent.
// That's the end of that!
err = ActivityManager.START_INTENT_NOT_RESOLVED;
}
// 在這裡aInfo == null 滿足條件,因此最終返回err = ActivityManager.START_CLASS_NOT_FOUND
if (err == ActivityManager.START_SUCCESS && aInfo == null) {
// We couldn't find the specific class specified in the Intent.
// Also the end of the line.
err = ActivityManager.START_CLASS_NOT_FOUND;
}
err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,
startFlags, true, options, inTask);
// 最終返回ActivityManager.START_CLASS_NOT_FOUND
return err;
}
複製程式碼
五、實踐:如何繞過校驗,動態注入Activity?
5.1 原理分析
由上面的原始碼分析可知:
- Activity的清單校驗邏輯在
Instrumentation.execStartActivity()
中進行的;Instrumentation
的例項在ActivityThread中;Activity
必須要在清單檔案註冊的;
方案: **佔坑方案:**既然Activity要註冊過,那我們就在清單檔案中預先註冊一個佔坑的Activity; **作用:**在Activity校驗的時候,將真正被啟動的Activity替換成這個佔坑的Activity,從而完成Activity校驗的過程。 **技術實現:**利用清單檔案合併的特點,預先在一個清單檔案中寫入佔坑的Activity;
問題:
在校驗之前,將被啟動的Activity替換成佔坑的Activity後,那真正啟動的不就是佔坑的Activity了嗎? 如何啟動我們真正需要的Activity呢?
分析: 其實,Activity的建立過程分為兩部分:
- Activity的校驗;(
Instrumentation.execStartActivity()
)- Activity例項的建立;(
Instrumentation.newActivity()
)
方案:(Hook技術)- 通過
反射技術
來替換ActivityThread
中的Instrumentation
例項物件,並重寫其內部的execStartActivity()、newActivity()
方法;- 在重寫的
execStartActivity()
方法中將目標Activity替換成佔坑的Activity,並呼叫系統自身的Instrumentation.execStartActivity()
,從而完成Activity的校驗過程;- 在重寫的
newActivity()
方法中將佔坑Activity的還原成目標Activity,並呼叫系統自身的Instrumentation.newActivity()
,最終完成目標Activity的建立;
5.2 實踐
說明:
MainActivity、PlaceHolderActivity
註冊、SecondActivity
未註冊 ;PlaceHolderActivity
是佔坑的Activity ;HookInstrumentation
繼承Instrumentation
,並重寫execStartActivity()、newActivity()
方法;- 在
Application.attachBaseContext()
中將HookInstrumentation
通過反射賦值給ActivityThread.mInstrumentation
欄位 ;
程式碼實現:
public class VirtualApkApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
init();
}
/**
* 1.反射獲取ActivityThread物件;
* 2.反射獲取ActivityThread物件中的mInstrumentation物件;
* 3.建立自定義的HookInstrumentation物件,
* 並通過反射將自定義的HookInstrumentation物件賦值給ActivityThread.mInstrumentation;
*/
private void init() {
try {
Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
Object activityThread = ReflectUtil.getField(activityThreadClazz,
null, "sCurrentActivityThread");
Instrumentation sInstrumentation = (Instrumentation) invoke(
activityThread.getClass(),
activityThread,
"getInstrumentation");
final HookInstrumentation instrumentation = new HookInstrumentation(sInstrumentation);
ReflectUtil.setField(activityThread.getClass(), activityThread,
"mInstrumentation", instrumentation);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object invoke(Class clazz, Object target, String name, Object... args)
throws Exception {
Class[] parameterTypes = null;
if (args != null) {
parameterTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) {
parameterTypes[i] = args[i].getClass();
}
}
Method method = clazz.getDeclaredMethod(name, parameterTypes);
method.setAccessible(true);
return method.invoke(target, args);
}
}
複製程式碼
public class HookInstrumentation extends Instrumentation {
private static final String PACKAGE_NAME = "package_name";
private static final String CLASS_NAME = "class_name";
Instrumentation mBase;
public HookInstrumentation(Instrumentation base) {
this.mBase = base;
}
// 這個方法被隱藏了
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
if (intent.getComponent() != null) {
intent.putExtra(PACKAGE_NAME, intent.getComponent().getPackageName());
intent.putExtra(CLASS_NAME, intent.getComponent().getClassName());
// 將目標Activity替換成佔位Activity;
intent.setClassName(intent.getComponent().getPackageName(),
PlaceHolderActivity.class.getName());
}
ActivityResult result = null;
try {
Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class,
int.class, Bundle.class};
result = (ActivityResult) invoke(Instrumentation.class, mBase,
"execStartActivity", parameterTypes,
who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
String targetClassName = intent.getStringExtra(CLASS_NAME);
if (!TextUtils.isEmpty(targetClassName)) {
// 開啟目標Activity
Activity activity = mBase.newActivity(cl, targetClassName, intent);
activity.setIntent(intent);
return activity;
}
return mBase.newActivity(cl, className, intent);
}
public static Object invoke(Class clazz, Object target, String name, Class[] parameterTypes, Object... args)
throws Exception {
Method method = clazz.getDeclaredMethod(name, parameterTypes);
method.setAccessible(true);
return method.invoke(target, args);
}
}
複製程式碼