android插件化(BroadCast、Service篇)

来源:互联网 发布:类似wpe的软件 编辑:程序博客网 时间:2024/06/14 01:46

android对于静态广播和动态广播的处理逻辑基本相同,只是注册方式有区别,静态广播要在AndroidManifest中注册,注册的信息存储在PMS中,动态广播只需要在程序中执行registerReceiver,注册的信息存储在AMS中,但是,广播是不可以用Stub(替身)来解决的,主要是BroadcastReceiver的概念,AndroidManifest中声明的Stub是有限的,我们不可能穷举出所有的IntentFilter,但是因为广播可以动态注册,我们可以把插件中的广播动态注册到主App中,其实,我们对于Activity的处理方式也有这个问题;如果你尝试用IntentFilter的方式启动Activity,这并不能成功,这算得上是DroidPlugin的缺陷之一

ContextImpl.registerReceiver --> ContextImpl.registerReceiverInternal(在该函数中,有一个IIntentReceiver对象,相当于IApplicationThread)--> ActivityManagerNative.getDefault().registerReceiver --> ActivityManagerService.registerReceiver(BroadcastReceiver以BroadcastFilter的形式存储在AMS的mReceiverResolver变量中)

ContextImpl.sendBroadcast --> ActivityManagerService.broadcastIntent --> ActivityManagerService.broadcastIntentLocked(在broadcastIntentLocked中,广播的发送和接收是融为一体的,某个广播被发送之后,AMS会找出所有注册过的BroadcastReceiver中与这个广播匹配的接收者,然后将这个广播分发给相应的接收者处理,其中,receivers存储了静态注册的BroadcastReceiver列表,mReceiverResolver存储了动态注册的BroadcastReceiver列表,接着我们创建了一个BroadcastRecord代表此次发送的广播,然后把它丢进一个队列,最后通过scheduleBroadcastsLocked通知队列对广播进行处理,在BroadcastQueue中通过Handler调度了对于广播处理的消息,调度过程由processNextBroadcast方法完成,而这个方法通过performReceiveLocked最终调用了IIntentReceiver的performReceive方法)

详细原理参见:http://weishu.me/2016/04/12/understand-plugin-framework-receiver/

ContextImpl.bindService(IServiceConnection与IApplicationThread以及IIntentReceiver相同,都是ActivityThread给AMS提供的用来与之进行通信的Binder对象)--> ActivityManager.getDefault().bindService --> ActivityManagerService.bindService(首先通过retrieveServiceLocked方法获取到了intent匹配到的需要Binder到的Service组件res,然后把ActivityThread传递过来的IServiceConnection使用ConnectionRecord进行了包装,方便接下来使用,最后如果启动的FLAG为BIND_AUTO_CREATE,那么调用bringUpServiceLocked开始创建Service)--> ActivityManagerService.bringUpServiceLocked(如果Service所在的进程已经启动,那么直接调用realStartServiceLocked方法来「真正」启动Service组件,如果Service所在的进程还没有启动,那么先在AMS中记下这个要启动的Service组件,然后通过startProcessLocked启动新的进程),接着会进行多次进程间通讯,自行分析

Activity组件的生命周期受用户交互影响,而Service组件不受交互影响,所以可以完全由代码模拟生命周期,而且Service没有任务栈的概念,所以可以有无数个实例,而且对于Service来说,多次调用startService并不会创建多个实例,因此一个StubService只能对应一个真正的Service

