Android组件化最佳实践 ARetrofit原理

简介

ARetrofit是一款针对Android组件之间通信的框架,实现组件之间解耦的同时还可以通信。

源码链接:https://github.com/yifei8/ARetrofit
欢迎star、issues、fork

组件化

Android组件化已经不是一个新鲜的概念了,出来了已经有很长一段时间了,大家可以自行Google,可以看到一堆相关的文章。

简单的来说,所谓的组件就是Android Studio中的Module,每一个Module都遵循高内聚的原则,通过ARetrofit来实现无耦合的代码结构,如下图:

每一个Module可单独作为一个project运行,而打包到整体时Module之间的通信通过ARetrofit完成。

ARetrofit原理

讲原理之前,我想先说说为什么要ARetrofit。开发ARetrofit这个项目的思路来源其实是Retrofit,Retrofit是Square公司开发的一款针对Android网络请求的框架,这里不对Retrofit展开来讲。主要是Retrofit框架使用非常多的设计模式,可以说Retrofit这个开源项目将Java的设计模式运用到了极致,当然最终提供的API也是非常简洁的。如此简洁的API,使得我们APP中的网络模块实现变得非常轻松,并且维护起来也很舒服。因此我觉得有必要将Android组件之间的通信也变得轻松,使用者可以优雅的通过简洁的API就可以实现通信,更重要的是维护起来也非常的舒服。

ARetrofit基本原理可以简化为下图所示:

  • 通过注解声明需要通信的Activity/Fragment或者Class
  • 每一个module通过annotationProcessor在编译时生成待注入的RouteInject的实现类和AInterceptorInject的实现类。

这一步在执行app[build]时会输出日志,可以直观的看到,如下所示:

注: AInjecton::Compiler >>> Apt interceptor Processor start... <<<
注: AInjecton::Compiler enclosindClass = null
注: AInjecton::Compiler value = 3
注: AInjecton::Compiler auto generate class = com$$sjtu$$yifei$$eCGVmTMvXG$$AInterceptorInject
注: AInjecton::Compiler add path= 3 and class= LoginInterceptor
....
注: AInjecton::Compiler >>> Apt route Processor start... <<<
注: AInjecton::Compiler enclosindClass = null
注: AInjecton::Compiler value = /login-module/ILoginProviderImpl
注: AInjecton::Compiler enclosindClass = null
注: AInjecton::Compiler value = /login-module/LoginActivity
注: AInjecton::Compiler enclosindClass = null
注: AInjecton::Compiler value = /login-module/Test2Activity
注: AInjecton::Compiler enclosindClass = null
注: AInjecton::Compiler value = /login-module/TestFragment
注: AInjecton::Compiler auto generate class = com$$sjtu$$yifei$$VWpdxWEuUx$$RouteInject
注: AInjecton::Compiler add path= /login-module/TestFragment and class= null
注: AInjecton::Compiler add path= /login-module/LoginActivity and class= null
注: AInjecton::Compiler add path= /login-module/Test2Activity and class= null
注: AInjecton::Compiler add path= /login-module/ILoginProviderImpl and class= null
注: AInjecton::Compiler >>> Apt route Processor succeed <<<
  • 将编译时生成的类注入到RouterRegister中,这个类主要用于维护路由表和拦截器,对应的[build]日志如下:

    TransformPluginLaunch >>> ========== Transform scan start ===========
    TransformPluginLaunch >>> ========== Transform scan end cost 0.238 secs and start inserting ===========
    TransformPluginLaunch >>> Inserting code to jar >> /Users/yifei/as_workspace/ARetrofit/app/build/intermediates/transforms/TransformPluginLaunch/release/8.jar
    TransformPluginLaunch >>> to class >> com/sjtu/yifei/route/RouteRegister.class
    InjectClassVisitor >>> inject to class:
    InjectClassVisitor >>> com/sjtu/yifei/route/RouteRegister{
    InjectClassVisitor >>>        public *** init() {
    InjectClassVisitor >>>            register("com.sjtu.yifei.FBQWNfbTpY.com$$sjtu$$yifei$$FBQWNfbTpY$$RouteInject")
    InjectClassVisitor >>>            register("com.sjtu.yifei.klBxerzbYV.com$$sjtu$$yifei$$klBxerzbYV$$RouteInject")
    InjectClassVisitor >>>            register("com.sjtu.yifei.JmhcMMUhkR.com$$sjtu$$yifei$$JmhcMMUhkR$$RouteInject")
    InjectClassVisitor >>>            register("com.sjtu.yifei.fpyxYyTCRm.com$$sjtu$$yifei$$fpyxYyTCRm$$AInterceptorInject")
    InjectClassVisitor >>>        }
    InjectClassVisitor >>> }
    TransformPluginLaunch >>> ========== Transform insert cost 0.017 secs end ===========
  • Routerfit.register(Class
    service) 这一步主要是通过动态代理模式实现接口中声明的服务。

