在继续看 VirtualApk 中如何启动一个插件的 Service 之前, 先简单的看一下 Android 如何启动一个 Service, 主要是有个印象.
下面的源码参考自 Android8.0. 贴的源码只是包含一些关键点.
Service 启动的大体流程
我们从 ContextImpl.startService() 开始看. 为什么从这里开始看呢? 如果你看过前面的文章插件 Activity 的启动, 你应该知道在 Activity 创建时, 其 Context 的实例就是 ContextImpl, 因此我们看一下它的 startService().
- public ComponentName startService(Intent service) {
- .....
- return startServiceCommon(service, false, mUser);
- }
- private ComponentName startServiceCommon(Intent service, boolean requireForeground,UserHandle user) {
- ..
- ActivityManager.getService().startService(mMainThread.getApplicationThread(), service, ....);
- ......
- }
即直接向 ActivityManager 请求启动 Service. 这是一次通过 Binder 跨进程调用调用. 最后调用到 ActivityManagerService.startService(), 然后它又调用了 ActiveServices.startServiceLocked(), 来看一下关键步骤:
- ComponentName startServiceLocked(....){
- // 创建 ServiceRecord, 并缓存起来
- ServiceLookupResult res = retrieveServiceLocked(.....); // 这里会检测 Service 是否在 manifest 文件中注册
- ServiceRecord r = res.record;
.... 一系列权限相关检查
- // 继续启动流程
- ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
- }
在后续流程中, 真正启动 Service 的方法是 ActiveServices.bringUpServiceLocked():
- private String bringUpServiceLocked(ServiceRecord r, .....) {
- ProcessRecord App;
- ....
- // 是不是要单独开了一个进程来启动这个 Service
- final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
- ...
- if (!isolated) {
- App = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
- .....
- realStartServiceLocked(r, App, execInFg); // 真正启动 Service
- return null;
- }
- // 启动一个对应的进程
- App = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
- hostingType, r.name, false, isolated, false)
- .....
- // 等会启动
- if (!mPendingServices.contains(r)) {
- mPendingServices.add(r);
- }
- }
ActiveServices.realStartServiceLocked() 这个方法做的事情就很熟悉了:
- App.thread.scheduleCreateService(r, r.serviceInfo,....); // 这个方法会导致 service.onCreate() 调用
- ....
- sendServiceArgsLocked(r, execInFg, true); // 这个方法会导致 service.onStartCommand() 调用
即切换到了我们应用的进程, 接下来就是在 ActivityThread 中启动 Service 了:
- //ActivityThread.java
- private void handleCreateService(CreateServiceData data) {
- ......
- 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); // 不为 null, 直接返回
- service.attach(context, this, data.info.name, data.token, App,
- ActivityManager.getService());
- service.onCreate();
- mServices.put(data.token, service);
- ......
- }
ok 分析到这里, 我们大致知道了 Service 是如何启动的. 至于 Service 的绑定这里先不做分析. 那分析这一遍源码有什么意义呢? 当然是为了方便接下来的分析插件化 Service 的启动.
VirtualApk - 插件化 Service 的启动
服务端对于 Service 的启动有校验机制吗 ?
前面在分析源码时注释已经标注了. ActiveServices.retrieveServiceLocked() 这个方法在构造 ServiceRecord 的时候会做这个校验. 即必须在 manifest 文件中注册. 所以启动一个插件的 Service, 我们也需要类似插件 Activity 来启动一个占坑的 Service.
动态代理 ActivityManagerService
所以 VirtualApk 对于插件 Service 启动的第一步就是需要绕过系统校验, 原理类似于插件 Activity 的启动, 只不过由于 Service 的启动和 Instrumentation 关系不大, 它 hook 的是 ActivityManagerService.
- protected void hookSystemServices() {
- 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();
- }
- // 通过 java 动态代理 hook
- IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class },
- createActivityManagerProxy(defaultSingleton.get()));
- Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy); // hook for android8.0 以下版本
- }
hook 之后目的当然是为了绕过系统对插件 Service 的校验. 类似于 Activity 的启动, 在插件 Service 启动时会把占坑 Service 给 ActivityManagerService 校验. 在 VirtualApk 中也定义了两个占坑 Service:
- <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"/>
现在我们看 VirtualApk 是如何在 Service 启动时替换插件 Service 为这两个占坑的 Service:
- public class ActivityManagerProxy implements InvocationHandler { // 前面已经标注, 对 ActivityManagerService 的 hook 是通过 java 的动态代理
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if ("startService".equals(method.getName())) {
- return startService(proxy, method, args);
- }
- if ("stopService".equals(method.getName())) {
- return stopService(proxy, method, args);
- }
- }
即如果调用的是 ActivityManagerService.startService, 那就做一些处理:
- protected Object startService(Object proxy, Method method, Object[] args) throws Throwable {
- Intent target = (Intent) args[1];
... 如果这个 intent 不是插件的 Service 直接返回
- Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, RemoteService.EXTRA_COMMAND_START_SERVICE);
- return mPluginManager.getHostContext().startService(wrapperIntent);
- }
- protected Object stopService(Object proxy, Method method, Object[] args) throws Throwable {
.... 流程同 startService
- startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_STOP_SERVICE);
- }
- 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();
- // 插件启动的 service 是 本地的还是远程的
- Class<? extends Service> delegate = PluginUtil.isLocalService(serviceInfo) ? LocalService.class : RemoteService.class;
- Intent intent = new Intent();
- intent.setClass(mPluginManager.getHostContext(), delegate);
- intent.putExtra(RemoteService.EXTRA_TARGET, target);
- intent.putExtra(RemoteService.EXTRA_COMMAND, command); // 注意这个 command. 这里是: EXTRA_COMMAND_START_SERVICE
- intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);
- .....
- return intent;
- }
即如果启动的是插件 Service, 就把要启动的插件 Serivice 的信息保存在 intent 中, 然后启动占坑 Service. 并且对于不同的操作在 intent 中标记为一个不同的 command, 比如上面的 EXTRA_COMMAND_START_SERVICE 和 EXTRA_COMMAND_STOP_SERVICE. 其余还有 EXTRA_COMMAND_BIND_SERVICE 和 EXTRA_COMMAND_UNBIND_SERVICE.
接下来还要像启动插件 Activity 那样再把 Activity 换回来吗? VirtualApk 并没有这么做, 它就是直接启动了 LocalService or RemoteService, 只不过在这两个 Service 中对不同的 command 做了不同的处理, 比如 EXTRA_COMMAND_START_SERVICE:
- public class LocalService extends Service {
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- int command = intent.getIntExtra(EXTRA_COMMAND, 0);
- switch (command) {
- case EXTRA_COMMAND_START_SERVICE: {
- ActivityThread mainThread = ActivityThread.currentActivityThread();
- IApplicationThread appThread = mainThread.getApplicationThread();
- Service service;
- if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
- service = this.mPluginManager.getComponentsHandler().getService(component); // 如果这个 Service 已经运行
- } else {
- // 实例化插件 Service
- service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();
- 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);
- IActivityManager am = mPluginManager.getActivityManager();
- attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, App, am);
- service.onCreate();
- // 缓存 Service, 避免重复实例化
- this.mPluginManager.getComponentsHandler().rememberService(component, service);
- }
- // 调用插件 Service 的 onStartCommand
- service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());
- break;
- }
- }
- }
即它在 LocalService 的 onStartCommand 中, 模拟了 Service 的生命周期方法的调用, 具体生命周期方法调用步骤可以回顾前面看过的 Service 源码. 对于其他 stopService,bindService 也是由 LocalService 在 onStartCommand 中来模拟的. 对于 RemoteService 这里就不列了, 处理其实和 LocalService 是一样的, 只不过 RemoteService 是跑在另一个进程中的.
所以这里来总结一下 VirtualApk 中的插件 Serivice 的真正运行情况吧, 这里以主进程中的 Service 为例:
插件 Service 都是运行在 LocalService 中的. 其实这无所谓, 都是跑在主进程中的.
插件 Service 的相关生命周期方法都是 LocalService 来模拟调用的 (在主线程中).
即真正在后台一直跑的 Service 是 LocalService.
所以从源代码来看, VirtualApk 对于插件 Service 通过依赖于系统 Service, 创建了一套自己的插件 Service 运行模型. 有没有什么缺点呢? 当然有
自己模拟的 Service 生命周期方法毕竟不是系统, 随着系统版本的更新需要不断维护
没有真正支持开多进程 Service. 并且远程 Service 的名称的修改, 需要修改源代码.
来源: https://juejin.im/entry/5bebb503f265da6179746173