Android插件化原理(三):Service的插件化

  上一节Activity的插件化中我们解决了四大组件中最重要的组件Activity的插件化问题。四大组件中,Service同样是使用相对频繁的组件,所以Service的插件化也是比较重要的。本节我们就跟着VirtualApk源码看一下Service插件化的实现。

Service插件化思路

  在Activity的插件化中我们看到对于Activity的插件化,VirtualApk采用了Hook及在宿主中注册占位Activity的方式解决,那么Service是否可用这种方式解决呢?是可以的,我们同样可以在宿主中注册一些占位Service,再通过Hook相关类,当启动一个Service时,在向AMS请求之前将目标Service替换成占位Service,在AMS回调以后再将目标Service替换回插件Service。但是Activity与Service启动有以下几个不同之处:

  1. 启动一个标准启动模式Activity是可以重复创建实例的,因此标准启动模式的占位Activity只需一个即可,但Service如果已经启动,是不会重复创建的,因此目标Service和占位Service只能是一对一的关系。
  2. Activity随着用户操作有复杂生命周期处理,而Service的生命周期比较简单,可以手动进行管理。

  所以占位Service可以解决Service插件化问题,但是需要在宿主中注册多个占位Service保证有足够的Service可用。VirtualApk采用了另一种思路,基于代理分发的方式实现,在启动插件Service时,会启动一个在宿主中注册过的代理Service,这里是真正的启动而非绕过AMS检查,因此会调用代理Service的onStartCommand(),在该方法中会创建插件Service实例并手动调用它的相关方法完成Service请求的分发。可以看到这种方式下就无需在宿主中注册大量的占位Service就能实现Service的插件化了。下面我们跟着具体源码看下VirtualApk是如何实现的,Service包括了启动和绑定两个过程,我们分别看下这两个过程。

Service的启动过程

  我们先看一下Service启动流程的时序图:


Service启动流程

  当启动一个Service时,会调用ContextWrapper的startService(),Activity、Service以及Application都继承了ContextWrapper,其内部会调用ContextImpl的startService(),最后会调用到startServiceCommon(),其内部又会调用IActivityManager的startService()向AMS发起请求;AMS收到请求并处理完成后,会调用ApplicationThread的scheduleCreateService()回调到应用进程,接着通过主线程Handler发送一条CREATE_SERVICE类型的消息,Handler处理这条消息时会调用ActivityThread的handleCreateService(),在它内部会创建Service实例,并调用attach()完成Service初始化,最后调用Service的onCreate()完成Service启动。

  根据Service的启动流程以及Service插件化实现的思路,我们需要在插件启动Service时先启动代理Service,因此我们同样可以采用Hook的方式,在向AMS发送请求之前将目标Service替换成代理Service,这样AMS收到的就是启动代理Service的请求了,所以我们看看VirtualApk是如何实现的。
  在Activity的插件化中我们说到了VirtualApk初始化的时候会hook多个对象,其中会调用hookSystemServices()hook AMS在应用进程的代理,我们看下这个方法的实现:

protected void hookSystemServices() {
    try {
        Singleton<IActivityManager> defaultSingleton;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            defaultSingleton = Reflector.on(ActivityManager.class).field("IActivityManagerSingleton").get();
        } else {
            defaultSingleton = Reflector.on(ActivityManagerNative.class).field("gDefault").get();
        }
        IActivityManager origin = defaultSingleton.get();
        IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class },
            createActivityManagerProxy(origin));
        Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy);
        if (defaultSingleton.get() == activityManagerProxy) {
            this.mActivityManager = activityManagerProxy;
            Log.d(TAG, "hookSystemServices succeed : " + mActivityManager);
        }
    } catch (Exception e) {
        Log.w(TAG, e);
    }
}

  因为AMS在应用进程的代理在8.0版本前后的形式有所不同,所以首先根据版本调用不同的获取方式,最终都会得到一个实现了IActivityManager的对象,这是一个单例对象。接着创建了IActivityManager接口的一个动态代理对象,其中调用createActivityManagerProxy()创建了代理的处理对象,最后通过反射将代理对象赋值给单例的容器中,createActivityManagerProxy()内部就直接new了一个ActivityManagerProxy对象,ActivityManagerProxy实现了InvocationHandler接口,因此我们看看ActivityManagerProxy的invoke()做了什么处理:

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 ("bindService".equals(method.getName())) {
        try {
            return bindService(proxy, method, args);
        } catch (Throwable e) {
            Log.w(TAG, e);
        }
    } else if ("unbindService".equals(method.getName())) {
        try {
            return unbindService(proxy, method, args);
        } catch (Throwable e) {
            Log.w(TAG, e);
        }
    }
    ...
}

  invoke()方法根据调用的方法名做不同的处理,上面看到了启动、停止、绑定、解绑服务的处理,稍后我们再看绑定的处理,这里我们先看下startService()

