接上篇,这一篇分析一下“占坑”部分。既然是占坑,先看一下DroidPlugin都占了哪些坑?
扫一眼AndroidManifest.xml可以发现,除了主进程意外,还注册了”:PluginP01” ~ ”:Plugin08”这8个进程,所以一共9个进程。
每个进程里注册了30个activity,1个service,和一个content provider:
• .stub.ActivityStub$P00$Standard00从activity名字就可以看出来,基本上把各种launch mode以及不同theme的activity都注册了一遍。这样当插件app需要启动某一类型的activity时,分配一个空闲的出来用就可以了。
Service只注册了一个,如果一个进程要运行多个service怎么办?后面会看到,会有一个service manager来解决这个问题。
Content provider也只注册了一个,不过一般够用了。
下面挨个分析一下四大组件动态注册的过程。
一、Activity动态注册
写过android app的都知道,所有activity组件都必须在AndroidManifest.xml里声明,否则是找不到的。但是在插件app里会定义哪些activity我们事先是不知道的,又怎么可能定义在AndroidManifest.xml里呢?这里又再次需要发挥“欺下瞒上”的技巧了:系统层看到的只是上面预定义的这些activity,而插件则以为它调用的系统服务是直接返回的,根本意识不到其实是从DroidPlugin那边过了一道。简单回顾一下activity启动的流程:
标红的两部分是被DroidPlugin hook住的两个主要API:
第一个API是最被经常使用的startActivity(),hook的方法上一篇已经介绍过了,主要是通过Java的反射机制替换掉IActivityManager全局对象,具体参见IActivityManager的onInstall()方法。
第二个API是handleLaunchActivity(),这个API隶属于ActivityThread的一个叫做H的内部类,该类继承自Handler,ActivityThread有一个该类型的成员变量mH。scheduleLaunchActivity()里会发送一个LAUNCH_ACTIVITY类型的消息,该消息被mH捕获并调用handleLaunchActivity()。说到这里,大家应该很容易想到如何hook这个API了,直接把这个mH对象通过反射替换掉呗!不错,这就是上一篇最后提到的PluginCallbackHook的工作,看一下PluginCallbackHook的onInstall()方法:
protected void onInstall(ClassLoader classLoader) throws Throwable {
Object target = ActivityThreadCompat.currentActivityThread();
Class ActivityThreadClass = ActivityThreadCompat.activityThreadClass();
/*替换ActivityThread.mH.mCallback,拦截组件调度消息*/
Field mHField = FieldUtils.getField(ActivityThreadClass, "mH");
Handler handler = (Handler) FieldUtils.readField(mHField, target);
Field mCallbackField = FieldUtils.getField(Handler.class, "mCallback");
//*这里读取出旧的callback并处理*/
Object mCallback = FieldUtils.readField(mCallbackField, handler);
if (!PluginCallback.class.isInstance(mCallback)) {
PluginCallback value = mCallback != null ? new PluginCallback(mHostContext, handler, (Handler.Callback) mCallback) : new PluginCallback(mHostContext, handler, null);
value.setEnable(isEnable());
mCallbacks.add(value);
FieldUtils.writeField(mCallbackField, handler, value);
Log.i(TAG, "PluginCallbackHook has installed");
} else {
Log.i(TAG, "PluginCallbackHook has installed,skip");
}
}
可以看到,不是完全替换掉mH,而是把mH的mCallback成员变量替换成了一个PluginCallback对象,这样就可以在PluginCallback的handleMessage()里任意拦截想要处理的消息了(注: mH原来的mCallback是null)。消息处理完后如果还想再送回ActivityThread进行处理,返回false,这样就会继续调用mH的handleMessage(),具体参见Handler的dispatchMessage()方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
我们回过头来看一下PluginCallback的handleMessage()方法:
public boolean handleMessage(Message msg) {
... ...
if (msg.what == LAUNCH_ACTIVITY) {
return handleLaunchActivity(msg);
}
if (mCallback != null) {
return mCallback.handleMessage(msg);
} else {
return false;
}
... ...
}
可以看到,这里只拦截了一个消息,就是LAUNCH_ACTIVITY。在做完该做的处理后,返回false,这样就会会再通过mH调用回ActivityThread的handleLaunchActivity()。
现在这两个点是怎么被hook的已经搞清楚了,接下来的问题就是这两个hook点都进行了什么样的处理?看下面这张图就明白了:
在startActivity()的hook里,会把原始的intent作为一个extra包装到一个新的intent里,我们可以称之为“穿马甲”。同时选出一个stub activity作为这个新intent的component,这个选出来的stub activity要跟原始intent里请求的activity的launch mode、theme完全相同。这样AMS那边收到的其实是一个假的intent,AMS以为真正启动就是这个预先占坑的stub activity。
当AMS回调回ActivityThread时,在handleLaunchActivity()的hook里,会把这个假的intent的马甲脱掉,恢复原来intent的模样,通过类加载器去加载真正要启动activity类,完成插件的启动。
下面我们就来看看这两段代码:
1. 先看startActivity()的hook,简单起见我们只看高版本android的处理:
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
RunningActivities.beforeStartActivity();
... ...
bRet = doReplaceIntentForStartActivityAPIHigh(args);
... ...
}
这里做了两件事,首先预先占坑的stub activity数量是有限的,因此要判断是否有可用的activity,如果没有的话得腾出一个来。看一下RunningActivities的beforeStartActivity()方法:
public static void beforeStartActivity() {
synchronized (mRunningActivityList) {
for (RunningActivityRecord record : mRunningActivityList.values()) {
if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) {
continue;
} else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
doFinshIt(mRunningSingleTopActivityList);
} else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
doFinshIt(mRunningSingleTopActivityList);
} else if (record.stubActivityInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
doFinshIt(mRunningSingleTopActivityList);
}
}
}
}
可以看到,除了MULTIPLE启动模式的activity record,其余的都会调用doFinshIt()方法(话说代码里的错别字挺多的):
private static void doFinshIt(Map runningActivityList) {
if (runningActivityList != null && runningActivityList.size() >= PluginManager.STUB_NO_ACTIVITY_MAX_NUM - 1) {
List activitys = new ArrayList<>(runningActivityList.size());
activitys.addAll(runningActivityList.values());
Collections.sort(activitys, sRunningActivityRecordComparator);
RunningActivityRecord record = activitys.get(0);
if (record.activity != null && !record.activity.isFinishing()) {
record.activity.finish();
}
}
}
STUB_NO_ACTIVITY_MAX_NUM定义为4,从上面的AndroidManifest.xml也可以看出来,这几种类型的stub activity都只占了4个坑。如果目前已经占用的activity数目大于等于3(为什么不是4?),就挑出最早启动的activity把它finish掉。
怎么判断哪个stub activity是最早启动的呢?每个activity的RunningActivityRecord在创建时都会指定一个index,从0开始,依次加1,这样越后启动的activity的index就会越大(具体参见RunningActivities的onActivityCreate()方法)。而上面代码里的比较器sRunningActivityRecordComparator其实就是按index的升序把所有记录进行排序,这样取出来的第一个记录就是最早启动的activity了。
接下来我们看第二件事情,也是最关键的一步,穿马甲。看下doReplaceIntentForStartActivityAPIHigh()方法:
protected boolean doReplaceIntentForStartActivityAPIHigh(Object[] args) throws RemoteException {
int intentOfArgIndex = findFirstIntentIndexInArgs(args);
if (args != null && args.length > 1 && intentOfArgIndex >= 0) {
Intent intent = (Intent) args[intentOfArgIndex];
if (!PluginPatchManager.getInstance().canStartPluginActivity(intent)) {
PluginPatchManager.getInstance().startPluginActivity(intent);
return false;
}
ActivityInfo activityInfo = resolveActivity(intent);
if (activityInfo != null && isPackagePlugin(activityInfo.packageName)) {
ComponentName component = selectProxyActivity(intent);
if (component != null) {
Intent newIntent = new Intent();
try {
ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(component.getPackageName());
setIntentClassLoader(newIntent, pluginClassLoader);
} catch (Exception e) {
Log.w(TAG, "Set Class Loader to new Intent fail", e);
}
newIntent.setComponent(component);
newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);
newIntent.setFlags(intent.getFlags());
String callingPackage = (String) args[1];
if (TextUtils.equals(mHostContext.getPackageName(), callingPackage)) {
newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
args[intentOfArgIndex] = newIntent;
args[1] = mHostContext.getPackageName();
} else {
Log.w(TAG, "startActivity,replace selectProxyActivity fail");
}
}
}
return true;
}
代码的逻辑还是很清楚的,分为下面几步:
• 从参数列表里获取intent 参数至此,这个假intent就包装完成了,待会儿就会被发到AMS那边去。
2. 再看看handleLaunchActivity()的hook,节选一些主要代码:
private boolean handleLaunchActivity(Message msg) {
... ...
Object obj = msg.obj;
Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent");
stubIntent.setExtrasClassLoader(mHostContext.getClassLoader());
Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
... ...
ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager());
ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0);
... ...
PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(targetComponentName.getPackageName());
setIntentClassLoader(targetIntent, pluginClassLoader);
... ...
FieldUtils.writeDeclaredField(msg.obj, "intent", targetIntent);
FieldUtils.writeDeclaredField(msg.obj, "activityInfo", targetActivityInfo);
... ...
}
主要执行了下面这些步骤:
• 从Message 的obj 字段里取出intent ,注意这里是AMS 返回过来的之前创建的那个假intent ,然后从其EXTRA_TARGET_INTENT 字段里取出原始intent关于activity的动态加载就分析完了,下面分析service的动态注册。
二、Service动态注册
Service的动态注册和Activity类似,也有“穿马甲”和“脱马甲”这两个过程,具体可以参见IActivityManagerHookHandle()里hook的startService()、stopService()、bindService()、unbindService()这几个方法。
但是之前我们提过,AndroidManifest.xml里每个进程只占了一个service的坑,如果插件程序要启动多个service怎么办?DroidPlugin定义了一个ServcesManager来解决这个问题(缺了个i看起来总是怪怪的,难道是大神故意敲错的?)。
首先我们看一下AbstractSubService的实现,所有的stub service都继承自这个类。这个类有一个成员变量叫mCreator,类型是ServcesManager:
private static ServcesManager mCreator = ServcesManager.getDefault();
这个成员变量在哪里用的呢?挑一个onStart()看一下:
public void onStart(Intent intent, int startId) {
... ...
mCreator.onStart(this, intent, 0, startId);
... ...
}
继续看ServcesManager的onStart()方法:
public int onStart(Context context, Intent intent, int flags, int startId) throws Exception {
Intent targetIntent = intent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
if (targetIntent != null) {
ServiceInfo targetInfo = PluginManager.getInstance().resolveServiceInfo(targetIntent, 0);
if (targetInfo != null) {
Service service = mNameService.get(targetInfo.name);
if (service == null) {
handleCreateServiceOne(context, intent, targetInfo);
}
handleOnStartOne(targetIntent, flags, startId);
}
}
return -1;
}
这里先取出了原始intent,然后调用了handleCreateServiceOne()方法,这个方法是关键所在。 要搞清楚这个方法在干什么,首先看一下ActivityThread的handleCreateService()方法:
private void handleCreateService(CreateServiceData data) {
... ...
mServices.put(data.token, service);
... ...
}
这个方法有一个CreateServiceData类型的参数,用来存储创建service需要的信息,在service创建完毕后,会把它放进一个叫做mServices的map以便后续访问。搞清楚了这些,下面我们来看看handleCreateServiceOne()都干了哪些事情:
private void handleCreateServiceOne(Context hostContext, Intent stubIntent, ServiceInfo info) throws Exception {
... ...
Object activityThread = ActivityThreadCompat.currentActivityThread();
IBinder fakeToken = new MyFakeIBinder();
Class CreateServiceData = Class.forName(ActivityThreadCompat.activityThreadClass().getName() + "$CreateServiceData");
Constructor init = CreateServiceData.getDeclaredConstructor();
if (!init.isAccessible()) {
init.setAccessible(true);
}
Object data = init.newInstance();
FieldUtils.writeField(data, "token", fakeToken);
FieldUtils.writeField(data, "info", info);
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
FieldUtils.writeField(data, "compatInfo", CompatibilityInfoCompat.DEFAULT_COMPATIBILITY_INFO());
}
Method method = activityThread.getClass().getDeclaredMethod("handleCreateService", CreateServiceData);
if (!method.isAccessible()) {
method.setAccessible(true);
}
method.invoke(activityThread, data);
Object mService = FieldUtils.readField(activityThread, "mServices");
Service service = (Service) MethodUtils.invokeMethod(mService, "get", fakeToken);
MethodUtils.invokeMethod(mService, "remove", fakeToken);
mTokenServices.put(fakeToken, service);
mNameService.put(info.name, service);
... ...
}
该方法主要执行了以下步骤:
• 篡改handleCreateService() 方法的CreateServiceData 参数,为service 指定一个假的token 。CreateServiceData 是一个内部类因此需要反射。看到这里ActivityThread估计要掩面而泣了,你们怎么能这样!最后用一幅图来直观地描述上面这个过程:
三、ContentProvider动态注册
ContentProvider的动态注册比较简单了,hook住ContentProvider的一系列API替换参数就可以了,具体参见IContentProviderInvokeHandle。我们看一下beforeInvoke()方法:
protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
if (!mLocalProvider && mStubProvider != null) {
final int index = indexFirstUri(args);
if (index >= 0) {
Uri uri = (Uri) args[index];
String authority = uri.getAuthority();
if (!TextUtils.equals(authority, mStubProvider.authority)) {
Uri.Builder b = new Builder();
b.scheme(uri.getScheme());
b.authority(mStubProvider.authority);
b.path(uri.getPath());
b.query(uri.getQuery());
b.appendQueryParameter(Env.EXTRA_TARGET_AUTHORITY, authority);
b.fragment(uri.getFragment());
args[index] = b.build();
}
}
}
return super.beforeInvoke(receiver, method, args);
}
代码很简单,把Uri的authority替换成stub provider的authority就行了,这样访问的就是stub provider指向的路径了。
四、BroadcastReceiver动态注册
BroadcastReceiver要解决的问题是如何处理静态receiver,用官方文档的说法叫做“静态广播非静态”。这也很容易理解,宿主根本不知道插件程序会注册哪些receiver,显然AndroidManifest.xml也不会有这些receiver,那就只能通过动态注册receiver来解决了。
具体代码是在PluginInstrumentation的callApplicationOnCreate()方法中:
public void callApplicationOnCreate(Application app) {
... ...
PluginProcessManager.registerStaticReceiver(app, app.getApplicationInfo(), app.getClassLoader());
... ...
}
这里调用了PluginProcessManager的registerStaticReceiver()方法来动态注册receiver:
public static void registerStaticReceiver(Context context, ApplicationInfo pluginApplicationInfo, ClassLoader cl) throws Exception {
List infos = PluginManager.getInstance().getReceivers(pluginApplicationInfo.packageName, 0);
if (infos != null && infos.size() > 0) {
CharSequence myPname = null;
try {
myPname = PluginManager.getInstance().getProcessNameByPid(android.os.Process.myPid());
} catch (Exception e) {
}
for (ActivityInfo info : infos) {
if (TextUtils.equals(info.processName, myPname)) {
try {
List filters = PluginManager.getInstance().getReceiverIntentFilter(info);
for (IntentFilter filter : filters) {
BroadcastReceiver receiver = (BroadcastReceiver) cl.loadClass(info.name).newInstance();
context.registerReceiver(receiver, filter);
}
} catch (Exception e) {
Log.e(TAG, "registerStaticReceiver error=%s", e, info.name);
}
}
}
}
}
首先先解析插件程序需要注册的所有静态receiver的列表,获取其IntentFilter,然后根据类名加载对应的receiver类,最后通过registerReceiver()完成动态注册。包的解析是一个兼容性问题比较大的部分,其实就是通过反射调用PackageParser的API(比如generatePackageInfo()),但是各个版本的android改动比较多,所以你会发现在pm/parser/下面针对不同的SDK版本有一堆不同的解析相关的类。
还有一个问题,在AndroidManifest.xml中,除了这些静态注册的receiver,每个activity或者service也有可能声明intent filter,例如:
我们把要启动的activity替换成了stub activity,那这些intent filter是怎么匹配上的呢?要搞清楚这一点,首先要了解下这个匹配是在哪里做的。实际上,在ActivityThread的performLaunchActivity()方法里,如果没有显式指定类名,会先调用resolveActivity()去找出最匹配的component:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
... ...
ComponentName component = r.intent.getComponent();
if (component == null) {
component = r.intent.resolveActivity(
mInitialApplication.getPackageManager());
r.intent.setComponent(component);
}
... ...
}
这个方法会调用最终会调用到IPackageManager的resolveIntent()方法,别忘了,IPackageManager也是被我们hook住的!
IPackageManager会调用PluginManager的resolveIntent(),再往前跟踪会进入IntentMatcher的resolveIntet():
public static final List resolveIntent(Context context, Map pluginPackages, Intent intent, String resolvedType, int flags) throws Exception {
... ...
queryIntentActivityForPackage(context, parser, intent, flags, list);
... ...
}
这个queryIntentActivityForPackage()会遍历所有的activity,完成最终的匹配工作(返回outList):
private static void queryIntentActivityForPackage(Context context, PluginPackageParser packageParser, Intent intent, int flags, List outList) throws Exception {
List activityInfos = packageParser.getActivities();
if (activityInfos != null && activityInfos.size() >= 0) {
for (ActivityInfo activityInfo : activityInfos) {
ComponentName className = new ComponentName(activityInfo.packageName, activityInfo.name);
List intentFilters = packageParser.getActivityIntentFilter(className);
if (intentFilters != null && intentFilters.size() > 0) {
for (IntentFilter intentFilter : intentFilters) {
int match = intentFilter.match(context.getContentResolver(), intent, true, "");
if (match >= 0) {
ActivityInfo flagInfo = packageParser.getActivityInfo(new ComponentName(activityInfo.packageName, activityInfo.name), flags);
if ((flags & PackageManager.MATCH_DEFAULT_ONLY) != 0) {
if (intentFilter.hasCategory(Intent.CATEGORY_DEFAULT)) {
ResolveInfo resolveInfo = newResolveInfo(flagInfo, intentFilter);
resolveInfo.match = match;
resolveInfo.isDefault = true;
outList.add(resolveInfo);
} else {
}
} else {
ResolveInfo resolveInfo = newResolveInfo(flagInfo, intentFilter);
resolveInfo.match = match;
resolveInfo.isDefault = false;
outList.add(resolveInfo);
}
... ...
}
至此,四大组件的动态注册过程分析完毕。下一篇分析进程管理相关的部分。