阿里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

插件化框架解读之四大组件调用原理-Service(三)下篇的更多相关文章

  1. 插件化框架解读之四大组件调用原理-Activity(三)上篇

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 本文通过Activity调用原理来解读Replugin插件化技术 ...

  2. 插件化框架解读之android系统服务实现原理(五)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 一.系统服务提供方式 1.我们平时最常见的系统服务使用方式 Wi ...

  3. Android Small插件化框架解读——Activity注册和生命周期

    通过对嵌入式企鹅圈原创团队成员degao之前发表的<Android Small插件化框架源码分析>的学习,对Android使用的插件化技术有了初步的了解,但还是有很多需要认真学习的地方,特 ...

  4. 插件化框架解读之so 文件加载机制(四)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 提问 本文的结论是跟着 System.loadlibrary() ...

  5. 插件化框架解读之Android 资源加载机制详解(二)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680Android提供了一种非常灵活的资源系统,可以根据不同的条件提供 ...

  6. 插件化框架解读之Class文件与Dex文件的结构(一)

    阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 Class文件 Class文件是Java虚拟机定义并被其所识别的 ...

  7. [置顶] 滴滴插件化框架VirtualAPK原理解析(一)之插件Activity管理

    上周末,滴滴与360都开源了各自的插件化框架,VirtualAPK与RePlugin,作为一个插件化方面的狂热研究者,在周末就迫不及待的下载了Virtualapk框架来进行研究,本篇博客带来的是Vir ...

  8. Android插件化框架

    1.   dynamic-load-apk/DL动态加载框架 是基于代理的方式实现插件框架,对 App 的表层做了处理,通过在 Manifest 中注册代理组件,当启动插件组件时,首先启动一个代理组件 ...

  9. android 插件化框架VitualAPK

    推荐阅读: 滴滴Booster移动App质量优化框架-学习之旅 一 Android 模块Api化演练 不一样视角的Glide剖析(一) LeakCanary 与 鹅场Matrix ResourceCa ...

随机推荐

  1. WEEX-EROS开发小笔记

    本文是作者之前刚接触移动端跨平台开发,使用weex-eros开发项目平日里记下来的一些笔记,分享出来方便为新手解惑,weex-eros是weex的一套解决方法,使用vue语法糖,对于前端开发者来说可以 ...

  2. JavaScript深入之变量对象(转载)

    前言 在上篇<JavaScript深入之执行上下文栈>中讲到,当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(executio ...

  3. 记一次redis读取超时的排查过程(SADD惹的祸)

    问题背景 在业务使用redis过程中,出现了read timeout 的异常. 问题排查 直接原因 运维查询redis慢查询日志,发现在异常时间节点,有redis慢查询日志,执行sadd 命令花费了1 ...

  4. java23种设计模式(三)-- 适配器模式

    一.适配器模式 转载:https://www.cnblogs.com/V1haoge/p/6479118.html 适配器就是一种适配中间件,它存在于不匹配的二者之间,用于连接二者,将不匹配变得匹配, ...

  5. canvas 点击图片播放视频

    canvas.js window.onload=function() { var canvas = document.getElementById('canvas'); var ctx= canvas ...

  6. globalAlpha 示例

    代码实例: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...

  7. python 时间和时间段显示

    两个包,最开始发现的是time包 import time print(time.time()) #显示当前时间戳 print(time.localtime(time.time())) #显示本地时间 ...

  8. Redis GeoHash

    原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/11632810.html 背景 微信找附近的人,滴滴找附近的单车,饿了么找附近的餐馆 GeoHash算法 ...

  9. Sublime Text 快捷键汇总

    1. 常用快捷键 Ctrl+D 选词 (反复按快捷键,即可继续向下同时选中下一个相同的文本进行同时编辑)Ctrl+G 跳转到相应的行Ctrl+J 合并行(已选择需要合并的多行时)Ctrl+L 选择整行 ...

  10. Leetcode_897. Increasing Order Search Tree

    题目:https://leetcode.com/problems/increasing-order-search-tree/ 题意: 将一棵二叉搜索树,重排为一棵递增的二叉排序树. 解法1: rson ...