主目录见:Android高级进阶知识(这是总目录索引)
框架地址:VirtualApk
在线源码查看:AndroidXRef
上面我们已经讲了两篇关于这个插件化框架了:
1.插件化框架VirtualApk之初始化
2.插件化框架VirtualApk之插件加载
如果看了前面这两篇应该对插件有点了解,但是要理解这篇文章还需要应用程序内Activity的启动流程相关的知识,并且我们知道要绕过系统检查必须要Activity在AndroidManifest.xml中显式声明,所以我们这里就会采用称为"占坑"的方式来绕过系统检查。
一.启动过程分析
为了大家有个直观的感受,我们这里再贴一张粗略图帮大家回顾回顾:
我们看到启动Activity的时候,都会调用到Instrumentation
类的execStartActivity()
,newActivity()
,callActivityOnCreate()
等方法,加上之前我们hook掉系统的Instrumentation
类:
private void hookInstrumentationAndHandler() {
try {
Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);
if (baseInstrumentation.getClass().getName().contains("lbe")) {
// reject executing in paralell space, for example, lbe.
System.exit(0);
}
final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);
Object activityThread = ReflectUtil.getActivityThread(this.mContext);
ReflectUtil.setInstrumentation(activityThread, instrumentation);
ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
this.mInstrumentation = instrumentation;
} catch (Exception e) {
e.printStackTrace();
}
}
所以在启动Activity的时候会调用到VAInstrumentation#execStartActivity()
方法:
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
// null component is an implicitly intent
if (intent.getComponent() != null) {
Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
intent.getComponent().getClassName()));
// resolve intent with Stub Activity if needed
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
ActivityResult result = realExecStartActivity(who, contextThread, token, target,
intent, requestCode, options);
return result;
}
execStartActivity()
方法主要做了下面几件事:
1.将隐式启动转为显式启动的方式,因为插件Activity不在宿主工程中,所以隐式启动不能启动插件中的Activity:
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
这个方法就是将隐式启动转化为显式启动的方法,我们跟踪进去可以看到:
/**
* transform intent from implicit to explicit
*/
public Intent transformIntentToExplicitAsNeeded(Intent intent) {
ComponentName component = intent.getComponent();
if (component == null
|| component.getPackageName().equals(mContext.getPackageName())) {
ResolveInfo info = mPluginManager.resolveActivity(intent);
if (info != null && info.activityInfo != null) {
component = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
intent.setComponent(component);
}
}
return intent;
}
这个方法主要是通过intent来查找要启动的插件的Activity的ResolveInfo
,然后设置进Intent中。我们可以直接跟进去看:
public ResolveInfo resolveActivity(Intent intent) {
return this.resolveActivity(intent, 0);
}
public ResolveInfo resolveActivity(Intent intent, int flags) {
for (LoadedPlugin plugin : this.mPlugins.values()) {
ResolveInfo resolveInfo = plugin.resolveActivity(intent, flags);
if (null != resolveInfo) {
return resolveInfo;
}
}
return null;
}
我们知道,插件的相关的信息都放在LoadedPlugin
类中,所以我们再调用到LoadedPlugin #resolveActivity()
方法中:
public ResolveInfo resolveActivity(Intent intent, int flags) {
List query = this.queryIntentActivities(intent, flags);
if (null == query || query.isEmpty()) {
return null;
}
ContentResolver resolver = this.mPluginContext.getContentResolver();
return chooseBestActivity(intent, intent.resolveTypeIfNeeded(resolver), flags, query);
}
从上面方法我们可以看出会查找匹配的ResolveInfo
集合,然后最后会调用chooseBestActivity()
方法来选择最匹配的Activity的ResolveInfo
类:
public List queryIntentActivities(Intent intent, int flags) {
ComponentName component = intent.getComponent();
List resolveInfos = new ArrayList();
ContentResolver resolver = this.mPluginContext.getContentResolver();
for (PackageParser.Activity activity : this.mPackage.activities) {
if (match(activity, component)) {
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = activity.info;
resolveInfos.add(resolveInfo);
} else if (component == null) {
// only match implicit intent
for (PackageParser.ActivityIntentInfo intentInfo : activity.intents) {
if (intentInfo.match(resolver, intent, true, TAG) >= 0) {
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = activity.info;
resolveInfos.add(resolveInfo);
break;
}
}
}
}
return resolveInfos;
}
这个方法主要是通过intent来查找插件中匹配的Activity,但是这里分为显示启动和隐式启动的方式,如果ComponentName
为空,那么说明是隐式启动的方式,这个地方就会通过隐式启动的一些条件来匹配到对应的ResolveInfo
。如果是显式启动则只要匹配相应的类名,包名对应即可。接着我们看下chooseBestActivity()
方法,这个方法把上面匹配到的ResolveInfo
集合选出第一个元素,接着execStartActivity()
方法会接着调用如下代码:
2.将要启动的Activity临时替换成占坑的Activity
// null component is an implicitly intent
if (intent.getComponent() != null) {
Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
intent.getComponent().getClassName()));
// resolve intent with Stub Activity if needed
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
因为插件的Activity不在宿主工程的AndroidManifest.xml
中,所以要想绕过检查,会提前在宿主工程的AndroidManifest.xml
中提前注册好:
...........
我们看到这里根据四种启动模式standard
,singleTop
,singleTask
,singleInstance
,分别注册了几个Activity
,因为我们知道,一般不会一时间连续在栈中压入这么多Activity
,所以一个启动模式只要提前注册数个即可,这里每个启动提前注册了8个Activity
。我们来看调用的markIntentIfNeeded()
方法:
public void markIntentIfNeeded(Intent intent) {
if (intent.getComponent() == null) {
return;
}
String targetPackageName = intent.getComponent().getPackageName();
String targetClassName = intent.getComponent().getClassName();
// search map and return specific launchmode stub activity
if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {
intent.putExtra(Constants.KEY_IS_PLUGIN, true);
intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
dispatchStubActivity(intent);
}
}
这个方法会判断启动的Activity在不在插件中,如果是的话,则会将要启动的Activity的包名和名称放进intent中,最后调用dispatchStubActivity()
方法来根据相应的启动模式来启动目标Activity:
private void dispatchStubActivity(Intent intent) {
ComponentName component = intent.getComponent();
String targetClassName = intent.getComponent().getClassName();
LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent);
ActivityInfo info = loadedPlugin.getActivityInfo(component);
if (info == null) {
throw new RuntimeException("can not find " + component);
}
int launchMode = info.launchMode;
Resources.Theme themeObj = loadedPlugin.getResources().newTheme();
themeObj.applyStyle(info.theme, true);
String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);
Log.i(TAG, String.format("dispatchStubActivity,[%s -> %s]", targetClassName, stubActivity));
intent.setClassName(mContext, stubActivity);
}
这个方法主要是根据intent获取到启动模式,然后匹配到的Activity放进intent的className中,这样intent的信息已经齐全,execStartActivity()
方法继续调用realExecStartActivity()
方法来真正启动Activity:
private ActivityResult realExecStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
ActivityResult result = null;
try {
Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class,
int.class, Bundle.class};
result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase,
"execStartActivity", parameterTypes,
who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
if (e.getCause() instanceof ActivityNotFoundException) {
throw (ActivityNotFoundException) e.getCause();
}
e.printStackTrace();
}
return result;
}
这个方法主要是反射调用系统的execStartActivity()
方法,这样的话流程就到AMS去了。
二.将StubActivity替换回TargetActivity
我们知道在AMS中的过程是无法进行hook操作的,所以只要等流程重新到达应用程序进程我们才能做手脚,我们重新回顾一下hook掉Instrumentation
类的过程:
private void hookInstrumentationAndHandler() {
try {
Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);
if (baseInstrumentation.getClass().getName().contains("lbe")) {
// reject executing in paralell space, for example, lbe.
System.exit(0);
}
final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);
Object activityThread = ReflectUtil.getActivityThread(this.mContext);
ReflectUtil.setInstrumentation(activityThread, instrumentation);
ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
this.mInstrumentation = instrumentation;
} catch (Exception e) {
e.printStackTrace();
}
}
我们看到这里调用setHandlerCallback()
方法将VAInstrumentation
设置替换成ActivityThread
中Handler的mCallback,所以当ActivityThread
中H进行dispathMessage()
的时候,会走到VAInstrumentation
类中的handleMessage()
方法:
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
// ActivityClientRecord r
Object r = msg.obj;
try {
Intent intent = (Intent) ReflectUtil.getField(r.getClass(), r, "intent");
intent.setExtrasClassLoader(VAInstrumentation.class.getClassLoader());
ActivityInfo activityInfo = (ActivityInfo) ReflectUtil.getField(r.getClass(), r, "activityInfo");
if (PluginUtil.isIntentFromPlugin(intent)) {
int theme = PluginUtil.getTheme(mPluginManager.getHostContext(), intent);
if (theme != 0) {
Log.i(TAG, "resolve theme, current theme:" + activityInfo.theme + " after :0x" + Integer.toHexString(theme));
activityInfo.theme = theme;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
我们可以看到这个方法只拦截了LAUNCH_ACTIVITY的处理,在里面将intent中的activityInfo.theme替换为插件的theme,并给intent设置了ClassLoader
,这是因为后面ActivityThread
类中的performLaunchActivity()
方法会将类加载器重新设置给mInstrumentation#newActivity()
方法:
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try {
cl.loadClass(className);
} catch (ClassNotFoundException e) {
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
String targetClassName = PluginUtil.getTargetActivity(intent);
Log.i(TAG, String.format("newActivity[%s : %s]", className, targetClassName));
if (targetClassName != null) {
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
try {
// for 4.1+
ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());
} catch (Exception ignored) {
// ignored.
}
return activity;
}
}
return mBase.newActivity(cl, className, intent);
}
这里会往类加载器中设置className,这里的className就是之前占坑的那一些类,但是这些类其实现实中并不存在,如果直接调用肯定是会报ClassNotFoundException
异常,所以会走到异常的地方,首先会根据intent获取到插件,然后根据intent获取到targetActivity
,这样就又替换回来插件中的Activity,最后我们注意这个地方有个调用:
ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());
这主要是performLaunchActivity()
方法中调用createBaseContextForActivity()
方法创建出的appContext
用的是宿主Resources
,所以如果不进行处理,在调用callActivityOnCreate()
方法的时候会获取不到资源,因为此时插件加载资源的时候还是使用的宿主的资源,而不是我们特意为插件所创建出来的Resources
对象,则会发生找不到资源的问题,所以在4.1以上的版本就会另外做处理。接下来会调用到VAInstrumentation
的callActivityOnCreate()
方法中:
@Override
public void callActivityOnCreate(Activity activity, Bundle icicle) {
final Intent intent = activity.getIntent();
if (PluginUtil.isIntentFromPlugin(intent)) {
Context base = activity.getBaseContext();
try {
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
ReflectUtil.setField(base.getClass(), base, "mResources", plugin.getResources());
ReflectUtil.setField(ContextWrapper.class, activity, "mBase", plugin.getPluginContext());
ReflectUtil.setField(Activity.class, activity, "mApplication", plugin.getApplication());
ReflectUtil.setFieldNoException(ContextThemeWrapper.class, activity, "mBase", plugin.getPluginContext());
// set screenOrientation
ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));
if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
activity.setRequestedOrientation(activityInfo.screenOrientation);
}
} catch (Exception e) {
e.printStackTrace();
}
}
mBase.callActivityOnCreate(activity, icicle);
}
这个方法主要是判断要启动的Activity是插件中的,这样就会替换目标Activity中的Resources
和Context
,mApplication
为相应的加载插件资源用的Resources
和Context
,然后调用系统Instrumentation
的callActivityOnCreate()
方法来启动插件TargetActivity。到这里插件中的Activity启动就已经完成了。
总结:到这里我们插件中的Activity启动已经讲完了,接下来我们会来讲讲Service的启动情况,希望有什么不理解或者不正确的地方可以留言,一起交流。