DroidPlugin代码分析(三) 占坑、动态注册

接上篇,这一篇分析一下“占坑”部分。既然是占坑,先看一下DroidPlugin都占了哪些坑?

扫一眼AndroidManifest.xml可以发现,除了主进程意外,还注册了”:PluginP01” ~ ”:Plugin08”这8个进程,所以一共9个进程。

每个进程里注册了30activity1service,和一个content provider

  •  .stub.ActivityStub$P00$Standard00
  •  .stub.ActivityStub$P00$SingleInstance00
  •  .stub.ActivityStub$P00$SingleInstance01
  •  .stub.ActivityStub$P00$SingleInstance02
  •  .stub.ActivityStub$P00$SingleInstance03
  •  .stub.ActivityStub$P00$SingleTask00
  •  .stub.ActivityStub$P00$SingleTask01
  •  .stub.ActivityStub$P00$SingleTask02
  •  .stub.ActivityStub$P00$SingleTask03
  •  .stub.ActivityStub$P00$SingleTop00
  •  .stub.ActivityStub$P00$SingleTop01
  •  .stub.ActivityStub$P00$SingleTop02
  •  .stub.ActivityStub$P00$SingleTop03
  •  .stub.ActivityStub$Dialog$P00$Standard00
  •  .stub.ActivityStub$Dialog$P00$SingleInstance00
  •  .stub.ActivityStub$Dialog$P00$SingleInstance01
  •  .stub.ActivityStub$Dialog$P00$SingleInstance02
  •  .stub.ActivityStub$Dialog$P00$SingleInstance03
  •  .stub.ActivityStub$Dialog$P00$SingleTask00
  •  .stub.ActivityStub$Dialog$P00$SingleTask01
  •  .stub.ActivityStub$Dialog$P00$SingleTask02
  •  .stub.ActivityStub$Dialog$P00$SingleTask03
  •  .stub.ActivityStub$Dialog$P00$SingleTop00
  •  .stub.ActivityStub$Dialog$P00$SingleTop01
  •  .stub.ActivityStub$Dialog$P00$SingleTop02
  •  .stub.ActivityStub$Dialog$P00$SingleTop03
  •  .stub.ServiceStub$StubP00$P00
  •  .stub.ContentProviderStub$StubP00

activity名字就可以看出来,基本上把各种launch mode以及不同themeactivity都注册了一遍。这样当插件app需要启动某一类型的activity时,分配一个空闲的出来用就可以了。

Service只注册了一个,如果一个进程要运行多个service怎么办?后面会看到,会有一个service manager来解决这个问题。

Content provider也只注册了一个,不过一般够用了。

下面挨个分析一下四大组件动态注册的过程。

一、Activity动态注册

写过android app的都知道,所有activity组件都必须在AndroidManifest.xml里声明,否则是找不到的。但是在插件app里会定义哪些activity我们事先是不知道的,又怎么可能定义在AndroidManifest.xml里呢?这里又再次需要发挥“欺下瞒上”的技巧了:系统层看到的只是上面预定义的这些activity,而插件则以为它调用的系统服务是直接返回的,根本意识不到其实是从DroidPlugin那边过了一道。简单回顾一下activity启动的流程:

DroidPlugin代码分析(三) 占坑、动态注册_第1张图片

标红的两部分是被DroidPlugin hook住的两个主要API

第一个API是最被经常使用的startActivity()hook的方法上一篇已经介绍过了,主要是通过Java的反射机制替换掉IActivityManager全局对象,具体参见IActivityManageronInstall()方法。

第二个APIhandleLaunchActivity(),这个API隶属于ActivityThread的一个叫做H的内部类,该类继承自HandlerActivityThread有一个该类型的成员变量mHscheduleLaunchActivity()里会发送一个LAUNCH_ACTIVITY类型的消息,该消息被mH捕获并调用handleLaunchActivity()。说到这里,大家应该很容易想到如何hook这个API了,直接把这个mH对象通过反射替换掉呗!不错,这就是上一篇最后提到的PluginCallbackHook的工作,看一下PluginCallbackHookonInstall()方法:

    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,而是把mHmCallback成员变量替换成了一个PluginCallback对象,这样就可以在PluginCallbackhandleMessage()里任意拦截想要处理的消息了(注: mH原来的mCallback是null)。消息处理完后如果还想再送回ActivityThread进行处理,返回false,这样就会继续调用mHhandleMessage(),具体参见HandlerdispatchMessage()方法:

    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调用回ActivityThreadhandleLaunchActivity()

现在这两个点是怎么被hook的已经搞清楚了,接下来的问题就是这两个hook点都进行了什么样的处理?看下面这张图就明白了:

DroidPlugin代码分析(三) 占坑、动态注册_第2张图片

