自从项目分解模块之后,由于之前重构过程比较仓促,花了比较少的精力来进行模块之间的解耦,沿用了比较传统的接口暴露方式,它的特点是需要在主app注册一个接口实现,暂时能够缓解模块独立开发的尴尬。
它的形式大概是这样的:
AController.getInstance().init(CallBack callback)//在调用方执行,调用方需要实现所有的方法
但它存在几个问题:
1.代码耦合:独立模块的代码分布在各个使用方 2.拓展性差:CallBack一旦修改,就会对其他使用方产生中断;且所有的接口都集中在CallBack, 不需要接口的也带给了使用方; 3.不支持无缝移除:当想移除某个独立模块的时候,代码会直接报错,无法编译通过;
由于模块间的开发节奏很快,经常出现接口改动和新增接口的问题,导致模块衔接出现中断,以上的方式开始满足不了现有的开发模式,于是:Summer框架横空出世。
Summer框架是什么
简答来说:他是一个基于APT(编译时生成代码)+动态代理的框架,如Dagger,ButterKnife都是基于APT实现的。
Summer本身主要解决 模块间 编译耦合问题。
这样说可能还是有点抽象,我们来看下模块间是怎么进行解耦和通讯的。
模块A(提供方)
我们假设模块A需要提供给模块B一个功能,我们把这个功能实现写在模块A里边,如下:
@Protocol("ForBModule")
public class TestForServer {
public void testMethod(String msg, Context context,
TextView textView) {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
@Protocol("ForBModule"):Protocol是summer提供的RetentionPolicy.CLASS注解;ForBModule这个只是个名字,会后后边的使用方一致,summer其实就是用这个名字来匹配实现类的。
就这样模块A的干的活结束了。接下来我们来看看模块B是怎么使用它的;
模块B(使用方)
1、按照一般的开发逻辑,应该是使用方提出了一个需求:我要操作模块A的某个东西;所以模块B定义了一个接口,让模块A去实现:
@ProtocolShadow("ForBModule")
public interface ModuleStub {
public void testMethod(String msg, Context context, TextView textView);
}
@ProtocolShadow("ForBModule"):ProtocolShadow是summer提供的RetentionPolicy.CLASS注解;ForBModule这个只是个名字,必须和前边的提供方一致,summer其实就是用这个名字来匹配的。
至此,协议代码编写完毕。
2、开始使用:
ProtocolInterpreter.getDefault().
create(ModuleStub.class)
.testMethod("oh this from mainActivity!",
getApplicationContext(), textView);
稍后再解释原理,从以上调用我们可以看出以下特点:
1、对实现方无感知,完全解耦;
2、面向接口编程,拓展方便
原理
编译时生成代码-APT
1、编写处理器:
编译时生成代码
public class ProtocolProcessor extends AbstractProcessor {
Elements elementUtils;
Types typeUtils;
Filer filer;
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
processProtocol(roundEnv);
return true;
}
private static class ElementHolder {
TypeElement typeElement;
String valueName;
String clazzName;
String simpleName;
public ElementHolder(TypeElement typeElement, String valueName, String clazzName, String simpleName) {
this.typeElement = typeElement;
this.valueName = valueName;
this.clazzName = clazzName;
this.simpleName = simpleName;
}
}
/**
* 编译期不做强制校验,这样运行 独立模块编译通过
* 但是一旦使用@ProtocolInterpreter 将触发强制校验
* 可能引发异常
*
* @param roundEnv env
*/
private void processProtocol(RoundEnvironment roundEnv) {
Map shadowMap = collectClassInfo(roundEnv, ProtocolShadow.class, ElementKind.INTERFACE);
if (shadowMap.keySet().size() == 0) {
System.out.println("find ProtocolShadow size 0");
//return;
}
for (String value : shadowMap.keySet()) {
System.out.println("create file for protocol shadow " + value);
ProtocolDataClass protocolDataClass = new ProtocolDataClass();
try {
String simpleName = shadowMap.get(value).simpleName;
JavaFileObject fileObject = filer.createSourceFile(ProtocolDataClass.getClassNameForPackage(simpleName), (Element[]) null);
Writer writer = fileObject.openWriter();
writer.write(protocolDataClass.generateMiddleClass(simpleName, value));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* package com.meiyou.framework.summer.data;
public class ModuleStub {
public static String value = "ModuleBarStub";
public ModuleStub() {
}
}
*/
Map protocolMap = collectClassInfo(roundEnv, Protocol.class, ElementKind.CLASS);
if (protocolMap.keySet().size() == 0) {
System.out.println("find Protocol size 0");
//return;
}
for (String value : protocolMap.keySet()) {
System.out.println("create file for protocol " + value);
ProtocolDataClass protocolDataClass = new ProtocolDataClass();
try {
JavaFileObject fileObject = filer.createSourceFile(value, (Element[]) null);
Writer writer = fileObject.openWriter();
//注意这里跟 shadow 传递参数有点不一样
writer.write(protocolDataClass.generateMiddleClass(value, protocolMap.get(value).clazzName));
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*
package com.meiyou.framework.summer.data;
public class ModuleBarStub {
public static String value = "com.meiyou.test.testmodule.TestForServer2";
public ModuleBarStub() {
}
}
*/
}
private Map collectClassInfo(RoundEnvironment roundEnv,
Class clazz, ElementKind kind) {
System.out.println("collectClassInfo for" + clazz.getSimpleName());
Map map = new HashMap<>();
for (Element element : roundEnv.getElementsAnnotatedWith(clazz)) {
if (element.getKind() != kind) {
throw new IllegalStateException(
String.format("@%s annotation must be on a %s.", element.getSimpleName(), kind.name()));
}
try {
TypeElement typeElement = (TypeElement) element;
Annotation annotation = element.getAnnotation(clazz);
Method annotationMethod = clazz.getDeclaredMethod("value");
String name = (String) annotationMethod.invoke(annotation);
String clazzName = typeElement.getQualifiedName().toString();
String simpleName = typeElement.getSimpleName().toString();
map.put(name, new ElementHolder(typeElement, name, clazzName, simpleName));
System.out.println("get Annotation from Class :" + simpleName+"-->name:"+name+"-->clazzName:"+clazzName+"-->annotationMethodName:"+annotationMethod.getName()+"-->map.zie():"+map.size());
//get Annotation from Class simpleName :TestForServer-->name:ModuleBarStub-->clazzName:com.meiyou.test.testmodule.TestForServer-->annotationMethodName:value
//get Annotation from Class simpleName:ModuleStub-->name:ModuleBarStub-->clazzName:com.meiyou.test.testmodule2.ModuleStub-->annotationMethodName:value
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return map;
}
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
elementUtils = env.getElementUtils();
typeUtils = env.getTypeUtils();
filer = env.getFiler();
}
@Override
public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet();
types.add(Function.class.getCanonicalName());
types.add(FunctionShadow.class.getCanonicalName());
types.add(Protocol.class.getCanonicalName());
types.add(ProtocolShadow.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
}
主要看processProtocol这个方法:
Map shadowMap = collectClassInfo(roundEnv, ProtocolShadow.class, ElementKind.INTERFACE);
Map protocolMap = collectClassInfo(roundEnv, Protocol.class, ElementKind.CLASS);
这里收集了含有ProtocolShadow和Protocol两个注解的类信息,并自动生成java类写入临时路径 build/intermediates/classes/debug/com/meiyou/framework/summer/data目录下;
分别生成两个类:
使用方生成的临时类
package com.meiyou.framework.summer.data;
import java.lang.String;
public class ModuleStub{
public static String value="ForBModule";
}
提供方生成的临时类
package com.meiyou.framework.summer.data;
public class ForBModule {
public static String value = "com.meiyou.test.testmodule.TestForServer";
public ForBModule() {
}
}
OK,生成这个两个类之后,Processor的活就算结束了,接下来我们看使用方如何利用这个两个类达到解耦的目的;
利用临时文件和动态代理实现解耦
先看一下summer的主要类:ProtocolInterpreter;
还记得我们上边的用法吗?
ProtocolInterpreter.getDefault().
create(ModuleStub.class)
.testMethod("oh this from mainActivity!",
getApplicationContext(), textView);
public class ProtocolInterpreter {
private static final String TAG = "ProtocolInterpreter";
private List mBeanFactoryList = new ArrayList<>();
private Map, InvocationHandler> mInvocationHandlerMap = new HashMap<>();
private Map, Object> mShadowBeanMap = new HashMap<>();
private DefaultBeanFactory mDefaultBeanFactory;
private ProtocolEventBus mProtocolEventBus;
private boolean enableCheckMethodToast,enableCheckMethod;
private Context mContext;
static public ProtocolInterpreter getDefault() {
return Holder.instance;
}
static class Holder {
static ProtocolInterpreter instance = new ProtocolInterpreter();
}
private ProtocolInterpreter() {
mDefaultBeanFactory = new DefaultBeanFactory();
mBeanFactoryList.add(mDefaultBeanFactory);
mProtocolEventBus = ProtocolEventBus.getInstance();
}
/**
* 使用入口
*
* @param stub interface lei
* @param clazz
* @return obj clazz
*/
public T create(Class stub) {
if (mShadowBeanMap.get(stub) != null) {
return (T) mShadowBeanMap.get(stub);
}
InvocationHandler handler = null;
try {
handler = findHandler(stub);
} catch (ClassNotFoundException e) {
throw new RuntimeException("error! findHandler!");
}
T result = (T) Proxy.newProxyInstance(stub.getClassLoader(), new Class[]{stub}, handler);
mShadowBeanMap.put(stub, result);
return result;
}
private InvocationHandler findHandler(Class stub) throws ClassNotFoundException {
if (mInvocationHandlerMap.keySet().contains(stub)) {
return mInvocationHandlerMap.get(stub);
}
//从Processor生产的类找到value的返回值(也就是实现类的全路径)
/**
* package com.meiyou.framework.summer.data;
public class ModuleStub {
public static String value = "ModuleBarStub";
public ModuleStub() {
}
}
*/
String simpleName = stub.getSimpleName();//ModuleStub
Class shadowMiddle = Class.forName(ProtocolDataClass.getClassNameForPackage(simpleName));//第一个生成的类com.meiyou.framework.summer.data.ModuleStub
String value = ProtocolDataClass.getValueFromClass(shadowMiddle);//找到注解value:ModuleBarStub
Log.d(TAG,"==>find AnnotationName : " + value+"==>tempClassName:"+shadowMiddle.getName());
/**
*
package com.meiyou.framework.summer.data;
public class ModuleBarStub {
public static String value = "com.meiyou.test.testmodule.TestForServer2";
public ModuleBarStub() {
}
}
*/
Class valueClass = Class.forName(ProtocolDataClass.getClassNameForPackage(value));//第二个生成类:com.meiyou.framework.summer.data.ModuleBarStub;
String targetClazzName = ProtocolDataClass.getValueFromClass(valueClass);//找到对应的业务类全路径:com.meiyou.test.testmodule.TestForServer
if (targetClazzName == null
|| targetClazzName.equals("")
|| targetClazzName.equals("null")) {
throw new RuntimeException("error! targetClazzName null");
}
Log.d(TAG,"==>find Target Class: :" + targetClazzName);
Object obj = null;
final Class clazz = Class.forName(targetClazzName);
//checkMethods
checkMethod(stub,clazz);
for (BeanFactory beanFactory : mBeanFactoryList) {
obj = beanFactory.getBean(clazz);
if (obj != null) {
break;
}
}
if (obj == null) {
obj = defaultGetBean(clazz);
}
if (obj == null) {
throw new RuntimeException("error! obj is null,cannot find action obj in all factory!");
}
final Object action = obj;
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//拦截替换方法;方法名称必须一样
Method realMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
realMethod.setAccessible(true);
return realMethod.invoke(action, args);
}
};
mInvocationHandlerMap.put(stub, handler);
return handler;
}
/**
* 启动方法校验Toast
* for debug
* @param flag
*/
public void enableCheckMethodToast(Context context, boolean flag) {
enableCheckMethodToast = flag;
enableCheckMethod = flag;
mContext = context;
}
/**
* 启动方法校验
*
* @param flag
*/
public void enableCheckMethod(boolean flag){
enableCheckMethod = flag;
}
//提供方法校验;
private void checkMethod(Class stub,Class clazz){
try {
if(!enableCheckMethod)
return;
//checkMethods
List listCheckMethodResult = new ArrayList<>();
Method[] listMethods = stub.getMethods();
Method[] listTargetMethods = clazz.getMethods();
if(listMethods!=null&& listMethods.length>0){
if(listTargetMethods==null || listTargetMethods.length==0){
Log.e(TAG,"You maybe miss all methods Imp,Please check it");
return;
}
for(Method method :listMethods){
String methodName = method.getName();
Class[] methodParams =method.getParameterTypes();
//开始匹配
boolean bFind =false;
for(Method methodTarget:listTargetMethods){
String methodNameTarget = methodTarget.getName();
Class[] methodParamsTarget =methodTarget.getParameterTypes();
//方法名一致;
if(methodName.equals(methodNameTarget)){
//参数一致
if((methodParams==null && methodParamsTarget==null)){
bFind =true;
break;
}
//参数一致
if(methodParams!=null && methodParamsTarget!=null && methodParams.length==methodParamsTarget.length){
int length = methodParams.length;
if(length==0){
bFind =true;
break;
}
if(length>0){
boolean bFetchWrongParams = false;
for(int i=0;i clazz, Object obj) {
mDefaultBeanFactory.put(clazz, obj);
}
//缺省采用 无参数构造bean
private Object defaultGetBean(Class clazz) {
try {
Constructor constructor = clazz.getConstructor();
return constructor.newInstance();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
private static class DefaultBeanFactory implements BeanFactory {
private Map, Object> defaultBeanMap = new HashMap<>();
public DefaultBeanFactory put(Class tClass, Object obj) {
defaultBeanMap.put(tClass, obj);
return this;
}
@Override
public Object getBean(Class clazz) {
return defaultBeanMap.get(clazz);
}
}
public void register(Object object) {
mProtocolEventBus.register(object);
}
public void unRegister(Object object) {
mProtocolEventBus.unRegister(object);
}
public boolean isRegistered(Object object) {
return mProtocolEventBus.isRegistered(object);
}
public void post(Object object) {
mProtocolEventBus.post(object);
}
}
这里我们主要看create方法干了什么:
create里边调用了findHandler(),目的是为了找到这个接口类的具体的业务实现类;只要找到了之后,对这个接口创建动态代理,拦截它调用的方法,然后使用实现类的调用方替换即可。
我们来看findHandler的主要代码:
//ModuleStub
String simpleName = stub.getSimpleName();
//利用第一个生成的类com.meiyou.framework.summer.data.ModuleStub
Class shadowMiddle =
Class.forName(ProtocolDataClass.getClassNameForPackage(simpleName));
//找到注解名字:ForBModule
String value = ProtocolDataClass.getValueFromClass(shadowMiddle);
//利用第二个生成类:com.meiyou.framework.summer.data.ModuleBarStub;
Class valueClass = Class.forName(ProtocolDataClass.getClassNameForPackage(value));
//找到对应的业务类全路径:com.meiyou.test.testmodule.TestForServer
String targetClazzName = ProtocolDataClass.getValueFromClass(valueClass);
...
//对接口创建动态代理进行拦截
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//拦截替换方法;方法名称必须一样
Method realMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
realMethod.setAccessible(true);
return realMethod.invoke(action, args);
}
};
这样就达到了解耦的目的。
关于校验的问题
summer提供了类校验和方法校验;类校验是强制的(找不到实现类会报RuntimeException);而方法校验是可选的,目前提供两个方法,供开发期使用,防止实现类遗漏方法实现;
/**
* 启动方法校验Toast
* for debug
* @param flag
*/
public void enableCheckMethodToast(Context context, boolean flag) {
enableCheckMethodToast = flag;
enableCheckMethod = flag;
mContext = context;
}
/**
* 启动方法校验
*
* @param flag
*/
public void enableCheckMethod(boolean flag){
enableCheckMethod = flag;
}
说明
summer的原作者:总悟君,感谢其对团队的贡献和支持。由于内部项目需求本人会对其做一些改造,但原理没有变动。
总悟君很快会将summer代码开源,请大家静候佳音。
欢迎补充
QQ:452825089
mail:452825089@qq.com
wechat:ice3897315
blog:iceAnson.github.io