最近在准备秋招,作为有过一年安卓开发经验的菜鸟,没想到秋招一败涂地。痛定思痛,决定重学安卓,因此开一个博客,记录学习的经验和相关总结。
这一篇文章,我将介绍如何通过抽象工厂+Android AutoService实现自动创建不同类型的view。注意:此方法会一次加载多个实例到内存,对内存有所影响。如果您的业务场景不能保证使用所有的实例,请谨慎选择,对于按需加载,也会在文章最后提及
(1) 需求定义
首先,我们明确需求。话说,还在杭州打工的时候,有一天老板和产品对我说,袁少,能不能做一个需求,我们的首页HeadView位支持不同的ViewType,根据不同的ViewType放不同的View。 比如:
ViewType=1,放小卡片View,
ViewType=2,放轮播图View,
ViewType=3,放广告海报View。
我一听:害,这种弱智需求能难得倒我?啪啪啪,我就在MainActivity把代码写出来了:
private View createViewByType(int type) {
View createdView = null;
switch (type) {
case 1:
createdView = myContext.getLayoutInflater().inflate(R.layout.view_1, null);
case 2:
createdView = myContext.getLayoutInflater().inflate(R.layout.view_2, null);
case 3:
createdView = myContext.getLayoutInflater().inflate(R.layout.view_4, null);
}
return createdView;
}
别告诉什么单一职责,什么封装,可扩展,我面试的时候背的设计模式原则什么的都是狗屁,主打的就是一个简单直接。
可是后面真把哥们搞懵逼了,别的先不说,就那个 “type” 可太让人闹心了。服务端那边就只给传过来 1、2、3、4..... N个数字,每个数字对应什么type经常换来换去。每个数字代表的具体含义是什么,我是真记不住呀,每次都得翻半天文档才能找到对应的创建逻辑,维护起来别提多麻烦了。
而且,随着业务的发展,“type” 的种类越来越多,每一种 “type” 对应的视图创建好之后,还有初始化、网络请求以及数据绑定等一大堆业务逻辑要往里塞。这下可好,那个 switch - case 语句里的代码量蹭蹭往上涨,看着那一大片代码,真是让人头皮都发麻!
所以,已老实,求放过,着手重构,家人们。
(2) 加入抽象工厂模式
回想我学过的设计模式,哎,这有个抽象工厂模式,非常适合这里的业务逻辑,天才呐!什么是抽象工厂模式?网上的介绍说实话没用之前我也是背的,理解起来比较困难。我下面说一下我个人的理解。
首先是抽象工厂模式的组成
- 抽象工厂(Abstract Factory) :它是一个接口或者抽象类,声明了创建一系列相关产品对象的抽象方法。这个抽象方法并不负责具体的产品创建细节,只是定义了创建的规范。例如,在一个游戏开发中,抽象工厂可以声明创建游戏角色、游戏场景、游戏道具等相关产品的抽象方法。
- 具体工厂(Concrete Factory) :它实现了抽象工厂接口或者继承自抽象工厂抽象类,负责具体的产品对象创建。每一个具体工厂对应一种具体的产品系列组合。比如,对于一款有不同风格(科幻风格、魔幻风格)的游戏,就可以有科幻风格的具体工厂负责创建科幻风格的游戏角色、科幻风格的游戏场景等,魔幻风格的具体工厂则创建相应魔幻风格的产品。
- 抽象产品(Abstract Product) :它也是接口或者抽象类,定义了产品对象的公共属性和方法,是具体产品类需要遵循的抽象规范。以游戏角色为例,抽象产品角色类可能定义了角色的基本行为,像移动、攻击等抽象方法。
- 具体产品(Concrete Product) :实现了抽象产品接口或者继承自抽象产品抽象类,代表了具体的产品对象,有着各自具体的实现细节。比如科幻风格的游戏角色类会具体实现移动、攻击等行为,并且带有科幻风格的特效展示,这就是具体产品。
抽象工厂模式的核心要点无疑聚焦在 “抽象” 这一关键特性之上,想必大家都清楚,在 Java 编程的世界里,抽象可是实现多态这一重要特性的关键机制。多态有什么用?有用,可太有用了,下面会看到的
好,这里我们的产品就是要创建的View,简单起见,不做进一步的定义。
我们直接开始用抽象工厂模式重构,先定义一个抽象创建工厂和其N个具体实现类
抽象工厂
public interface CreateViewFactory {
public View createView(LayoutInflater inflater);
}
具体工厂1
public class CreateView1Factory implements CreateViewFactory{
@Override
public View createView(LayoutInflater inflater) {
View view= inflater.inflate(R.layout.view_1,null);
//todo 其他初始化业务逻辑
return view;
}
}
如果你认真看了,宝,你认真看了对吗? 你看,这里我的抽象工厂被定义为一个接口而不是抽象类,你不好奇吗?为什么这样子,那,玄妙在后面,且听我慢慢道来。
好了我有抽象工厂和具体工厂了,再定义一个创建helper:
public class CreateHelper {
// 直接创建实例,在类加载时就完成实例化
private static final CreateHelper instance = new CreateHelper();
// 私有构造函数,防止外部通过new关键字创建实例
private CreateHelper() {}
// 提供一个公共的静态方法获取实例
public static CreateHelper getInstance() {
return instance;
}
//根据type创建view
public View createViewByType(int type, LayoutInflater inflater){
View view;
switch (type){
//根据type 调用不同的创建工厂创建view
case 1:
view=new CreateView1Factory().createView(inflater);
case 2:
view=new CreateView2Factory().createView(inflater);
//.........
}
return view;
}
}
这里的业务逻辑是,将创建业务都封装到Helper类中,在调用Helper类的createViewByType方法 创建出具体的产品工厂,接着创建view。
似乎,好像有点脱裤子放屁,😂,createViewByType方法还不是写一大堆的Switch-Case代码,然后就是把具体创建逻辑封装了呗。也没用抽象工厂接口啊!多态体现在哪里啊!
可恨!还是菜的不行,这里我还是要写一大堆Switch-Case代码啊,再研究,下一步我要拿下它!!!且看:
public class CreateHelper {
// 直接创建实例,在类加载时就完成实例化
private static final CreateHelper instance = new CreateHelper();
//创建工厂们
private static HashMap<Integer, CreateViewFactory> factorys= new HashMap<>();
// 私有构造函数,防止外部通过new关键字创建实例
private CreateHelper() {}
// 提供一个公共的静态方法获取实例
public static CreateHelper getInstance() {
return instance;
}
public void registerFactory(int type,CreateViewFactory factory){
factorys.put(type,factory);
}
//根据type创建view
public View createViewByType(int type, LayoutInflater inflater){
if(factorys.containsKey(type)){
return factorys.get(type).createView(inflater);
}
return null;
}
}
你看
//创建工厂们
private static HashMap<Integer, CreateViewFactory> factorys= new HashMap<>();
这里我加入了一个 HashMap factorys 这里我们将viewtype作为键,抽象工厂接口作为值的类型,这个factorys用于保存的各个具体的创建工厂。这就是多态了。当我需要createViewByType时,只需要根据type取得对应的创建工厂就行了。
//根据type创建view
public View createViewByType(int type, LayoutInflater inflater){
if(factorys.containsKey(type)){
return factorys.get(type).createView(inflater);
}
return null;
}
再强调一遍, factorys中值的类型是抽象工厂接口,而抽象工厂接口是可以指向其具体工厂实现类的,因此我们才能通过HashMap去获得具体工厂类。这就是多态的好处,大大减少了代码量。
好了好了,如果你看懂了,此处应该有掌声,说明你和我一样入门了。
但是,说但是了,求你继续看吧,还有一个问题,怎么把Type和它对应的具体工厂实例保存的啊? 原来是通过 registerFactory方法。
public void registerFactory(int type,CreateViewFactory factory){
factorys.put(type,factory);
}
哎,原来通过调用这个registerFactory方法手动去注册到HashMap里,这是不是有点不够优雅!!!!!,主要是我懒啊,有20个type,我的手要去注册20遍, 你看这拙劣的代码:
CreateHelper.instance.registerFactory(1,new CreateView1Factory());
CreateHelper.instance.registerFactory(2,new CreateView2Factory());
CreateHelper.instance.registerFactory(3,new CreateView3Factory());
CreateHelper.instance.registerFactory(4,new CreateView5Factory());
.........
CreateHelper.instance.registerFactory(20,new CreateView20Factory());
(3)使用AutoService实现自动注册功能
**哎,要是有人帮我注册就好了,还真有,谷歌推出的 AutoService,什么是AutoService:
AutoService 是谷歌提供的一个组件,使用简单但是功能却是非常强大。AutoService 会自动在 META-INF 文件夹下生成 Processor 配置信息文件,该文件包含实现该服务接口的具体实现类。当外部程序加载这个模块时,可以通过该 jar 包 META-INF/services/ 里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
AutoService 其实是基于 SPI 机制,这样我们可以直接跨模块查找到想要的接口实现类,避免不必要的模块间依赖,降低模块之间的耦合性。
说了一堆,也不太好懂,我还是用我们的业务给您说明白吧,在我这里,此时此刻AutoService 就是一个能通过注解帮助我们自动创建和加载接口实例的工具。(当然它不止这么点功能)
怎么个事呢? 首先我们引入AutoService到项目:
implementation 'com.google.auto.service:auto-service-annotations:1.0.1'
annotationProcessor 'com.google.auto.service:auto-service:1.0.1'
然后回到我们的抽象工厂接口,给它加上一个方法:
public interface CreateViewFactory {
public int getType();
public View createView(LayoutInflater inflater);
}
注意:这里我们加入了一个getType的抽象方法,在具体工厂实现类里,各个工厂返回它们自身创建的viewtype的值。这里不难理解吧,就是viewtype=1的创建工厂返回1,依次类推:看代码;
@AutoService(CreateViewFactory.class)
public class CreateView1Factory implements CreateViewFactory{
@Override
public int getType() {
return 1;
}
@Override
public View createView(LayoutInflater inflater) {
View view= inflater.inflate(R.layout.view_1,null);
//todo 其他初始化业务逻辑
return view;
}
}
好!,你看这里的具体工厂类,我给它添加了一个注解 @AutoService(CreateViewFactory.class),并且实现了getType()方法返回1。加了有什么用呢?来来注意看,回到Helper类的构造函数里。
// 私有构造函数,防止外部通过new关键字创建实例
private CreateHelper() {
//加载所有添加了@AutoService(CreateViewFactory.class)注解的实现类的实例
ServiceLoader<CreateViewFactory> load = ServiceLoader.load(CreateViewFactory.class);
//循环放入factorys map
for (CreateViewFactory factory : load) {
if(!factorys.containsKey(factory.getType())){
factorys.put(factory.getType(), factory);
}
}
}
可以看到,这里我使用了ServiceLoader.load(CreateViewFactory.class); 这个语句,来加载CreateViewFactory接口的实现类的实例,并且循环所有实例,根据其type,存放到map之中。
可见,只要具体工厂类加上了这句注解:
@AutoService(CreateViewFactory.class)
那么系统就能自动的加载该接口的具体实现类并创建实例,并且能够通过ServiceLoader.load方法拿到自动创建的实例,而你,我的朋友,就不用手动注册了。
在这里我总结一下,希望各位佬,能看的更明白一点。
1.首先我们可以通过抽象工厂模式来减少创建不同type的View的模版代码量,并且支持扩展,具体来说,我们将所有View创建工厂存放到一个HashMap中,通过type去取创建工厂进行创建View,这个过程中涉及到抽象工厂模式里抽象带来的多态的好处,不难理解。
2.如何保存每个type和其对应的创建工厂到Hashmap?要么手动注册,一行一行的注册,比较蠢笨。而使用AutoService添加注解的方式,能够自动帮我们加载所有具体工厂类的实例,然后通过一个for循环进行注册,既不需要手动注册,也不会漏注册。 缺点就是有些工厂我们用不到也可能被创建实例了,占用了内存
这个方法的缺点就是一次性创建所有的实例并加载到内存之中,对内存占用有影响。 解决:这个时候就不能使用AutoService,我们应当保存各个工厂类的.Class对象到HashMap,然后然后根据type取出.Class进行反射来创建实例。此外可以使用注解处理器APT技术来搞定手动注册问题(下一篇讲解)