protected Object startService(Object proxy, Method method, Object[] args) throws Throwable {
    IApplicationThread appThread = (IApplicationThread) args[0];
    Intent target = (Intent) args[1];
    ResolveInfo resolveInfo = mPluginManager.resolveService(target, 0);
    if (null == resolveInfo || null == resolveInfo.serviceInfo) {
        return method.invoke(this.mActivityManager, args);
    }
    return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);
}

  方法首先从参数中取出Intent,调用PluginManager.resolveService()根据Intent中的目标Service信息看是否匹配一个插件Service,如果不是则不做处理,否则则调用startDelegateServiceForTarget(),我们看下这个方法的实现:

protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
    Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
    return mPluginManager.getHostContext().startService(wrapperIntent);
}

  可以看到方法调用wrapperTargetIntent()将Intent进行转化,接着通过转化后的Intent调用宿主的Context的startService()启动Service,因此我们看下wrapperTargetIntent()是如何处理Intent的,方法如下所示:

protected Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
    target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
    String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();
    boolean local = PluginUtil.isLocalService(serviceInfo);
    Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class;  // 1
    Intent intent = new Intent();
    intent.setClass(mPluginManager.getHostContext(), delegate);  // 2
    intent.putExtra(RemoteService.EXTRA_TARGET, target);  // 3
    intent.putExtra(RemoteService.EXTRA_COMMAND, command);  // 4
    intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);
    if (extras != null) {
        intent.putExtras(extras);
    }
    return intent;
}

  在注释1处会根据插件Service运行在的进程名判断该服务是运行在本地还是远程,如果是本地就会用LocalService作为代理Service,否则会使用RemoteService,RemoteService继承自LocalService,但它运行在子进程中,我们可以看一下LocalService以及RemoteService的声明:

<service android:exported="false" android:name="com.didi.virtualapk.delegate.LocalService" />

<service android:exported="false" android:name="com.didi.virtualapk.delegate.RemoteService" android:process=":daemon">
    <intent-filter>
        <action android:name="${applicationId}.intent.ACTION_DAEMON_SERVICE" />
    </intent-filter>
</service>

  可以看到LocalService没有指定进程而RemoteService运行在:daemon进程中。wrapperTargetIntent()方法在注释2处会创建一个新的Intent,并且将目标Service设置成刚选定的代理Service;在注释3会把原本的包含插件Service信息的Intent作为参数添加到新创建的Intent中;在注释4处会向Intent中添加一个参数标识当前执行的操作,用来和绑定服务进行区分,这个参数我们后面还会看到。这样就会启动我们选定的一个代理Service,因为RemoteService继承自LocalService,处理逻辑都在LocalService中,因此我们这里以LocalService为例。LocalService启动以后,它的onStartCommand()会被调用,且即使LocalService已经被创建,onStartCommand()也是会被调用的,所以不会因为LocalService已经启动而影响代理分发逻辑的执行,我们看下onStartCommand()是如何处理的,代码如下:

public int onStartCommand(Intent intent, int flags, int startId) {
    if (null == intent || !intent.hasExtra(EXTRA_TARGET) || !intent.hasExtra(EXTRA_COMMAND)) {
        return START_STICKY;
    }
    Intent target = intent.getParcelableExtra(EXTRA_TARGET);  // 1
    int command = intent.getIntExtra(EXTRA_COMMAND, 0);  // 2
    if (null == target || command <= 0) {
        return START_STICKY;
    }
    ComponentName component = target.getComponent();  // 3
    LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);
    if (plugin == null) {
        Log.w(TAG, "Error target: " + target.toURI());
        return START_STICKY;
    }
    target.setExtrasClassLoader(plugin.getClassLoader());
    switch (command) {  // 4
        ...
    }
    return START_STICKY;
}

  可以看到方法在注释1和注释2处分别从Intent中取出我们前面作为参数添加到Intent中的目标Intent以及操作标识,在注释3处从目标Intent中取出实际要启动的Service的包名类名,在注释4处根据操作标识做不同处理,这里我们看一下对启动服务的处理,处理逻辑如下所示:

ActivityThread mainThread = ActivityThread.currentActivityThread();
IApplicationThread appThread = mainThread.getApplicationThread();
Service service;
if (mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
    service = mPluginManager.getComponentsHandler().getService(component);  // 1
} else {
    try {
        service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();  // 2
        Application app = plugin.getApplication();
        IBinder token = appThread.asBinder();
        Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class);  // 3
        IActivityManager am = mPluginManager.getActivityManager();
        attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);  // 4
        service.onCreate();  // 5
        mPluginManager.getComponentsHandler().rememberService(component, service);
    } catch (Throwable t) {
        return START_STICKY;
    }
}
service.onStartCommand(target, 0, mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());  // 6

  上面代码在注释1处先判断要启动的插件Service是否已经启动,如果是则从缓存取出,不再重复创建实例,直接在注释6调用它的onStartCommand(),否则在注释2处通过插件Service所在的插件的DexClassLoader加载Service类并创建实例,在注释3处通过反射获得Service类的attach(),并在注释4处对刚刚创建的Service实例调用attach()进行初始化,在注释5处调用Service的onCreate(),最后在注释6处调用Service的onStartCommand(),这样就完成了插件Service的启动。可以看出插件Service的生命周期是由代理Service手动调用的,这就是代理分发的思想,这种思路我们在下面的ContentProvider插件化中还会再次看到。

  上面就是Service启动的插件化实现,接下来看一下Service绑定的插件化实现,绑定和启动的思路都是一致的,因此理解了启动的原理后,绑定也就很简单了。