前面讲的是整体的框架设计思想,便于读者从全局的觉得来理解ARetrofit的框架的架构。接下来,将待大家个个击破上面提到的annotationProcessor、 transform在项目中如何使用,以及动态代理、拦截器功能的实现等细节。

一、annotationProcessor生成代码

annotationProcessor(注解处理器)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。简单的说,在源代码编译阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容。Android Gradle 2.2 及以上版本提供annotationProcessor的插件。
在ARetrofit中annotationProcessor对应的module是auto-complier,在使用annotationProcessor之前首先需要声明好注解。关于注解不太了解或者遗忘的同学可直接参考我之前写的(https://www.jianshu.com/p/ef1146a771b5" target="_blank">Java注解这篇文章,本项目中声明的注解在auto-annotation这个module中,主要有:

  • @Extra 路由参数
  • @Flags intent flags
  • @Go 路由路径key
  • @Interceptor 声明自定义拦截器
  • @RequestCode 路由参数
  • @Route路由
  • @Uri
  • @IMethod 用于标记注册代码将插入到此方法中(transform中使用)
  • @Inject 用于标记需要被注入类,最近都将插入到标记了#com.sjtu.yifei.annotation.IMethod的方法中(transform中使用)

创建自定义的注解处理器,具体使用方法可参考利用注解动态生成代码,本项目中的注解处理器如下所示:

//这是用来注册注解处理器要处理的源代码版本。
@SupportedSourceVersion(SourceVersion.RELEASE_8)
//这个注解用来注册注解处理器要处理的注解类型。有效值为完全限定名(就是带所在包名和路径的类全名
@SupportedAnnotationTypes({ANNOTATION_ROUTE, ANNOTATION_GO})
//来注解这个处理器,可以自动生成配置信息
@AutoService(Processor.class)
public class IProcessor extends AbstractProcessor {

}

生成代码的关键部分在GenerateAInterceptorInjectImpl 和 GenerateRouteInjectImpl中,以下贴出关键代码:

public void generateAInterceptorInjectImpl(String pkName) {
        try {
            String name = pkName.replace(".",DECOLLATOR) + SUFFIX;
            logger.info(String.format("auto generate class = %s", name));
            TypeSpec.Builder builder = TypeSpec.classBuilder(name)
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Inject.class)
                    .addSuperinterface(AInterceptorInject.class);

            ClassName hashMap = ClassName.get("java.util", "HashMap");

            //Map<String, Class<?>>
            TypeName wildcard = WildcardTypeName.subtypeOf(Object.class);
            TypeName classOfAny = ParameterizedTypeName.get(ClassName.get(Class.class), wildcard);
            TypeName string = ClassName.get(Integer.class);

            TypeName map = ParameterizedTypeName.get(ClassName.get(Map.class), string, classOfAny);

            MethodSpec.Builder injectBuilder = MethodSpec.methodBuilder("getAInterceptors")
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .returns(map)
                    .addStatement("$T interceptorMap = new $T<>()", map, hashMap);

            for (Map.Entry<Integer, ClassName> entry : interceptorMap.entrySet()) {
                logger.info("add path= " + entry.getKey() + " and class= " + entry.getValue().simpleName());
                injectBuilder.addStatement("interceptorMap.put($L, $T.class)", entry.getKey(), entry.getValue());
            }
            injectBuilder.addStatement("return interceptorMap");

            builder.addMethod(injectBuilder.build());

            JavaFile javaFile = JavaFile.builder(pkName, builder.build())
                    .build();
            javaFile.writeTo(filer);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

public void generateRouteInjectImpl(String pkName) {
        try {
            String name = pkName.replace(".",DECOLLATOR) + SUFFIX;
            logger.info(String.format("auto generate class = %s", name));
            TypeSpec.Builder builder = TypeSpec.classBuilder(name)
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Inject.class)
                    .addSuperinterface(RouteInject.class);

            ClassName hashMap = ClassName.get("java.util", "HashMap");

            //Map<String, String>
            TypeName wildcard = WildcardTypeName.subtypeOf(Object.class);
            TypeName classOfAny = ParameterizedTypeName.get(ClassName.get(Class.class), wildcard);
            TypeName string = ClassName.get(String.class);

            TypeName map = ParameterizedTypeName.get(ClassName.get(Map.class), string, classOfAny);

            MethodSpec.Builder injectBuilder = MethodSpec.methodBuilder("getRouteMap")
                    .addModifiers(Modifier.PUBLIC)
                    .addAnnotation(Override.class)
                    .returns(map)
                    .addStatement("$T routMap = new $T<>()", map, hashMap);

            for (Map.Entry<String, ClassName> entry : routMap.entrySet()) {
                logger.info("add path= " + entry.getKey() + " and class= " + entry.getValue().enclosingClassName());
                injectBuilder.addStatement("routMap.put($S, $T.class)", entry.getKey(), entry.getValue());
            }
            injectBuilder.addStatement("return routMap");

            builder.addMethod(injectBuilder.build());

            JavaFile javaFile = JavaFile.builder(pkName, builder.build())
                    .build();
            javaFile.writeTo(filer);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

二、Transform

Android Gradle 工具在 1.5.0 版本后提供了 Transfrom API, 允许第三方 Plugin在打包dex文件之前的编译过程中操作 .class 文件。这一部分面向高级Android工程师的,面向字节码编程,普通工程师可不做了解。

写到这里也许有人会有这样一个疑问,既然annotationProcessor这么好用为什么还有Transform面向字节码注入呢?这里需要解释以下,annotationProcessor具有局限性,annotationProcessor只能扫描当前module下的代码,切对于第三方的jar、aar文件都扫描不到。而Transform就没有这样的局限性,在打包dex文件之前的编译过程中操作.class 文件。

关于Transfrom API在Android Studio中如何使用可以参考Transform API?—?a real world example,顺便提供一下(http://gityuan.com/2015/10/24/jvm-bytecode-grammar/)[字节码指令]方便我们读懂ASM
本项目中的Transform插件在auto-injectmodule中,实现源码TransformPluginLaunch如下,贴出关键部分:

/**
 *
 * 标准transform的格式,一般实现transform可以直接拷贝一份重命名即可
 *
 * 两处todo实现自己的字节码增强/优化操作
 */
class TransformPluginLaunch extends Transform implements Plugin<Project> {

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
        super.transform(transformInvocation)

        //todo step1: 先扫描
        transformInvocation.inputs.each {
            TransformInput input ->
                input.jarInputs.each { JarInput jarInput ->
                   ...
                }

                input.directoryInputs.each { DirectoryInput directoryInput ->
                    //处理完输入文件之后,要把输出给下一个任务
                  ...
                }
        }

        //todo step2: ...完成代码注入
        if (InjectInfo.get().injectToClass != null) {
          ...
        }

    }

    /**
     * 扫描jar包
     * @param jarFile
     */
    static void scanJar(File jarFile, File destFile) {

    }

    /**
     * 扫描文件
     * @param file
     */
    static void scanFile(File file, File dest) {
       ...
    }
}

注入代码一般分为两个步骤

  • 第一步:扫描br/>这一部分主要是扫描的内容有:
    注入类和方法的信息,是AutoRegisterContract的实现类和其中@IMethod,@Inject的方法。
    待注入类的和方法信息,是RouteInject 和 AInterceptorInject实现类且被@Inject注解的。
  • 第二步:注入
    以上扫描的结果,将待注入类注入到注入类的过程。这一过程面向ASM操作,可参考字节码指令来读懂以下的关键注入代码:
    class InjectClassVisitor extends ClassVisitor {
    ...
    class InjectMethodAdapter extends MethodVisitor {
    
        InjectMethodAdapter(MethodVisitor mv) {
            super(Opcodes.ASM5, mv)
        }
    
        @Override
        void visitInsn(int opcode) {
            Log.e(TAG, "inject to class:")
            Log.e(TAG, own + "{")
            Log.e(TAG, "       public *** " + InjectInfo.get().injectToMethodName + "() {")
            if (opcode >= Opcodes.IRETURN &amp;&amp; opcode <= Opcodes.RETURN) {
                InjectInfo.get().injectClasses.each { injectClass ->
                    injectClass = injectClass.replace(‘/‘, ‘.‘)
                    Log.e(TAG, "           " + method + "(\"" + injectClass + "\")")
                    mv.visitVarInsn(Opcodes.ALOAD, 0)
                    mv.visitLdcInsn(injectClass)
                    mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, own, method, "(Ljava/lang/String;)V", false)
                }
            }
            Log.e(TAG, "       }")
            Log.e(TAG, "}")
            super.visitInsn(opcode)
        }
    ...
    }
    ...
    }

    三、动态代理

定义:为其它对象提供一种代理以控制对这个对象的访问控制;在某些情况下,客户不想或者不能直接引用另一个对象,这时候代理对象可以在客户端和目标对象之间起到中介的作用。
Routerfit.registerjava动态代理。
本项目相关源码:

public final class Routerfit {
...
      private <T> T create(final Class<T> service) {
        RouterUtil.validateServiceInterface(service);
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, @Nullable Object[] args) throws Throwable {
                // If the method is a method from Object then defer to normal invocation.
                if (method.getDeclaringClass() == Object.class) {
                    return method.invoke(this, args);
                }
                ServiceMethod<Object> serviceMethod = (ServiceMethod<Object>) loadServiceMethod(method, args);
                if (!TextUtils.isEmpty(serviceMethod.uristring)) {
                    Call<T> call = (Call<T>) new ActivityCall(serviceMethod);
                    return call.execute();
                }
                try {
                    if (serviceMethod.clazz == null) {
                        throw new RouteNotFoundException("There is no route match the path \"" + serviceMethod.routerPath + "\"");
                    }
                } catch (RouteNotFoundException e) {
                    Toast.makeText(ActivityLifecycleMonitor.getApp(), e.getMessage(), Toast.LENGTH_SHORT).show();
                    e.printStackTrace();
                }
                if (RouterUtil.isSpecificClass(serviceMethod.clazz, Activity.class)) {
                    Call<T> call = (Call<T>) new ActivityCall(serviceMethod);
                    return call.execute();
                } else if (RouterUtil.isSpecificClass(serviceMethod.clazz, Fragment.class)
                        || RouterUtil.isSpecificClass(serviceMethod.clazz, android.app.Fragment.class)) {
                    Call<T> call = new FragmentCall(serviceMethod);
                    return call.execute();
                } else if (serviceMethod.clazz != null) {
                    Call<T> call = new IProviderCall<>(serviceMethod);
                    return call.execute();
                }

                if (serviceMethod.returnType != null) {
                    if (serviceMethod.returnType == Integer.TYPE) {
                        return -1;
                    } else if (serviceMethod.returnType == Boolean.TYPE) {
                        return false;
                    } else if (serviceMethod.returnType == Long.TYPE) {
                        return 0L;
                    } else if (serviceMethod.returnType == Double.TYPE) {
                        return 0.0d;
                    } else if (serviceMethod.returnType == Float.TYPE) {
                        return 0.0f;
                    } else if (serviceMethod.returnType == Void.TYPE) {
                        return null;
                    } else if (serviceMethod.returnType == Byte.TYPE) {
                        return (byte)0;
                    } else if (serviceMethod.returnType == Short.TYPE) {
                        return (short)0;
                    } else if (serviceMethod.returnType == Character.TYPE) {
                        return null;
                    }
                }
                return null;
            }
        });
    }
