以下文章摘录自滴滴插件化框架VirtualAPK原理解析(一)之插件Activity管理
具体的使用手册请参考官方文档和demo: VirtualAPK
(Activity启动流程图)
(startActivityLocked之后处理的都是Activity任务栈相关内容)
ActivityManagerService的功能
在解释VirtualAPK是如何对Activity进行管理之前,有必要说一下ActivityManagerService,Android的四大组件都需要与它打交道,它主要为四大组件做了这些事情:
1.启动activity:startActivity最终会调用到AMS的startActivty方法(由上图可知),实现了Activity的启动。同时Activity的生命周期回调也是在AMS中完成的
[->ActivityManagerService.java]
@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags, profilerInfo, bOptions,
UserHandle.getCallingUserId());
}
2.启动service:startService,bindService最终会调用到AMS的startSservice和bindService方法:
[->ActivityManagerService.java]
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, boolean requireForeground, String callingPackage, int userId)
throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
// Refuse possible leaked file descriptors
if (service != null && service.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
if (callingPackage == null) {
throw new IllegalArgumentException("callingPackage cannot be null");
}
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
"*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
synchronized(this) {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
ComponentName res;
try {
res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid,
requireForeground, callingPackage, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
return res;
}
}
public int bindService(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags, String callingPackage,
int userId) throws TransactionTooLargeException {
enforceNotIsolatedCaller("bindService");
// Refuse possible leaked file descriptors
if (service != null && service.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
if (callingPackage == null) {
throw new IllegalArgumentException("callingPackage cannot be null");
}
synchronized(this) {
return mServices.bindServiceLocked(caller, token, service,
resolvedType, connection, flags, callingPackage, userId);
}
}
3.广播:动态广播的注册和接收在AMS中完成(静态广播在PMS中完成)
[->ActivityManagerService.java]
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {
enforceNotIsolatedCaller("registerReceiver");
int callingUid;
int callingPid;
synchronized(this) {
ProcessRecord callerApp = null;
if (caller != null) {
callerApp = getRecordForAppLocked(caller);
if (callerApp == null) {
throw new SecurityException(
"Unable to find app for caller " + caller
+ " (pid=" + Binder.getCallingPid()
+ ") when registering receiver " + receiver);
}
if (callerApp.info.uid != Process.SYSTEM_UID &&
!callerApp.pkgList.containsKey(callerPackage) &&
!"android".equals(callerPackage)) {
throw new SecurityException("Given caller package " + callerPackage
+ " is not running in process " + callerApp);
}
callingUid = callerApp.info.uid;
callingPid = callerApp.pid;
} else {
callerPackage = null;
callingUid = Binder.getCallingUid();
callingPid = Binder.getCallingPid();
}
userId = this.handleIncomingUser(callingPid, callingUid, userId,
true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
List allSticky = null;
// Look for any matching sticky broadcasts...
Iterator actions = filter.actionsIterator();
if (actions != null) {
while (actions.hasNext()) {
String action = (String)actions.next();
allSticky = getStickiesLocked(action, filter, allSticky,
UserHandle.USER_ALL);
allSticky = getStickiesLocked(action, filter, allSticky,
UserHandle.getUserId(callingUid));
}
} else {
allSticky = getStickiesLocked(null, filter, allSticky,
UserHandle.USER_ALL);
allSticky = getStickiesLocked(null, filter, allSticky,
UserHandle.getUserId(callingUid));
}
// The first sticky in the list is returned directly back to
// the client.
Intent sticky = allSticky != null ? (Intent)allSticky.get(0) : null;
if (DEBUG_BROADCAST) Slog.v(TAG, "Register receiver " + filter
+ ": " + sticky);
if (receiver == null) {
return sticky;
}
ReceiverList rl
= (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());
...........
4.ContentProvider:getContentResolver最终从AMS的getContentProvider获取到ContentProvider
[->ActivityManagerService.java]
@Override
public final ContentProviderHolder getContentProvider(
IApplicationThread caller, String name, int userId, boolean stable) {
enforceNotIsolatedCaller("getContentProvider");
if (caller == null) {
String msg = "null IApplicationThread when getting content provider "
+ name;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
// The incoming user check is now handled in checkContentProviderPermissionLocked() to deal
// with cross-user grant.
return getContentProviderImpl(caller, name, null, stable, userId);
}
AMS以Binder的方式提供给应用程序使用的系统服务,所以我们要处理插件中的四大组件,必须要Hook掉AMS进行相应的处理。
Activity的启动
Context的startActivity
[->Context.java]
/**
* Same as {@link #startActivity(Intent, Bundle)} with no options
* specified.
*
* @param intent The description of the activity to start.
*
* @throws ActivityNotFoundException
*`
* @see #startActivity(Intent, Bundle)
* @see PackageManager#resolveActivity
*/
public abstract void startActivity(@RequiresPermission Intent intent);
由于Activity和Service并没有直接继承Context,而是继承ContextWrapper,所以我们直接看看ContextWrapper的startActivity方法:
[->ContextWrapper.java]
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);
}
而此处的mBase其实就是ContextImpl,下面看看ContextImpl的startActivity:
[->ContextImpl.java]
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity)null, intent, -1, options);
}
看到这里,你是不是突然恍然大悟,为什么平常在使用非Activity的Context来startActivity的时候会需要添加FLAG_ACTIVITY_NEW_TASK;
[->Instrumentation.java]
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
// ... 省略无关代码
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess();
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
}
return null;
}
到这里我们发现真正调用的是ActivityManagerNative的startActivity方法。
Activity的startActivity
Activity类的startActivity方法相比Context而言直观了很多;这个startActivity通过若干次调用辗转到达startActivityForResult这个方法,在这个方法内部有如下代码:
[->]
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {
................
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
................
}
可以得知,通过Activity和ContextImpl类启动Activity是一样的,他们都通过Instrumentation这个辅助类调用到了ActivityManagerNative的方法。
Hook AMS
前面说过,startActivity最终通过ActivityManagerNative的本地ActivityManagerProxy的startActivity方法远程调用了AMS的startActivity方法,ActivityManagerNative实际上就是ActivityManagerService这个远程对象的Binder代理对象;每次需要与AMS打交道的时候,需要借助这个代理对象通过驱动进而完成IPC调用。
这里我们多说一句:ActivityManagerNative是运行在app进程,而ActivityManagerService是系统服务运行在在system_Server进程中,在SystemServer#startBootstrapServices启动,system_Server是由Zygote进程孵化出来的。我们从包名也可以看得出来。
ActivityManagerNative所属包名为:package android.app;
ActivityManagerService所属包名为:package com.android.server.am;
这里我们先看下ActivityManagerService在什么时候被注册的
[->ActivityManagerService.java]
public void setSystemProcess() {
//注意这行代码 其中Context.ACTIVITY_SERVICE为 public static final String ACTIVITY_SERVICE = "activity";
ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);
ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
ServiceManager.addService("meminfo", new MemBinder(this));
ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
ServiceManager.addService("dbinfo", new DbBinder(this));
if (MONITOR_CPU_USAGE) {
ServiceManager.addService("cpuinfo", new CpuBinder(this));
}
ServiceManager.addService("permission", new PermissionController(this));
ServiceManager.addService("processinfo", new ProcessInfoService(this));
.....................
}
下面言归正传,在ActivityManagerNative中有一个getDefault()方法
[->ActivityManagerNative.java]
/**
* 这里返回的IActivityManager为ActivityManagerProxy对象
*/
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
//获取的是ActivityManagerService
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
//返回的为ActivityManagerProxy对象
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
}
framework使用了一个单例把这个AMS的代理对象保存了起来;这样只要需要与AMS进行IPC调用,获取这个单例即可,所以我们需要Hook掉这个单例,就可以达到Hook AMS的效果(其实hook的是ActivityManagerProxy),在Virtualapk中我们就可以看到如下一段代码:
[->VirtualAPK\PluginManager.java]
/**
* hookSystemServices, but need to compatible with Android O in future.
*/
private void hookSystemServices() {
try {
//系统ActivityManagerProxy的单例模式
Singleton<IActivityManager> defaultSingleton = (Singleton<IActivityManager>) ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault");
//其中这里的ActivityManagerProxy为我们自己的ActivityManagerProxy,注意和系统的ActivityManagerProxy区别开来
IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get());
// Hook IActivityManager from ActivityManagerNative
ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy);
if (defaultSingleton.get() == activityManagerProxy) {
this.mActivityManager = activityManagerProxy;
}
} catch (Exception e) {
e.printStackTrace();
}
}
可以看到在VirtualAPK中PluginManager类里,就是替换掉了ActivityManagerNative里原先ActivityManagerProxy的为VirtualAPK中自己的ActivityManagerProxy,而这个ActivityManagerProxy其实是一个动态代理,所有ActivityManagerNative中的方法都会经过这个代理。
[->自定义的ActivityManagerProxy.java]
public static IActivityManager newInstance(PluginManager pluginManager, IActivityManager activityManager) {
return (IActivityManager) Proxy.newProxyInstance(activityManager.getClass().getClassLoader(), new Class[] { IActivityManager.class }, new ActivityManagerProxy(pluginManager, activityManager));
}
我们可以看到在ActivityManagerProxy的invoke方法,针对一些方法进行了处理
[->ActivityManagerProxy.java]
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startService".equals(method.getName())) {
try {
return startService(proxy, method, args);
} catch (Throwable e) {
Log.e(TAG, "Start service error", e);
}
} else if ("stopService".equals(method.getName())) {
try {
return stopService(proxy, method, args);
} catch (Throwable e) {
Log.e(TAG, "Stop Service error", e);
}
} else if ("stopServiceToken".equals(method.getName())) {
try {
return stopServiceToken(proxy, method, args);
} catch (Throwable e) {
Log.e(TAG, "Stop service token error", e);
}
} else if ("bindService".equals(method.getName())) {
try {
return bindService(proxy, method, args);
} catch (Throwable e) {
e.printStackTrace();
}
} else if ("unbindService".equals(method.getName())) {
try {
return unbindService(proxy, method, args);
} catch (Throwable e) {
e.printStackTrace();
}
} else if ("getIntentSender".equals(method.getName())) {
try {
getIntentSender(method, args);
} catch (Exception e) {
e.printStackTrace();
}
} else if ("overridePendingTransition".equals(method.getName())){
try {
overridePendingTransition(method, args);
} catch (Exception e){
e.printStackTrace();
}
}
try {
// sometimes system binder has problems.
return method.invoke(this.mActivityManager, args);
} catch (Throwable th) {
Throwable c = th.getCause();
if (c != null && c instanceof DeadObjectException) {
// retry connect to system binder
IBinder ams = ServiceManager.getService(Context.ACTIVITY_SERVICE);
if (ams != null) {
IActivityManager am = ActivityManagerNative.asInterface(ams);
mActivityManager = am;
}
}
Throwable cause = th;
do {
if (cause instanceof RemoteException) {
throw cause;
}
} while ((cause = cause.getCause()) != null);
throw c != null ? c : th;
}
}
上述关于startService等方法的处理,我们在后面说道service时再详细说明,这里我们只要知道VirtualAPK中是如何Hook AMS的就可以了。
启动插件Activity的限制
在Android中启动Activity有一个限制:必须在AndroidManifest.xml中显示声明使用的Activity;否则会遇到下面这种异常:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.dldemo/com.example.dldemo.MainActivity}: android.content.ActivityNotFoundException: Unable to find explicit activity class {com.example.dldemo/com.example.dldemo.TestActivity1}; have you declared this activity in your AndroidManifest.xml?
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
必须在AndroidManifest.xml中显示声明使用的Activity,这个硬性要求很大程度上限制了插件系统的发挥:因为插件的Activity必定不会在宿主程序中进行声明。那么我们改如何绕过这个限制呢?既然AndroidManifest文件中必须声明,那么我们就声明一个或者多个替身Activity(其实这个替身Activity在本地并不存在,只是为了绕过本地检查)好了,当需要启动插件中的某个Activity,就让系统以为启动的是AndroidManifest中声明的那个替身,暂时骗过系统,然后到合适的时候又替换为我们在插件中真正需要启动的Activity。接下来我们看下Virtualapk的Library库,在AndroidManifest中声明了各种启动模式的Activity,如下所示:
<!-- Stub Activities -->
<activity android:name=".A$1" android:launchMode="standard"/>
<activity android:name=".A$2" android:launchMode="standard"
android:theme="@android:style/Theme.Translucent" />
<!-- Stub Activities -->
<activity android:name=".B$1" android:launchMode="singleTop"/>
<activity android:name=".B$2" android:launchMode="singleTop"/>
<activity android:name=".B$3" android:launchMode="singleTop"/>
<activity android:name=".B$4" android:launchMode="singleTop"/>
<activity android:name=".B$5" android:launchMode="singleTop"/>
<activity android:name=".B$6" android:launchMode="singleTop"/>
<activity android:name=".B$7" android:launchMode="singleTop"/>
<activity android:name=".B$8" android:launchMode="singleTop"/>
<!-- Stub Activities -->
<activity android:name=".C$1" android:launchMode="singleTask"/>
<activity android:name=".C$2" android:launchMode="singleTask"/>
<activity android:name=".C$3" android:launchMode="singleTask"/>
<activity android:name=".C$4" android:launchMode="singleTask"/>
<activity android:name=".C$5" android:launchMode="singleTask"/>
<activity android:name=".C$6" android:launchMode="singleTask"/>
<activity android:name=".C$7" android:launchMode="singleTask"/>
<activity android:name=".C$8" android:launchMode="singleTask"/>
<!-- Stub Activities -->
<activity android:name=".D$1" android:launchMode="singleInstance"/>
<activity android:name=".D$2" android:launchMode="singleInstance"/>
<activity android:name=".D$3" android:launchMode="singleInstance"/>
<activity android:name=".D$4" android:launchMode="singleInstance"/>
<activity android:name=".D$5" android:launchMode="singleInstance"/>
<activity android:name=".D$6" android:launchMode="singleInstance"/>
<activity android:name=".D$7" android:launchMode="singleInstance"/>
<activity android:name=".D$8" android:launchMode="singleInstance"/>
那以上这些占坑的Activity是在什么时候被偷梁换柱的呢?这就涉及到Activity的启动过程了。
Activity启动过程
从上文的分析我们已经知道,Activity的startActivity最终会调用到ActivityManagerNative中的内部类ActivityManagerProxy的startActivity方法,然后会通过Binder IPC(至于Binder驱动是如何传递信息的,有时间我会再写一篇分析)调用到AMS所在进程的AMS的startActivity方法,Android系统的组件生命周期管理就是在AMS里面完成的。
[->ActivityManagerService]
ActivityManagerService#startActivity
@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags, profilerInfo, options,
UserHandle.getCallingUserId());
}
直接调用了startActivityAsUser这个方法,接着是ActivityStarter的startActivityMayWait方法。
startActivityMayWait这个方法前面对参数进行了一系列处理,在这个方法内部对传进来的Intent进行了解析,并尝试从中取出关于启动Activity的信息。
然后这个方法调用了当前类的startActivityLocked方法,在startActivityLocked方法内部进行了一系列重要的检查:比如权限检查,Activity的exported属性检查等等。上文说过,启动没有在Manifestfest总生命的Activity会抛出异常也是在这里进行检查的,如下:
[->ActivityStarter.java]
ActivityStarter#startActivityLocked
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!
//当前class不能解析
err = ActivityManager.START_INTENT_NOT_RESOLVED;
}
if (err == ActivityManager.START_SUCCESS && aInfo == null) {
// We couldn't find the specific class specified in the Intent.
// Also the end of the line.
//当前class不存在
err = ActivityManager.START_CLASS_NOT_FOUND;
}
这里返回ActivityManager.START_CLASS_NOT_FOUND之后,在Instrument的execStartActivity返回之后会检查这个值,然后抛出异常:
[->Instrumentation.java]
Instrumentation#execStartActivity
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);
checkStartActivityResult(result, intent);
}
public static void checkStartActivityResult(int res, Object intent) {
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?");
throw new ActivityNotFoundException(
"No Activity found to handle " + intent);
}
}
从这里,我们明白了必须在AndroidManifest.xml中显示声明使用的Activity的原因;然而这个校检过程发生在AMS所在的进程system_server,我们是不能更改system_server进程的东西的。
在明白了为什么一定要在注册表中声明Activity之后,我们来看下activity真正启动的地方。通过一开始的Activity启动图我们知道,调用最终会到达ActivityStackSupervisor的realStartActivityLocked方法。然后它调用了ApplicationThread的scheduleLaunchActivity方法,开始了真正的Activity对象创建以及启动过程。
ApplicationThread实际上是一个Binder对象,是app所在的进程与AMS坐在进程system_server通信的桥梁:
1.app进程会委托AMS进程完成Activity生命周期的管理以及任务栈的管理;这个通信过程AMS是server端,app进程通过持有AMS的client代理ActivityManagerNative完成通信过程;
2.AMS进程完成生命周期管理以及任务栈的管理后,会把控制权交还给app进程,让app进程完成Activity类对象的创建,以及生命周期的回调;这个通信过程也是通过Binder完成的。此时app为server端,它的Binder对象存在于ActivityThread的内部类ApplicationThread;AMS所在的client通过持有IApplicationThread的代理对象完成对于app进程的通信。
app进程内部的ApplicationThread与App的主线程并不在一个线程类,所以他们之间的通信需要通过Handler完成,这个Handler存在于ActivityThread中,它的名字叫做H。
ApplicationThread的 scheduleLaunchActivity方法,正式包装了参数最终使用Handler发送了一个消息,然后在ActivityThread中对消息进行处理:
[->ActivityThread.java]
ActivityThread#H#handleMessage
case LAUNCH_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
可以看到直接调用了handleLaunchActivity方法
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//.....省略代码
// Initialize before creating the activity
WindowManagerGlobal.initialize();
//执行启动Activity
Activity a = performLaunchActivity(r, customIntent);
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
//第2步
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
}
1.使用ClassLoader加载并通过反射创建Activity对象
2.如果Application还没有创建,那么创建Application对象并回调相应的生命周期方法
整个系统的Activity堆栈,Activity生命周期回调都是由AMS所在的系统进程system_server帮开发者完成的;Android的Framework层帮忙完成了诸如生命周期管理等繁琐复杂的过程,简化了应用层的开发。
启动插件中声明的Activity
通过上文的分析,我们已经了解了Activity的启动过程。之前我们说过,启动插件中的Activity会遇到的问题是必须在注册表中进行声明,我们也说了解决的思路是在注册表中声明替身Activity,然后在合适得到时候把这个假的替换成我们真正需要启动的Activity。
我们可以先启动一个已经存在在注册表中的替身Activity,让这个Activity今入AMS进程并接受检验;最终在创建Activity的时候替换成我们真正需要启动的Activity;这样就成功得到欺骗了AMS进程,完成了偷梁换柱的工作。
使用StubActivity绕过AMS
在启动Activity的控制权转交给AMS进程之前,我们需要想办法临时把TargetActivity替换成StubActivity,我们来看一下Virtualapk里面是如何做到的:
1.代理(hook)系统的Instrumentation
前面分析Activity的启动流程的时候,我们知道无论是通过Context还是Activity的startActivity方法最终都会通过Instrumentation的execStartActivity方法进行调用。
[->PluginManager.java]
PluginManager#hookInstrumentationAndHandler
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);
}
//创建自定义Instrumentation 并注入到ActivityThread中
final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);
Object activityThread = ReflectUtil.getActivityThread(this.mContext);
ReflectUtil.setInstrumentation(activityThread, instrumentation);
//将ActivityThread的H:Handler的mCallBack替换为自定义的CallBack
ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
this.mInstrumentation = instrumentation;
} catch (Exception e) {
e.printStackTrace();
}
}
它在PluginManager初始化的时候被调用,首先通过反射获取到了ActivityThread中的instrumentation,并通过VAInstrumentation对系统的Instrumentation进行了封装,其实VAInstrumentation就是系统Instrumentation的一个代理,然后这个VAInstrumentation 替换掉原来ActivityThread中的Instrumentation,VAInstrumentation 里重写了execStartActivity,realExecStartActivity等方法,为什么要重写这些方法,接下来我们一个个来分析。
首先是execStartActivity
[->VAInstrumentation.java]
VAInstrumentation#execStartActivity
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
//将隐试启动插件里activity的意图转化为显示启动,因为在宿主通过隐式启动插件activity,是无法启动的,原因很简单,插件的activity并不在宿主apk中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;
}
VA的execStartActivity方法中主要做了以下这些事情:
1.将隐试启动插件里activity的意图转化为显示启动,因为在宿主通过隐式启动插件activity,是无法启动的,原因很简单,插件的activity并不在宿主apk中
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
ComponentsHandler的transformIntentToExplicitAsNeeded做的就是这个事情,里面会通过mPluginManager.resolveActivity方法查找插件里面第一个符合隐式条件的第一个ResolveInfo,然后new component 设置进intent,大家可以追着代码进去看
public Intent transformIntentToExplicitAsNeeded(Intent intent) {
ComponentName component = intent.getComponent();
if (component == null) {
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;
}
2.临时把TargetActivity替换成StubActivity,我们前面说过,要进行偷梁换柱,也就是在这个地方
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是不是插件里的,如果是,则将目标Activity的包名和TargetActivity的名字存储到intent中,接着通过dispatchStubActivity方法,根据要启动的TargetActivity是什么启动模式的来启动相应的代理StubActivity
*/
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);
}
}
/**
*dispatchStubActivity方法里面会通过TargetActivity是什么启动模式的来获取相应的代理StubActivity的类名并设置进intent中,这里就完成了偷梁换柱的过程。
*/
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);
}
3.调用系统Instrumentation的execStartActivity方法,进入正常启动Activity的流程
ActivityResult result = realExecStartActivity(who, contextThread, token, target,
intent, requestCode, options);
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) {
e.printStackTrace();
}
return result;
}
这里是通过反射调用系统Instrumentation的execStartActivity方法(因为api中标注了 {@hide}注释,属于不公开的api,只允许内部调用)
通过这个替换过程,在ActivityManagerNative的startActivity调用之后,system_server端收到Binder驱动的消息,开始执行ActivityManagerService里面真正的startActivity方法;这时候AMS看到的intent参数里面的组件已经是StubActivity了,因此可以成功绕过检查。
将StubActivity替换回TargetActivity
在上文中间TargetActivity替换为StubActivity以后,接下来就是跨进程通信,system_server管理Activity的生命周期,之前我们用替身StubActivity临时换了TargetActivity,肯定需要在合适的时候替换回来,Virtualapk替换的回来的地方是哪里呢?
在AMS进程里面我们是没有办法换回来的,因为我们不可能调用到其他进程的代码,因此我们要等AMS把控制权交给App所在进程,还记得前面我们说ApplicationThread所在的Binder线程池通过Handler与ActivityThread进行通信么?
那我们是不是可以拦截Handler的handleMessage时做处理呢?我们简单的看一下Handler的代码:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Handler类消息分发的过程:
1.如果传递的Message本身就有callback,那么直接使用Message对象的callback方法;
2.如果Handler类的成员变量mCallback存在,那么首先执行这个mCallback回调;
3.如果mCallback的回调返回true,那么表示消息已经成功处理;直接结束。
4.如果mCallback的回调返回false,那么表示消息没有处理完毕,会继续使用Handler类的handleMessage方法处理消息。
ActivityThread中的Handler类H重载了handleMessage方法:
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
// 省略一万行代码。。。。
}
}
从dispathMessage方法中,我们可以得出思路:我们可以拦截这一过程:把这个H类的mCallback替换为我们的自定义实现,这样dispathMessage就会首先使用这个自定义的mCallback,然后看情况使用H重载的handleMessage。
这个Handler.Callback是一个接口,我们可以使用动态代理或者普通代理完成Hook,在Virtualapk中使用普通的静态代理方式,还记得前面的VAInstrumentation类么?它不仅继承了系统的Instrumentation,同时还实现了 Handler.Callback 接口。
这里再次贴出前面PluginManager初始化的时候hookInstrumentationAndHandler的代码
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);
//HOOK HandlerCallback
ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
this.mInstrumentation = instrumentation;
} catch (Exception e) {
e.printStackTrace();
}
}
public static void setHandlerCallback(Context base, Handler.Callback callback) {
try {
Object activityThread = getActivityThread(base);
Handler mainHandler = (Handler) ReflectUtil.invoke(activityThread.getClass(), activityThread, "getHandler", (Object[])null);
ReflectUtil.setField(Handler.class, mainHandler, "mCallback", callback);
} catch (Exception e) {
e.printStackTrace();
}
}
果不其然,替换了ActivityThread中的Handler的mCallback为VAInstrumentation ,所以当ActivityThread的H进行dispathMessage的时候,必定会走到VAInstrumentation 的handleMessage方法,代码如下:
[->VAInstrumentation.java]
@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;
}
可以看到在VAInstrumentation 的handleMessage方法只是拦截了LAUNCH_ACTIVITY的处理,在里面将intent中的activityInfo.theme替换为插件的theme,并给intent设置了ClassLoader,这里为什么要设置一个ClassLoader?我想是因为在ActivityThread的performLaunchActivity方法会将其取出,然后设置进mInstrumentation.newActivity方法中
[->ActivityThread.java#performLaunchActivity]
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
}
经过上面的调用,又会进入到VAInstrumentation 的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);
}
首先是cl.loadClass(className),注意,我使用的demo,这里的className为com.didi.virtualapk.core.A$1,还有可能是其他的,但都会是在清单文件声明的stubActivity的名字
调用cl.loadClass(className)去加载这些类,肯定是会爆出ClassNotFoundException异常的,因为这些类并不存在,他们只是在清单文件中起到占坑的作用,用来欺骗系统的,这里的设计确实非常巧妙,接下来自然走到catch里,catch里自然是去构建真正需要加载的TargetActivity
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
传入newActivity方法的是LoadedPlugin中的ClassLoader,这个ClassLoader已经是修改过的,可以加载插件和宿主里的类。关于classLoader,可以参考Android插件化学习之路(二)之ClassLoader完全解析
然后将intent设置进插件Activity 中,注意,这里的intent里的className还是com.didi.virtualapk.core.A$1
接下来会调用到ActivityThread中的performLaunchActivity 中的如下代码
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
然后又进入到VAInstrumentation 的callActivityOnCreate方法中
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);
}
最后这里就是进行一些替换的工作了,替换掉TargetActivity里的mResources,mBase,mApplication为LoadedPlugin中生成的可以用于加载插件资源的相应Resources和Context,为什么要进行这些替换工作?后续文章会进行详细讲解,最后就是调用系统Instrumentation的callActivityOnCreate其启动这个插件TargetActivity了,我们可以发现,到最后intent里的className还是com.didi.virtualapk.core.A$1,这是因为这个intent只是用来欺骗系统的作用。
public void callActivityOnCreate(Activity activity, Bundle icicle,
PersistableBundle persistentState) {
prePerformCreate(activity);
activity.performCreate(icicle, persistentState);
postPerformCreate(activity);
}
可以看见,接下来就是调用插件Activity的onCreate方法了,就完了加载插件Activity的过程。
你可能会问通过上面的方式启动的插件TargetActivity就具有生命周期了吗?答案是肯定的,大家可以通过demo去验证这一点,我们以onDestroy为例分析一下这个过程:
Activity的finish方法最终会通过ActivityManagerNative到AMS然后接着通过ApplicationThread到ActivityThread,然后通过H转发消息到ActivityThread的handleDestroyActivity,接着这个方法把任务交给performDestroyActivity完成。
[->ActivityThread.java#performDestroyActivity]
performDestroyActivity,关键代码如下:
ActivityClientRecord r = mActivities.get(token);
// ......
mInstrumentation.callActivityOnDestroy(r.activity);
通过mActivities拿到了一个ActivityClientRecord,然后直接把这个record里面的Activity交给Instrument类完成了onDestroy的调用。
这里的r.activity是StubActivity为什么它能正确完成对TargetActivity生命周期的回调呢?
答案是token。AMS与ActivityThread之间对于Activity的生命周期的交互,并没有直接使用Activity对象进行交互,而是使用一个token来标识,这个token是binder对象,因此可以方便地跨进程传递。Activity里面有一个成员变量mToken代表的就是它,token可以唯一地标识一个Activity对象,它在Activity的attach方法里面初始化;
在AMS处理Activity的任务栈的时候,使用这个token标记Activity,因此在demo里面,AMS进程里面的token对应的是StubActivity,但是在App进程里面,token对应的却是TargetActivity!因此,在ActivityThread执行回调的时候,能正确地回调到TargetActivity相应的方法。
为什么App进程里面,token对应的是TargetActivity呢?
ActivityClientRecord是在mActivities里面取出来的,确实是根据token取;那么这个token是什么时候添加进去的呢?我们看performLaunchActivity就完成明白了:它通过classloader加载了TargetActivity,然后完成一切操作之后把这个activity添加进了mActivities!另外,在这个方法里面我们还能看到对Ativity.attach方法的调用,它传递给了新创建的Activity一个token对象,而这个token是在ActivityClientRecord构造函数里面初始化的。
至此,整个Virtualapk框架对于插件Activity的管理就到此结束了,接下来还会继续分析其他组件的原理,最后,如果小弟有分析偏差的地方,欢迎矫正。