startActivity()hook里,会把原始的intent作为一个extra包装到一个新的intent里,我们可以称之为“穿马甲”。同时选出一个stub activity作为这个新intentcomponent,这个选出来的stub activity要跟原始intent里请求的activitylaunch modetheme完全相同。这样AMS那边收到的其实是一个假的intentAMS以为真正启动就是这个预先占坑的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,如果没有的话得腾出一个来。看一下RunningActivitiesbeforeStartActivity()方法:

    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是最早启动的呢?每个activityRunningActivityRecord在创建时都会指定一个index,从0开始,依次加1,这样越后启动的activityindex就会越大(具体参见RunningActivitiesonActivityCreate()方法)。而上面代码里的比较器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 获取待启动的ActivityInfo ,如果发现是插件程序,则进入下面的intent 包装流程
  •  调用selectProxyActivity() 选出一个合适的stub activity ,主要是根据launch mode 还有theme 进行判断
  •  创建一个新的intent ,其component 指向选出来的stub activity flags 和原始intent 相同,同时将原始的intent 作为一个EXTRA_TARGET_INTENT 参数设置到这个新的intent 里面
  •  最后,如果是宿主程序启动的插件,加上FLAG_ACTIVITY_NEW_TASK 在一个新的task 中启动插件

至此,这个假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
  •  根据原始intent 解析出ComponentName ActivityInfo
  •  设置原始intent class loader ,查看preLoadApk() 方法可知,创建了一个PluginClassLoader 。这个PluginClassLoader 是继承自DexClassLoader 的,从注释上可以了解到,是为了解决奇酷手机修改了support V4 库的导致的兼容问题。这个PluginClassLoader 重载了loadClass() 方法,打破了“双亲委托模型”,想来也是迫不得已。后面准备单独写一篇关于class loader 的文件进行阐述。
  •  把这个原始intent 以及解析出来的ActivityInfo 重新写回Message 中,这样后续的处理中就会用这个原始的intent 了,这就叫“偷梁换柱”

关于activity的动态加载就分析完了,下面分析service的动态注册。

二、Service动态注册

Service的动态注册和Activity类似,也有“穿马甲”和“脱马甲”这两个过程,具体可以参见IActivityManagerHookHandle()hookstartService()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);
        ... ...
}

继续看ServcesManageronStart()方法:

    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()方法,这个方法是关键所在。 要搞清楚这个方法在干什么,首先看一下ActivityThreadhandleCreateService()方法:

private void handleCreateService(CreateServiceData data) {
    ... ...
    mServices.put(data.token, service);
    ... ...
}

这个方法有一个CreateServiceData类型的参数,用来存储创建service需要的信息,在service创建完毕后,会把它放进一个叫做mServicesmap以便后续访问。搞清楚了这些,下面我们来看看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 是一个内部类因此需要反射。
  •  调用handleCreateService() 方法创建service 对象
  •  通过反射获取到ActivityThread mServices 成员,把之前的假token 对应的条目删掉!也就是说,从ActivityThread 眼中看,它根本不包含任何service
  •  那总要有人管这些service 吧?把这些service 都加到mTokenServices 这个map 中,让ServcesManager 进行托管

看到这里ActivityThread估计要掩面而泣了,你们怎么能这样!最后用一幅图来直观地描述上面这个过程:

DroidPlugin代码分析(三) 占坑、动态注册_第3张图片

三、ContentProvider动态注册

ContentProvider的动态注册比较简单了,hookContentProvider的一系列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);
        }

代码很简单,把Uriauthority替换成stub providerauthority就行了,这样访问的就是stub provider指向的路径了。

四、BroadcastReceiver动态注册

BroadcastReceiver要解决的问题是如何处理静态receiver,用官方文档的说法叫做“静态广播非静态”。这也很容易理解,宿主根本不知道插件程序会注册哪些receiver,显然AndroidManifest.xml也不会有这些receiver,那就只能通过动态注册receiver来解决了。

具体代码是在PluginInstrumentationcallApplicationOnCreate()方法中:

public void callApplicationOnCreate(Application app) {
    ... ...
    PluginProcessManager.registerStaticReceiver(app, app.getApplicationInfo(), app.getClassLoader());
    ... ...
}

这里调用了PluginProcessManagerregisterStaticReceiver()方法来动态注册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是怎么匹配上的呢?要搞清楚这一点,首先要了解下这个匹配是在哪里做的。实际上,在ActivityThreadperformLaunchActivity()方法里,如果没有显式指定类名,会先调用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);
        }
        ... ...
}

这个方法会调用最终会调用到IPackageManagerresolveIntent()方法,别忘了,IPackageManager也是被我们hook住的!

IPackageManager会调用PluginManagerresolveIntent(),再往前跟踪会进入IntentMatcherresolveIntet()

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);
                            }
        ... ...
    }

至此,四大组件的动态注册过程分析完毕。下一篇分析进程管理相关的部分。

你可能感兴趣的:(插件化)