...
}

这里ServiceMethod是一个非常重要的类,使用了外观模式,主要用于解析方法中的被注解所有信息并保存起来。

四、拦截器链实现

本项目中的拦截器链设计,使得使用者可以非常优雅的处理业务逻辑。如下:

@Interceptor(priority = 3)
public class LoginInterceptor implements AInterceptor {

    private static final String TAG = "LoginInterceptor";
    @Override
    public void intercept(final Chain chain) {
        //Test2Activity 需要登录
        if ("/login-module/Test2Activity".equalsIgnoreCase(chain.path())) {
            Routerfit.register(RouteService.class).launchLoginActivity(new ActivityCallback() {
                @Override
                public void onActivityResult(int i, Object data) {
                    if (i == Routerfit.RESULT_OK) {//登录成功后继续执行
                        Toast.makeText(ActivityLifecycleMonitor.getTopActivityOrApp(), "登录成功", Toast.LENGTH_LONG).show();
                        chain.proceed();
                    } else {
                        Toast.makeText(ActivityLifecycleMonitor.getTopActivityOrApp(), "登录取消/失败", Toast.LENGTH_LONG).show();
                    }
                }
            });
        } else {
            chain.proceed();
        }
    }

}

这一部分实现的思想是参考了okhttp中的拦截器,这里使用了java设计模式责任链模式,具体实现欢迎阅读源码。

