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

作者: susion哒哒 | 来源:发表于2018-11-13 20:33 被阅读9次

    本文出自: https://github.com/SusionSuc/AdvancedAndroid

    在继续看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进阶计划 : https://github.com/SusionSuc/AdvancedAndroid

    相关文章

      网友评论

        本文标题:插件化-插件Service的运行管理

        本文链接:https://www.haomeiwen.com/subject/ngqrfqtx.html