Natasha 插件化之dll
Posted 摧残一生 涅槃重生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Natasha 插件化之dll相关的知识,希望对你有一定的参考价值。
调用外部dll来实现组件化
场景
- 有一个设备管理控制系统,主要作用是控制设备及收集相关设备的信息,目前只集成了门禁和监控,后期期望添加更多设备时,一般都是在公司编写完后现场实施并调试,代码一般也是每个设备创建独立的项目,供总项目调用;慢慢的可能会演变出所有设备都继承一个公共的接口类,接口类中实现获取能力集和发送命令,以此来减少对于总控模块的修改,而此时只需要维护好能力集即可;可是这样每次也需要运行一整个解决方案,如果其他地方也需要这个系统,只能通过卸载项目来减少引用进行实时,这时就可以通过将项目分拆出去,通过Natasha进行组件化的管理。这样子的好处是通过约定的接口和能力集进行通信,主程序和设备耦合度低,如果遇到其他项目需要该系统,只需要将所需的dll放入特定文件夹即可。
- 任何可以分拆成模块的系统都可以按照组件化的逻辑进行开发,例如有业务流程的项目,每个步骤可有多个组件选项,可执行一个或者多个;或者说通过获得的插件来动态配置那一步应执行那个插件,通过插件的反馈判断是否应执行下一步操作。
好处
- 低耦合,业务分拆,插件只需要关心插件接口及反馈即可,不需要关心核心系统的业务逻辑
- 分工明确,每个人只需关注插件代码即可,项目整合后出现问题也好排查,未调用插件,则主系统有问题,调用插件结果与实际不符则插件有问题。
- 维护方便,学习成本低,对于某一个特定插件,代码量会远远低于整个项目的代码量,而且每个业务都可以进行分拆。
实现
- 获得Assembly
主要使用NatashaDomain类实例化的方法(源码位置:src\\Natasha.Domain\\Extension\\NatashaDomainExtension.cs)
相关方法:
LoadPluginUseDefaultDependency 如果加载的dll已经被加载过了,则跳过
LoadPluginWithAllDependency 不会判断高低版本,源码中的解释是默认的,感觉和Default类似
LoadPluginWithHighDependency 使用高版本的dll
LoadPluginWithLowDependency 使用低版本的dll
参数说明:
string path 必填,dll所在路径
Func<AssemblyName, bool>? excludeAssembliesFunc = null 选填,需要排除的dll,返回true为排除引用,例如共同引用某个公用的dll(例如Utils.dll),此时可以选择使用哪个版本的dll
- 找到clas类并且实例化
找到class类(默认所有插件的实现类为*Controller),也可以按照下面例子来
var type = assembly.GetTypes().Where(item => item.Name.IndexOf("Controller")!=-1).First();
实例化
var plugin = (IPluginClass)(Activator.CreateInstance(type)!);
例子
-
创建两个底层项目
-
IPluginBase项目:用于创建插件使用的接口
using PluginUtil; namespace IPluginBase public interface IPluginClass /// <summary> /// 初始化方法 /// </summary> public void initialize(); /// <summary> /// 获得插件特有的方法 /// </summary> /// <returns></returns> public List<FruitFunction> getFunction(); /// <summary> /// 获得需要定时方法 /// </summary> /// <returns></returns> public List<FruitFunction> getTimeFunc(); /// <summary> /// 通过方法执行插件代码 /// </summary> /// <param name="function">FruitFunction</param> /// <param name="param">可能输入的数值</param> /// <returns></returns> public String execute(FruitFunction function, String param);
-
PluginUtil项目,用于声明FruitFunction
using System.ComponentModel; namespace PluginUtil public enum FruitFunction [Description("硬度")] hardness = 01, [Description("苹果特有功能")] appleAttr = 02, [Description("切")] cut = 03
-
-
创建两个插件项目
-
PluginApple项目
using IPluginBase; using PluginUtil; namespace PluginApple public class PluginAppleClass : IPluginClass public string execute(FruitFunction function, string param) switch (function) case FruitFunction.cut: Console.WriteLine("切苹果"); break; case FruitFunction.hardness: Console.WriteLine("苹果很脆"); break; case FruitFunction.appleAttr: Console.WriteLine("苹果特有属性"); break; return "结束"; public List<FruitFunction> getFunction() Console.WriteLine("返回苹果的现有功能"); return new List<FruitFunction>() FruitFunction.appleAttr, FruitFunction.cut, FruitFunction.hardness ; public List<FruitFunction> getTimeFunc() Console.WriteLine("返回苹果的定时功能"); return new List<FruitFunction>() FruitFunction.cut; public void initialize() Console.WriteLine("苹果初始化完成");
-
PluginBanana项目
using IPluginBase; using PluginUtil; namespace PluginBanana public class PluginBananaClass : IPluginClass public string execute(FruitFunction function, string param) switch (function) case FruitFunction.cut: Console.WriteLine("切香蕉"); break; case FruitFunction.hardness: Console.WriteLine("香蕉很软"); break; return "结束"; public List<FruitFunction> getFunction() Console.WriteLine("返回香蕉的现有功能"); return new List<FruitFunction>() FruitFunction.cut, FruitFunction.hardness ; public List<FruitFunction> getTimeFunc() Console.WriteLine("返回香蕉的定时功能"); return new List<FruitFunction>() FruitFunction.cut ; public void initialize() Console.WriteLine("香蕉初始化完成");
-
-
使用Natasha实现两个插件项目的方法
前置条件:将PluginApple.dll和PluginBanana.dll放到NatashaStudyConsole.exe同级下的plugins文件夹中
using IPluginBase; using PluginUtil; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace NatashaStudyConsole internal class PluginDemo public void PluginMethod() //获得dll存放路径 string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory+ "plugins"); //实例化NatashaDomain NatashaDomain domain = new(Guid.NewGuid().ToString()); List<IPluginClass> assemblies = new List<IPluginClass>(); //通过获得所有的dll来进行实例化 Directory.GetFiles(path, "*.dll").ToList().ForEach(dll => //加载dll var assembly = domain.LoadPluginWithAllDependency(dll); // 本例子中项目名称为"A",需要实例化的类为"AClass",因此使用IndexOf方法 // 获得项目名称 var asmName = assembly.GetName().Name!; // 根据项目名称判断应该实例那个class var type = assembly.GetTypes().Where(item => item.Name.IndexOf(asmName)!=-1).First(); // 实例化 var plugin = (IPluginClass)(Activator.CreateInstance(type)!); if (plugin != null) // 保存 assemblies.Add(plugin); ); assemblies.ForEach(assembly => // 实现相关方法 List<FruitFunction> functions = assembly.getFunction(); if(functions != null && functions.Count > 0) assembly.execute(functions[0], ""); );
代码结构示意图:
执行结果:
插件化之插件Service 新的Hook方法
本文同步自wing的地方酒馆
给大家分享一个新的Hook插件Service的方法,与Activity替换类似,可以先在AndroidManifest.xml预留一个service,然后通过intent启动,并且将真正的Service的classname传递过去。可是Service没有涉及到Instrumentation更没有Instrumentation.newService()方法,怎么办呢? 且听我细细道来。
观察Service的创建流程,发现我们只需要想办法拿到intent即可做一些手脚。大家都知道,创建Service的时候,会走到H类里:
他接收一个CreateServiceData类型的参数,恰好这个参数里面有intent参数:
那么能不能从这里下手呢,答案是否定的。因为data初始化的时候,并没有将intent传入,见下代码:
所以我们换一种思路,bind的时候,有没有intent呢?阅读源码得到以下信息:
哈哈,确实传进来了。所以我们可以在bind的时候,拿到真正要启动的classname,再bind的时候,把他给偷梁换柱! 具体怎么做呢。H类是个Handler,观察Handler内部有个callback,他可以在真正处理消息之前去做一些手脚,所以我们搞个callback给他干进去。
新建一个callback ,在里面把intent取出:
由于是基于bind的,所以要看一下之后做了什么,观察handleBindService方法,可以发现bind的时候service是已经创建好了的,并且存在一个名为mService的ArrayMap里。key为token ,IBinder:
所以要做的事情就很明了了,他不是从这里取吗,那我们就给他替换掉呀,所以把mService取出来:
此时我拥有bind的msg里面包含了intent、token等等信息,所以只要把对应key的value替换掉即可。在intent里拿到真正的service class name,用classloader加载,此时service已经创建完毕了,但是还没有初始化。
观察系统service初始化过程,发现还需要context.setOuter attch等操作:
所以我们来手动模拟这个过程:
attach方法全部参数设置为原本service自带的参数。这里只是改变了实例。初始化完毕之后,发现还需要进行binder通信
所以反射调用他,然后因为是在bind的时候做的手脚,所以service丢失了onCreate()这个生命周期,所以手动调用他:
这样就完成了Service的插件操作~~
欢迎加入我的Android酒馆:425983695 讨论技术~~
以上是关于Natasha 插件化之dll的主要内容,如果未能解决你的问题,请参考以下文章