• 插件化框架解读之四大组件调用原理-Service(三)下篇


    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

    本文将继续通过Service调用原理来解读Replugin插件化技术

    一、开启插件内Service

    第1步
    在《Replugin插件化技术解读之框架初始化、插件安装与加载(二)》中我们知道插件在加载的时候会自动生成PluginContext来作为插件内上下文使用,而插件中startService其实调用的就是已经被重写的startService方法了。

    PluginContext.java

    public ComponentName startService(Intent service) {
            if (mContextInjector != null) {
                mContextInjector.startServiceBefore(service);
            }
     
            。。。
            try {
                return PluginServiceClient.startService(this, service, true);
            } catch (PluginClientHelper.ShouldCallSystem e) {
                // 若打开插件出错,则直接走系统逻辑
                return super.startService(service);
            } finally {
                if (mContextInjector != null) {
                    mContextInjector.startServiceAfter(service);
                }
            }
        }
    

    第2步
    PluginServiceClient.startService中会首先获取ComponentName对象,得到插件包名和对应Service Class类型对象,然后获得开启的Service所属的进程号,如UI进程或者常驻进程。接着会通过进程号获得对应的IPluginServiceService pss对象,进行pss.startService操作。

    public static ComponentName startService(Context context, Intent intent, boolean throwOnFail) {
     
            // 1. 从 Intent 中获取 ComponentName,包含想要开启的ServiceName以及插件包名
            ComponentName cn = getServiceComponentFromIntent(context, intent);
     
            // 2. 获取将要使用服务的进程ID,
            int process = getProcessByComponentName(cn);
            if (process == PROCESS_UNKNOWN) {
                // 有可能是不支持的插件,也有可能本意就是想调用Main工程的服务。则直接调用系统方法来做
                if (LOG) {
                    LogDebug.d(PLUGIN_TAG, "PSS.startService(): Call SystemAPI: in=" + intent);
                }
                if (throwOnFail) {
                    throw new PluginClientHelper.ShouldCallSystem();
                } else {
                    return context.startService(intent);
                }
            }
     
            // 既然确认是插件,则将之前获取的插件信息填入
            intent.setComponent(cn);
     
            //3. 获取对应进程ID的IPluginServiceServer对象,IBinder实现提是PluginServiceService.Stub
            IPluginServiceServer pss = sServerFetcher.fetchByProcess(process);
            if (pss == null) {
                if (LOGR) {
                    LogRelease.e(PLUGIN_TAG, "psc.ss: pss n");
                }
                return null;
            }
     
            // 开启服务
            try {
                return pss.startService(intent, sClientMessenger);
            } catch (Throwable e) {
                if (LOGR) {
                    LogRelease.e(PLUGIN_TAG, "psc.ss: pss e", e);
                }
            }
            return null;
        }
    

    第3步
    重点看下如果根据不同进程去生成相应的PSS对象,也就是方法sServerFetcher.fetchByProcess方法

    public IPluginServiceServer fetchByProcess(int process) {
            if (process == PluginServiceClient.PROCESS_UNKNOWN) {
                return null;
            }
            // 取之前的缓存
            IPluginServiceServer pss;
            synchronized (PSS_LOCKER) {
                pss = mServiceManagerByProcessMap.get(process);
                if (pss != null) {
                    if (LOG) {
                        LogDebug.d(PLUGIN_TAG, "PluginServiceClient.fsmbp(): Exists! p=" + process);
                    }
                    return pss;
                }
            }
     
            // 缓存没有?则去目标进程获取新的
            if (LOG) {
                LogDebug.d(PLUGIN_TAG, "PluginServiceClient.fsmbp(): Create a new one! p=" + process);
            }
            try {
                if (process == IPluginManager.PROCESS_PERSIST) {
                    //1. 如果是常驻进程,则直接获取得到Pss对象,PluginServiceServer.mStub
                    IPluginHost ph = PluginProcessMain.getPluginHost();
                    pss = ph.fetchServiceServer();
                } else {
                    //2. 如果是非常驻其他进程,则会通过 MP.startPluginProcess方法,利用动态编译自动生成的进程挂在宿主Manifest中的Provider
                    //去通过PluginProviderStub.proxyStartPluginProcess 利用insert方法去吊起provider对应的进程号,开启进程
                    //IPluginClient的实现体就是PluginProcessPer中,获得对象同样是PluginServiceServer.mStub
                    PluginBinderInfo pbi = new PluginBinderInfo(PluginBinderInfo.NONE_REQUEST);
                    IPluginClient pc = MP.startPluginProcess(null, process, pbi);
                    pss = pc.fetchServiceServer();
                }
     
                // 挂死亡周期,如果出问题了就置空重来,防止外界调用psm出现DeadObject问题
                pss.asBinder().linkToDeath(new PSSDeathMonitor(process, pss.asBinder()), 0);
            } catch (Throwable e) {
                if (LOGR) {
                    LogRelease.e(PLUGIN_TAG, "psc.fsm: e", e);
                }
            }
            if (pss != null) {
                synchronized (PSS_LOCKER) {
                    mServiceManagerByProcessMap.put(process, pss);
                }
            }
            return pss;
        }
    
    1. 显然,针对不同进程号,使用了一个ArrayMap来缓存对应pss对象,如果是常驻进程,也就是进程号
    process == IPluginManager.PROCESS_PERSIST
    

    会直接获得PluginServiceServer.mStub的IPluginServiceService对象来进行相关Service操作。

    1. 如果Service所处的是非常驻进程,这里会采用一个比较巧妙的方法去首先拉起该进程,跟踪下面方法:
    IPluginClient pc = MP.startPluginProcess(null, process, pbi);
    

    MP.java

    public static final IPluginClient startPluginProcess(String plugin, int process, PluginBinderInfo info) throws RemoteException {
            return PluginProcessMain.getPluginHost().startPluginProcess(plugin, process, info);
        }
    

    显然,会调用常驻进程服务的PmHostSvc.startPluginProcess方法去拉起对应进程,PmHostSvc常驻进程服务再整个Replugin确实起到了对各插件管理的作用,包括拉起插件Service进程。继续往下看,调用了PmBase.startPluginProcessLocked方法:

    final IPluginClient startPluginProcessLocked(String plugin, int process, PluginBinderInfo info) {
        ......
        PluginProcessMain.schedulePluginProcessLoop(PluginProcessMain.CHECK_STAGE1_DELAY);
     
        // 尝试从缓存中查找
        IPluginClient client = PluginProcessMain.probePluginClient(plugin, process, info);
        ......
        int index = IPluginManager.PROCESS_AUTO;
        try {
            index = PluginProcessMain.allocProcess(plugin, process); 
        } catch (Throwable e) {
        }
        // 启动
        boolean rc = PluginProviderStub.proxyStartPluginProcess(mContext, index);
        ......
        // 再次获取
        client = PluginProcessMain.probePluginClient(plugin, process, info);
        ......
        return client;
    }
     
    PluginProviderStub.proxyStartPluginProcess方法去会吊起对应进程,代码如下:
    
    PluginProviderStub.java
    
    static final boolean proxyStartPluginProcess(Context context, int index) {
            //
            ContentValues values = new ContentValues();
            values.put(KEY_METHOD, METHOD_START_PROCESS);
            values.put(KEY_COOKIE, PMF.sPluginMgr.mLocalCookie);
            Uri uri = context.getContentResolver().insert(ProcessPitProviderBase.buildUri(index), values);
            if (LOG) {
                LogDebug.d(PLUGIN_TAG, "proxyStartPluginProcess insert.rc=" + (uri != null ? uri.toString() : "null"));
            }
            if (uri == null) {
                if (LOG) {
                    LogDebug.d(PLUGIN_TAG, "proxyStartPluginProcess failed");
                }
                return false;
            }
     
            return true;
        }
    

    这里是个对数据库Provider的插入操作,那么究竟是对哪个Provider进行了数据库操作呢?

    关键在这里:

    ProcessPitProviderBase.buildUri(index)
    

    最终生成的Uri是这样的:

    content://com.qihoo360.replugin.sample.host.loader.p.mainN99/main
    

    这个玩意对应的啥呢?貌似宿主Manifest中并没有配置这个,如果分析过gradle插件应该可以想到,估计又是动态编译自动生成的,反编译宿主apk可以在Manifest中发现:

    <provider
                android:name="com.qihoo360.replugin.component.process.ProcessPitProviderP0"
                android:exported="false"
                android:process=":p0"
                android:authorities="com.qihoo360.replugin.sample.host.loader.p.mainN100" />
    

    这样就全部串起来啦,想要开启分配在自定义进程中的Service首先就需要通过启动动态编译自动生成的对应进程Provider来拉起进程。
    最终返回的IPluginClient的IBinder实现体其实就是PluginProcessPer,生成的PSS也就是PluginServiceServer.mStub了。跟上述常驻进程生成的PSS方式是一样的。

    第4步
    PSS中startService最终调用的是PluginServiceServer.this.startServiceLocked(intent,client)方法,其内部首先通过retrieveServiceLocked方法生成ServiceRecord对象,接着利用installServiceIfNeededLocked方法去加载Service对象,利用发射将PluginContext通过attachBaseContextLocked方法赋值到上下文内部,接着执行Service的onCreate方法,然后记录以及被加载,同时startPitService开启一个坑位Service,提高进程优先级防止被杀,后面继续调用onStartCommand方法。

    // 加载插件,获取Service对象,并将其缓存起来
        private boolean installServiceLocked(ServiceRecord sr) {
            // 通过ServiceInfo创建Service对象
            Context plgc = Factory.queryPluginContext(sr.plugin);
            if (plgc == null) {
                if (LogDebug.LOG) {
                    Log.e(TAG, "installServiceLocked(): Fetch Context Error! pn=" + sr.plugin);
                }
                return false;
            }
            ClassLoader cl = plgc.getClassLoader();
            if (cl == null) {
                if (LOGR) {
                    LogRelease.e(PLUGIN_TAG, "psm.is: cl n " + sr.className);
                }
                return false;
            }
     
            // 构建Service对象
            Service s;
            try {
                s = (Service) cl.loadClass(sr.serviceInfo.name).newInstance();
            } catch (Throwable e) {
                if (LOGR) {
                    LogRelease.e(TAG, "isl: ni f " + sr.plugin, e);
                }
                return false;
            }
     
            // 只复写Context,别的都不做
            try {
                attachBaseContextLocked(s, plgc);
            } catch (Throwable e) {
                if (LOGR) {
                    LogRelease.e(PLUGIN_TAG, "psm.is: abc e", e);
                }
                return false;
            }
            s.onCreate();
            sr.service = s;
     
            // 开启“坑位”服务,防止进程被杀
            startPitService();
            return true;
        }
    

    二、总结

    针对插件中startService的Service对象,其实Replugin框架仅仅将此Service当做一个普通的类进行代码强制执行其对应的生命周期各回调方法,当然Android FWK启动Service证明周期也是类似的方式做的,只是原始启动Service的各生命周期是在进程对应的ActivityThread中,而Replugin则是在PSS内部代码执行。这一点跟启动插件Activity是不太一样的。同时还有一个就是启动自定义进程的Service会利用动态编译自动生成的对应该进程的Provider,首先调用该Provider的insert数据库操作去吊起此进程,然后再获得IPluginClient中的PSS,显然,Replugin框架的使用动态编译的地方无处不在。

    原文链接:https://blog.csdn.net/hellogmm/article/details/79188334
    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680

  • 相关阅读:
    Python学习笔记六:集合
    Python学习笔记五:字符串常用操作,字典,三级菜单实例
    Python学习笔记四:列表,购物车程序实例
    Python学习笔记三:数据类型
    python学习笔记二:if语句及循环语句,断点,模块,pyc
    Python学习笔记一:第一个Python程序,变量,字符编码与二进制,用户交互程序
    JS教程:从0开始
    基于Token认证的多点登录和WebApi保护
    数据库高级对象(存储过程,事务,锁,游标,触发器)
    Sql基础(零基础学数据库_SqlServer版)
  • 原文地址:https://www.cnblogs.com/Android-Alvin/p/11983670.html
Copyright © 2020-2023  润新知