Android 模块间的代码级解耦实现

Android · sword · 于 发布 · 2142 次阅读
96

Android模块间的代码级解耦实现

前言

自从项目分解模块之后,由于之前重构过程比较仓促,花了比较少的精力来进行模块之间的解耦,沿用了比较传统的接口暴露方式,它的特点是需要在主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<? extends TypeElement> 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<String, ElementHolder> 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<String, ElementHolder> 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<String, ElementHolder> collectClassInfo(RoundEnvironment roundEnv,
                                                        Class<? extends Annotation> clazz, ElementKind kind) {
        System.out.println("collectClassInfo for" + clazz.getSimpleName());
        Map<String, ElementHolder> 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<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<String>();
        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<String, ElementHolder> shadowMap = collectClassInfo(roundEnv, ProtocolShadow.class, ElementKind.INTERFACE);

     Map<String, ElementHolder> 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<BeanFactory> mBeanFactoryList = new ArrayList<>();
    private Map<Class<?>, InvocationHandler> mInvocationHandlerMap = new HashMap<>();
    private Map<Class<?>, 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 <T>  clazz
     * @return obj clazz
     */
    public <T> T create(Class<T> 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 <T> InvocationHandler findHandler(Class<T> 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<String> 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<length;i++){
                                        if(!methodParams[i].getName().equals(methodParamsTarget[i].getName())){
                                            bFetchWrongParams = true;
                                            break;
                                        }
                                    }
                                    if(!bFetchWrongParams){
                                        bFind =true;
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    if(!bFind){
                        listCheckMethodResult.add(methodName);
                    }
                }
                StringBuilder stringBuilder = new StringBuilder();
                stringBuilder.append("You maybe miss these methods Imp:\n\n");
                for(String methodName:listCheckMethodResult){
                    stringBuilder.append(methodName).append("\n");
                }
                stringBuilder.append("\nPlease check it!\n");
                if(enableCheckMethodToast && mContext!=null){
                    Toast.makeText(mContext,stringBuilder.toString(),Toast.LENGTH_SHORT).show();
                }else{
                    Log.e(TAG,stringBuilder.toString());
                }
            }
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }

    /**
     * 初始化解释器,如果传入 beanFactory 怎具有更强大灵活地配置响应者的能力
     * 如果要定制  则要在使用该bean之前 add进来
     *
     * @param beanFactory beanFactory
     */
    public void addFactory(BeanFactory beanFactory) {
        if (!mBeanFactoryList.contains(beanFactory)) this.mBeanFactoryList.add(beanFactory);
    }

    /**
     * 自己定制 bean 而不是要解释器new出来
     * 如果要定制 则要在使用该bean之前 add进来
     *
     * @param clazz class
     * @param obj   object
     */
    public void addBean(Class<?> 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<Class<?>, Object> defaultBeanMap = new HashMap<>();

        public <T> DefaultBeanFactory put(Class<T> tClass, Object obj) {
            defaultBeanMap.put(tClass, obj);
            return this;
        }

        @Override
        public <T> Object getBean(Class<T> 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:http://iceAnson.github.io

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册