完全手动接管Service:我们必须在startService,stopService等方法被调用的时候拿到控制权,才能手动去控制Service的生命周期,方法很简单——Hook ActivityManagerNative即可,在Activity的插件化方案中我们就通过这种方式接管了startActivity调用,相信读者并不陌生,我们Hook掉ActivityManagerNative之后,可以拦截对于startService以及stopService等方法的调用;拦截之后,我们可以直接对插件Service进行操作:拦截到startService之后,如果Service还没有创建就直接创建Service对象(可能需要加载插件),然后调用这个Service的onCreate,onStartCommond方法;如果Service已经创建,获取到原来创建的Service对象并执行其onStartCommand方法。拦截到stopService之后,获取到对应的Service对象,直接调用这个Service的onDestroy方法。这种方案很简直,但是,这么干是不行的,首先,Service存在的意义在于它作为一个后台任务,拥有相对较高运行时优先级;除非在内存及其不足威胁到前台Activity的时候,这个组件才会被系统杀死。上述这种实现完全把Service当作一个普通的Java对象使用了,因此并没有完全实现Service所具备的能力。其次,Activity以及Service等组件是可以指定进程的,而让Service运行在某个特定进程的情况非常常见——所谓的远程Service;用上述这种办法压根儿没有办法让某个Service对象运行在一个别的进程。Android系统给开发者控制进程的机会太少了,要么在AndroidManifest.xml中通过process属性指定,要么借助Java的Runtime类或者native的fork;这几种方式都无法让我们以一种简单的方式配合上述方案达到目的

代理分发技术:既然我们希望插件的Service具有一定的运行时优先级,那么一个货真价实的Service组件是必不可少的——只有这种被系统认可的真正的Service组件才具有所谓的运行时优先级,因此,我们可以注册一个真正的Service组件ProxyService,让这个Service承载一个真正的Service组件所具备的能力(进程优先级等);当启动插件的服务比如PluginService的时候,我们统一启动这个ProxyService,当这个ProxyService运行起来之后,再在它的onStartCommand等方法里面进行分发,执行PluginService的onStartCommond等对应的方法;代理分发技术也可以完美解决插件Service可以运行在不同的进程的问题——我们可以在AndroidManifest.xml中注册多个ProxyService,指定它们的process属性,让它们运行在不同的进程;当启动的插件Service希望运行在一个新的进程时,我们可以选择某一个合适的ProxyService进行分发。也许有童鞋会说,那得注册多少个ProxyService才能满足需求啊?理论上确实存在这问题,但事实上,一个App使用超过10个进程的几乎没有;因此这种方案是可行的

