绩效定了,钱也发了,虽然有些不如意,仍需努力吧。工作至今马上三年,也完成了自己金库可支配现金流的
一个小目标,接下来就要减肥了,同时年纪也不小了,是时候考虑了,但是作为一个自卑且不自信的人,真的
太难了,尽力而为吧。现在什么行情都不好,谁也不能保证自己可以一直干下去,我们能做的就是多学习,让
自己的技术更加精湛,这个可能是当下最需要做的。而且也要早早的想后路了,程序员不可能干一辈子的,这
个东西还是要在脑子里有一根弦的。
最近看到罗翔老师说的一句话很喜欢,天道酬勤,在当今的社会可能我们不要辩证的去看,不是说努力了就一
定会有收货的,我们需要管理自己的预期。其实人生中可能只有百分之五的事情使我们可以决定的,剩下的大
头百分之九十五我们是决定不了的。但是我们仍需努力,我们能做的就是尝试用这微小的我们可以左右的可能
性去撬动大部分的未知的未来。
问题
既然上篇说道了ASM+Transform,我们也仅仅是Demo级别的实践,那么有什么具体的应用场景吗?就是当前项目大规模用到的。答案是有的-IOC。(IOC就不具体的介绍了,可以自行百度一下。简单来说就是获取能力的实例对象,我们依赖接口,会有专门统一的管理容器给我们提供对象,这个其实是Spring后端常用的)
- Android目前的项目为什么需要IOC呢?
- IOC具体需要怎么实现呢?
- 对象实例创建,需要手动吗?保障单例?
- 对象什么时候把实例注入容器?
- 模块比较多,分散在各个module,如何保证统一注册不遗漏?
Android目前的项目为什么需要IOC
当前现状
当前的APP越来越大了,我们都是模块化开发,比如首页模块,我的模块,基础能力有网络模块,安全加密模块,而且模块之间是有耦合的,比如首页和我的可能有一些页面要相互调用,而且他们都需要使用网络和安全加密模块,如果直接依赖,依赖关系会非常复杂,也可以看到模块的全貌,也不易于维护,这个时候参考Spring的IOC,其实android也可以实现自己的IOC。
第一步解耦
我们可以把模块拆分成链两个部分,接口 Interface+实现Impl两部分,(当然,如果拆分成两个Module也是可以的),如果首页模块,要使用网络,直接依赖网络的接口Module。这一步,我们就实现了业务之间的解耦,而且从Java设计的角度,我只关心我需要能力,模块其他的部分对我来说也是不可见的。
注入实现
我们实现了接口的依赖,但是看不到实现啊 ?没有实现我们怎么调用呢?这个时候就需要IOC了,如果 提供一个容器 ,我们传递依赖的接口就返回给我们需要的额实例是不是就可以了?是的,答案是可以的。我们让当前模块再次依赖IOC容器即可(暂时当成黑盒,后面会介绍),这样我们就可以获取到实例了。这样依赖不仅简单,而且我们需要什么能力,就依赖能力的接口就好。
IOC具体实现方案
这里我们也是讲方案,不会精细到每一行代码怎么实现。
生成实例对象代码
定义接口
接口的统一顶层接口类Service。如果要创建网络的接口,比如INetService,需要继承Service。后面会讲到。
同时定义register接口,目的是实现Service的注册,注册到IOC容器之中。
public interface Service {
}
public interface INetService extends Service {
}
public interface IServiceRegister {
void register();
}
定义实现注解
构建生成代码的实现类注解。需要把这个注解用到具体Service的实现类。比如NetServiceImpl。
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface InjectService {
}
@InjectService
public class NetServiceImpl implements INetService {
...
}
定义生成代码基类
为了实现单例,我们创建一个ServiceWrapper基类,这是一个包裹类,只有调用getInstance的时候,才会创建真正的Service实例,这样可以保证单例的同时也是延迟创建。(为什么getInstance的权限是package呢?因为这个类是在IOC的module的,getInstance我也不希望外界其他module感知并调用,你直接从容器中统一入口获取就好,这个getInstance的方法,会在ioc的module自己调用)
public abstract class ServiceWrapper<T> {
private T mInstance;
protected abstract T newInstance();
final T getInstance() {
if (this.mInstance == null) {
synchronized (this) {
this.mInstance = this.create();
}
}
return this.mInstance;
}
private T create() {
return newInstance();
}
}
生成代码
这个我们可以通过JavaPoet来实现,如果有小伙伴不会,B站链接www.bilibili.com/video/BV1RW…
我们可以读取@InjectService注解,找到这个类NetServiceImpl,然后生成代码,如下:
首先生成NetServiceWrapper类,然后生成NetServiceRegister,其中的register方法,就是把Wrapper注入到IOC容器之中。很好理解。
public class NetServiceWrapper extends ServiceWrapper<INetService> {
protected INetService newInstance() {
return new NetServiceImpl();
}
}
public class NetServiceRegister implements IServiceRegister {
public void register() {
ServiceManager.register(INetService.class, new NetServiceWrapper());
}
}
为什么我们需要INetService继承Service呢,因为哈,NetServiceImpl可能除了实现INetService ,可能还会实现InterfaceA,InterfaceB,那么我们怎么确定ServiceWrapper的泛型呢?答案就是INetService继承Service,通过Service来确定最终的泛型。
小结
这样,我们就通过注解和接口,让JavaPoet生成了代码,我们的实际Service实例都是在ServiceWrapper中,我们接下来就需要吧各种ServiceWrapper的实现类放到我们的IOC容器即可。(容器其实是一个Map,key就是INetService接口,value就是servicewrapper对象)
Transform获取Register
既然各种ServiceWrapper都已经生成了,并且都在Register中准备好嘞。那么如何获取到这些ServiceRegister呢?很显然,我们可以通过Transform获取。
tips:如果对于Transform不是很熟悉,可以参考上一篇博客哈juejin.cn/post/708751…
简单回顾:Transform就是在class2dex的时候,我们可以读取class字节码,以及修改class字节码(可以参考android打包流程哈),我们需要的就是自定义Transform,读取字节码,找到各种register的class。而且Transform主要是关心的是input(输入)和output(修改的字节码),这部分代码其实是可以从网上复制的模板代码。
获取IServiceRegister
通过Transform获取到各种register,他们都是实现了IServiceRegister接口的,比如NetServiceRegister,存放到一个List中。
定义注册入口
这里,我们为了方便,同样定义一个注解@RegisterInit,目的是放我们找到对应的类(ServiceRegisterInit),方便ASM字节码注入到init方法中。在init方法中把之前的IServiceRegister的各种实现类,依次调用register方法,把ServiceWrapper注入到IOC容器中。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
@interface RegisterInit {
}
@RegisterInit
public class ServiceRegisterInit {
/**
* ASM字节码注入
*/
public static void init() {}
}
外界调用
外界只需要在Application的onCreate方法中,调用ServiceRegisterInit的init方法即可,这样IOC容器中就会注册好所有需要的Service能力了。
小结
通过Transform找到需要的实现类,然后ASM字节码插桩,把初始化的代码注入到指定的方法之中。
总结
这篇文章我们知道IOC实现的一种方式即可,IOC=JavaPoet+Transform+ASM。然后外界在Application的onCreate调用初始化入口即可。
- JavaPoet:生成代码,生成的代码主要存放着Service的实例对象。
- Transform:获取到生活曾代码的class,存放到一个ServiceList当中,方便后续的字节码插桩。
- 找到初始化的入口,然后获取到ServiceList,执行字节码插桩,把注册代码写入其中。