结束语

到此,ARetrofit开源项目基本都以及讲完,在做这个项目的过程中其实遇到了各种各样的问题,其中ASM这块耗费的时间较长,对于当时的我还是个小白。当然收获也是颇多,这也是本人的第一个开源项目,存在的不足之处欢迎读者和用户提出。

原文地址:https://blog.51cto.com/14332859/2404876

时间: 2024-08-02 10:56:20

Android组件化最佳实践 ARetrofit原理的相关文章

Android组件化开发实践

Android项目中代码量达到一定程度,编译将是一件非常痛苦的事情,短则一两分钟,长则达到五六分钟.Android studio推出instant run由于各种缺陷一般情况下是被关闭的--组件化开发可以有效降低代码模块的耦合度,使代码架构更加清晰,同时模块化的编译可以有效减少编译时间,当然总的编译时间是不会减少的,只是App模块化之后开发某个模块时,只需要编译特定模块,可以快速编译调试. 百牛信息技术bainiu.ltd整理发布于博客园 原理 组件化和插件化有些同学有些迷惑,简单来说组件化是在

Android组件化框架设计与实践

在目前移动互联网时代,每个 APP 就是流量入口,与过去 PC Web 浏览器时代不同的是,APP 的体验与迭代速度影响着用户的粘性,这同时也对从事移动开发人员提出更高要求,进而移动端框架也层出不穷. 上图显示的是传统的服务端架构和客户端 App 架构对比.传统的服务端架构中最底下是一个 OS,一般是 Linux,最上面服务端的业务,而中间有非常多的层次可以在架构上,按照我们的意愿搭建中间的各个层次的衔接环节,使得架构具有足够的灵活性和扩展性.但是到了 App 就会面对一个完全不同的现状,App