if ("startService".equals(method.getName())) {    Pair<Integer, Intent> integerIntentPair = foundFirstIntentOfArgs(args);    Intent newIntent = new Intent();    ComponentName componentName = new ComponentName(UPFApplication.getContext().getPackageName(), ProxyService.class.getName());    newIntent.setComponent(componentName);    newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, integerIntentPair.second);    args[integerIntentPair.first] = newIntent;    return method.invoke(mBase, args);}if ("stopService".equals(method.getName())) {    Intent raw = foundFirstIntentOfArgs(args).second;    String pkgName = UPFApplication.getContext().getPackageName();    String destComponent = raw.getComponent().getPackageName();    if (!TextUtils.equals(pkgName, destComponent)) {        Log.v(TAG, "hook method stopService success " + pkgName + " " + destComponent);        return ServiceManager.getInstance().stopService(raw);    }}
public int stopService(Intent targetIntent) {    ServiceInfo serviceInfo = selectPluginService(targetIntent);    if (serviceInfo == null) {        Log.w(TAG, "can not found service: " + targetIntent.getComponent());        return 0;    }    Service service = mServiceMap.get(serviceInfo.name);    if (service == null) {        Log.w(TAG, "can not running, are you stopped it multi-times?");        return 0;    }    service.onDestroy();    mServiceMap.remove(serviceInfo.name);    if (mServiceMap.isEmpty()) {        Log.d(TAG, "service all stopped, stop proxy");        Context appContext = UPFApplication.getContext();        appContext.stopService(new Intent().setComponent(new ComponentName(appContext.getPackageName(), ProxyService.class.getName())));    }    return 1;}
@Overridepublic void onStart(Intent intent, int startId) {    Log.d(TAG, "onStart() called with " + "intent = [" + intent + "], startId = [" + startId + "]");    // 分发Service    ServiceManager.getInstance().onStart(intent, startId);    super.onStart(intent, startId);}
void onStart(Intent proxyIntent, int startId) {    Intent targetIntent = proxyIntent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);    ServiceInfo serviceInfo = selectPluginService(targetIntent);    if (serviceInfo == null) {        Log.w(TAG, "can not found service : " + targetIntent.getComponent());        return;    }    try {        if (!mServiceMap.containsKey(serviceInfo.name)) {            // service还不存在, 先创建            proxyCreateService(serviceInfo);        }        Service service = mServiceMap.get(serviceInfo.name);        service.onStart(targetIntent, startId);    } catch (Exception e) {        e.printStackTrace();    }}

Service作为Android系统的组件,最重要的特点是它具有Context,所以,直接通过反射创建出来的PluginService就是一个壳子——没有Context的Service能干什么?因此我们需要给将要创建的Service类创建出Conetxt,Context都是系统给我们创建好的。既然这样,我们可以参照一下系统是如何创建Service对象的;在上文的Service源码分析中,在ActivityThread类的handleCreateService完成了这个步骤,摘要如下:

    java.lang.ClassLoader cl = packageInfo.getClassLoader();
    service = (Service) cl.loadClass(data.info.name).newInstance();

    ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
    context.setOuterContext(service);
    Application app = packageInfo.makeApplication(false, mInstrumentation);
    service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault());
    service.onCreate();

可以看到,系统也是通过反射创建出了对应的Service对象,然后也创建了对应的Context,并给Service注入了活力。如果我们模拟系统创建Context这个过程,势必需要进行一系列反射调用,那么我们何不直接反射handleCreateService方法呢?当然,handleCreateService这个方法并没有把创建出来的Service对象作为返回值返回,而是存放在ActivityThread的成员变量 mService 之中,这个是小case,我们反射取出来就行

private void proxyCreateService(ServiceInfo serviceInfo) throws Exception {    IBinder token = new Binder();    // 创建createServiceData    Class<?> createServiceDataClass = Class.forName("android.app.ActivityThread$CreateServiceData");    Constructor<?> constructor  = createServiceDataClass.getDeclaredConstructor();    constructor.setAccessible(true);    Object createServiceData = constructor.newInstance();    // 写入token字段, ActivityThread的handleCreateService用这个作为key存储Service    Field tokenField = createServiceDataClass.getDeclaredField("token");    tokenField.setAccessible(true);    tokenField.set(createServiceData, token);    // 写入info字段, 这个修改是为了loadClass的时候, LoadedApk会是主程序的ClassLoader, 我们选择Hook BaseDexClassLoader的方式加载插件    serviceInfo.applicationInfo.packageName = UPFApplication.getContext().getPackageName();    Field infoField = createServiceDataClass.getDeclaredField("info");    infoField.setAccessible(true);    infoField.set(createServiceData, serviceInfo);    // 写入compatInfo字段,获取默认的compatibility配置    Class<?> compatibilityClass = Class.forName("android.content.res.CompatibilityInfo");    Field defaultCompatibilityField = compatibilityClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");    Object defaultCompatibility = defaultCompatibilityField.get(null);    Field compatInfoField = createServiceDataClass.getDeclaredField("compatInfo");    compatInfoField.setAccessible(true);    compatInfoField.set(createServiceData, defaultCompatibility);    Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");    Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");    Object currentActivityThread = currentActivityThreadMethod.invoke(null);    Method handleCreateServiceMethod = activityThreadClass.getDeclaredMethod("handleCreateService", createServiceDataClass);    handleCreateServiceMethod.setAccessible(true);    handleCreateServiceMethod.invoke(currentActivityThread, createServiceData);    // handleCreateService创建出来的Service对象并没有返回, 而是存储在ActivityThread的mServices字段里面, 这里我们手动把它取出来    Field mServicesField = activityThreadClass.getDeclaredField("mServices");    mServicesField.setAccessible(true);    Map mServices = (Map) mServicesField.get(currentActivityThread);    Service service = (Service) mServices.get(token);    // 获取到之后, 移除这个service, 我们只是借花献佛    mServices.remove(token);    // 将此Service存储起来    mServiceMap.put(serviceInfo.name, service);}

详细原理参见:http://weishu.me/2016/05/11/understand-plugin-framework-service/


0 0
原创粉丝点击