插件化-插件Service的运行管理

569 阅读5分钟
原文链接: www.jianshu.com

本文出自: github.com/SusionSuc/A…

在继续看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启动时会把占坑ServiceActivityManagerService校验。在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_SERVICEEXTRA_COMMAND_STOP_SERVICE。其余还有EXTRA_COMMAND_BIND_SERVICEEXTRA_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;
        }
    }
}

即它在LocalServiceonStartCommand中,模拟了Service的生命周期方法的调用,具体生命周期方法调用步骤可以回顾前面看过的Service源码。对于其他stopServicebindService也是由LocalServiceonStartCommand中来模拟的。对于RemoteService这里就不列了,处理其实和LocalService是一样的,只不过RemoteService是跑在另一个进程中的。

所以这里来总结一下VirtualApk中的插件Serivice的真正运行情况吧,这里以主进程中的Service为例:

  1. 插件Service都是运行在LocalService中的。其实这无所谓,都是跑在主进程中的。
  2. 插件Service的相关生命周期方法都是LocalService来模拟调用的(在主线程中)。
  3. 即真正在后台一直跑的Service是LocalService

所以从源代码来看,VirtualApk对于插件Service通过依赖于系统Service,创建了一套自己的插件Service运行模型。有没有什么缺点呢?当然有

  1. 自己模拟的Service生命周期方法毕竟不是系统,随着系统版本的更新需要不断维护
  2. 没有真正支持开多进程Service。并且远程Service的名称的修改,需要修改源代码。

好,对于VirtualApk插件Service的管理源码的解读就看到这里。 欢迎关注我的Android进阶计划 : github.com/SusionSuc/A…