Gradle自动实现Android组件化模块构建

背景 随着App的不断迭代,业务会变得越来越复杂,业务模块会越来越多,且每个模块的代码也会变得越来越多.为了应对这一场景,我们需要把不同的业务模块划分成一个个组件,在修改业务代码的时候只需要在对应模块修改就可以了.通过高内聚,低耦合的业务模块来保证工程的健壮性和稳定性.现在问题来了,当组件的数量变得越来多的时候,我们如何管理业务组件呢? 原创声明: 该文章为原创文章,未经博主同意严禁转载. 为什么我们要用Gradle管理组件呢? 先来看看Android组件化需要实现的目标.(什么是组件化构建?)

Android企业级应用程序开发完整训练:精通Android商业级开发最佳实践的24堂课

从企业级商业实战的角度入手,24小时内通过23个动手实战案例,循序渐进的对Android商业级别的应用程序开发要点各个击破,依托于在多年的Android(6款完整的硬件产品和超过20款应用软件)开发和企业级培训经验(超过150期的次Android的企业内训和公开课),旨在在实务的基础之上帮助你完成任何复杂程序的高质量Android应用程序开发,让Android开发跟上想象的速度.最后,通过ActivityManagerService揭秘Android应用程序一切行为背后的核心根源,让你从此开发应