Service的绑定过程

  我们还是先看一下Service绑定流程的时序图:


Service绑定流程

  绑定Service的流程和启动Service基本一样,只是调用的方法从create变成了bind,大家看时序图基本都能明白,我就不做解释了。但需要注意的一点是,绑定Service的时候,如果Service还没有启动,AMS会先回调应用进程触发Service的启动。

  绑定服务时会调用IActivityManager的bindService()与AMS通信,我们前面已经知道VirtualApk hook了IActivityManager,因此调用bindService()依然会调用到ActivityManagerProxy的invoke(),现在我们看下invoke()对bindService的处理,代码如下:

protected Object bindService(Object proxy, Method method, Object[] args) throws Throwable {
    Intent target = (Intent) args[2];
    ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
    if (null == resolveInfo || null == resolveInfo.serviceInfo) {
        return method.invoke(this.mActivityManager, args);
    }
    Bundle bundle = new Bundle();
    PluginUtil.putBinder(bundle, "sc", (IBinder) args[4]);  // 1
    startDelegateServiceForTarget(target, resolveInfo.serviceInfo, bundle, RemoteService.EXTRA_COMMAND_BIND_SERVICE);  // 2
    mPluginManager.getComponentsHandler().remberIServiceConnection((IBinder) args[4], target);
    return 1;
}

  可以看到对绑定的处理和启动基本一致,不一样的地方在注释1处从参数中取出了一个ServiceDispatcher对象,它内部封装了我们调用bindService()传递的ServiceConnection对象;并在注释2处调用startDelegateServiceForTarget()启动代理Service时作为参数传入,同时传递了表示绑定Service的标识符,因此在代理Service启动后就会执行绑定Service的逻辑。注意注释2我们执行startDelegateServiceForTarget()启动代理服务,虽然我们这里是绑定一个插件Service,但我们依旧是启动代理Service而非绑定,因为无论是启动还是绑定Service,代理Service的分发逻辑都在onStartCommand()中,所以我们这里还是需要启动代理Service。startDelegateServiceForTarget()的逻辑我们前面已经看过了,这里就不再赘述了。我们依旧是以LocalService为例,在LocalService启动以后,还是会调用到LocalService的onStartCommand(),这个方法我们前面也看到过了,是从Intent中取出目标Intent以及标识符command,并根据command决定执行的逻辑,这里我们会执行到绑定Service的逻辑,代码如下所示:

ActivityThread mainThread = ActivityThread.currentActivityThread();
IApplicationThread appThread = mainThread.getApplicationThread();
Service service = null;
if (mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
    service = mPluginManager.getComponentsHandler().getService(component);  // 1
} else {
    try {
        service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();  // 2
        Application app = plugin.getApplication();
        IBinder token = appThread.asBinder();
        Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class);  // 3
        IActivityManager am = mPluginManager.getActivityManager();
        attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);  // 4
        service.onCreate();  // 5
        mPluginManager.getComponentsHandler().rememberService(component, service);
    } catch (Throwable t) {
        Log.w(TAG, t);
    }
}
try {
    IBinder binder = service.onBind(target);  // 6
    IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), "sc");  // 7
    IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection);
    if (Build.VERSION.SDK_INT >= 26) {
        iServiceConnection.connected(component, binder, false);  // 8
    } else {
        Reflector.QuietReflector.with(iServiceConnection).method("connected", ComponentName.class, IBinder.class).call(component, binder);
    }
} catch (Exception e) {
    Log.w(TAG, e);
}

  在注释1处判断是否已经创建过Service实例,如果是则从缓存中取出,否则在注释2处通过插件Service所在的插件的DexClassLoader加载Service类并创建实例,注释3处反射获取到attach()方法并在注释4处调用进行Service初始化,在注释5处手动调用插件Service的onCreate(),到这里Service的创建及初始化就完成了。接着在注释6处手动调用插件Service的onBind()进行绑定请求的分发,onBind()会返回一个Binder对象,在注释7处取出我们之前作为作为参数添加到Intent的ServiceDispatcher对象,并将其转化成ServiceConnection类型,在注释8处将onBind()获取到的Binder对象作为参数调用ServiceConnection的connected(),这样我们调用bindService()时传递的ServiceConnection的监听就能收到回调,并且通过拿到的Binder对象与插件Service进行通信了。

  到这里Service的启动以及绑定的插件化原理都分析清楚了,可以看出Service插件化采用了与Activity插件化不一样的思路,采用了代理分发的思想,通过代理分发的思路可以保证宿主只需要注册少量的代理Service就可以完成全部插件Service的启动,我们在ContentProvider的插件化上还会再次看到这种思路。我们主要讲了启动和绑定的流程,还有停止和解绑流程,但是相信理解了前两个流程以后,剩下两个流程也不是问题了。下一节会介绍一下剩下两个组件—BroadcastReceiver和ContentProvider的插件化实现原理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345