Android组件化

附:Android组件化和插件化开发 App组件化与业务拆分那些事 Android项目架构之业务组件化 Android组件化核心之路由实现

Android组件化方案

1为什么要项目组件化 2如何组件化 3组件化实施流程 1组件模式和集成模式的转换 2组件之间AndroidManifest合并问题 3全局Context的获取及组件数据初始化 4library依赖问题 5组件之间调用和通信 6组件之间资源名冲突 4组件化项目的工程类型 1app壳工程 2功能组件和Common组件 2业务组件和Main组件 5组件化项目的混淆方案 6工程的buildgradle和gradleproperties文件 1组件化工程的buildgradle文件 2组件化工程的grad

Android组件化之终极方案

Android组件化项目地址:Android组件化项目AndroidModulePattern Fragment或View如何支持组件化 如何管理组件 Fragment或View如何支持组件化 距离 Android组件化方案 发布已经半年有余,虽说这个方案已经能够解决一些项目的需求,但是依然不够完美.很多开发者也在博客和GitHub中留言甚至发邮件问我,Fragment怎么办? 目前市面上APP的风格还是类似于微信界面的比较多,好几个Fragment摆在主界面中,然后点击NavigationBa

Android组件化探索与实践

什么是组件化 不用去纠结组件和模块语义上的区别,如果模块间不存在强依赖且模块间可以任意组合,我们就说这些模块是组件化的. 组件化的好处 实现组件化本身就是一个解耦的过程,同时也在不断对你的项目代码进行提炼.对于已有的老项目,实现组件化刚开始是很难受的,但是一旦组件的框架初步完成,对于后期开发效率是会有很大提升的. 组件间间相互独立,可以减少团队间的沟通成本. 每一个组件的代码量不会特别巨大,团队的新人也能快速接手项目. 如何实现组件化 这是本文所主要讲述的内容,本篇文章同时适用于新老项目,文中会

Android 业务组件化开发实践

组件化并不是新话题,其实很早很早以前我们开始为项目解耦的时候就讨论过的.但那时候我们说的是功能组件化.比如很多公司都常见的,网络请求模块.登录注册模块单独拿出来,交给一个团队开发,而在用的时候只需要接入对应模块的功能就可以了. 百牛信息技术bainiu.ltd整理发布于博客园 今天我们来讨论一下业务组件化,拿出手机,打开淘宝或者大众点评来看看,里面的美食电影酒店外卖就是一个一个的业务.如果我们在一个项目里面去写的时候,总会出现或多或少的代码耦合,最典型的有时为了赶上线时间而先复制粘贴一段类似的代