Hello,各位伟大的程序猿们,提前给各位拜年了,祝大家在新的一年收获满满、挣钱多多、猪年吉祥。

开源ARetrofit大约半年左右的时间,没有任何推广和介绍,今天一看也有160+的stars了,这里非常感谢大家的支持。趁着年关降至,也想写一点东西来介绍一下这个框架。无论是ARetrofit的用户也好,还是对源码感兴趣的同学也好,希望能从这篇文章中有所收获。

简介

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

源码链接

欢迎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基本原理可以简化为下图所示:

  1. 通过注解声明需要通信的Activity/Fragment或者Class
  2. 每一个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 <<<
  1. 将编译时生成的类注入到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 ===========
  1. Routerfit.register(Class service) 这一步主要是通过动态代理模式实现接口中声明的服务。

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

一、annotationProcessor生成代码

annotationProcessor(注解处理器)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。简单的说,在源代码编译阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容。Android Gradle 2.2 及以上版本提供annotationProcessor的插件。
在ARetrofit中annotationProcessor对应的module是auto-complier,在使用annotationProcessor之前首先需要声明好注解。关于注解不太了解或者遗忘的同学可直接参考我之前写的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 {}

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

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,顺便提供一下字节码指令方便我们读懂ASM。

本项目中的Transform插件在auto-injectmodule中,实现源码TransformPluginLaunch如下,贴出关键部分:

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

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

  • 第一步:扫描
    这一部分主要是扫描的内容有:
    注入类和方法的信息,是AutoRegisterContract的实现类和其中@IMethod,@Inject的方法。
    待注入类的和方法信息,是RouteInject 和 AInterceptorInject实现类且被@Inject注解的。
  • 第二步:注入
    以上扫描的结果,将待注入类注入到注入类的过程。这一过程面向ASM操作,可参考字节码指令来读懂以下的关键注入代码:
class InjectClassVisitor extends ClassVisitor {
...class InjectMethodAdapter extends MethodVisitor {InjectMethodAdapter(MethodVisitor mv) {super(Opcodes.ASM5, mv)}@Overridevoid visitInsn(int opcode) {Log.e(TAG, "inject to class:")Log.e(TAG, own + "{")Log.e(TAG, "       public *** " + InjectInfo.get().injectToMethodName + "() {")if (opcode >= Opcodes.IRETURN && 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.register(Class service) 这里就是采用动态代理的模式,使得ARetrofit的API非常简洁,使用者可以优雅定义出路由接口。关于动态代理的学习难度相对来说还比较小,想了解的同学可以参考这篇文章java动态代理。

本项目相关源码:

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() {@Overridepublic 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";@Overridepublic void intercept(final Chain chain) {//Test2Activity 需要登录if ("/login-module/Test2Activity".equalsIgnoreCase(chain.path())) {Routerfit.register(RouteService.class).launchLoginActivity(new ActivityCallback() {@Overridepublic 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这块耗费的时间较长,对于当时的我还是个小白。当然收获也是颇多,这也是本人的第一个开源项目,存在的不足之处欢迎读者和用户提出,可以直接在qq群里提出。

Android 组件化最佳实践 ARetrofit原理相关推荐

  1. Android组件化最佳实践 ARetrofit原理

    来源丨安卓巴士Android开发者门户 Hello,各位伟大的程序猿们,提前给各位拜年了,祝大家在新的一年收获满满.挣钱多多.猪年吉祥. 开源ARetrofit大约半年左右的时间,没有任何推广和介绍, ...

  2. Android组件化开发实践(九):自定义Gradle插件

    本文紧接着前一章Android组件化开发实践(八):组件生命周期如何实现自动注册管理,主要讲解怎么通过自定义插件来实现组件生命周期的自动注册管理. 1. 采用groovy创建插件 新建一个Java L ...

  3. Android组件化架构实践,成功拿下大厂offer

    缘起 经过近十年的发展,Android技术优化日新月异,如今Android 10.0 已经发布,Android系统性能也已经非常流畅,可以在体验上完全媲美iOS.到了各大厂商手里,改源码.自定义系统, ...

  4. Android组件化架构实践,全套教学资料

    缘起 经过近十年的发展,Android技术优化日新月异,如今Android 10.0 已经发布,Android系统性能也已经非常流畅,可以在体验上完全媲美iOS.到了各大厂商手里,改源码.自定义系统, ...

  5. Android组件化架构实践,字节跳动学习笔记

    前言 2021全年从开头到结尾似乎就没多少好消息.这里我说的是整个互联网行业,并没有单单挑出某个公司或者某个细分领域.而对于广大Android开发者来说,找工作似乎也变得越来越难了. 2021年1月, ...

  6. 基于 MVP 的 Android 组件化开发框架实践

    一.背景 当我们的项目变得越来越大,代码变得越来越臃肿,耦合会越来越多,编译速度越来越慢,开发效率也会变得越来越低,怎么办?这个时候我们就需要对旧项目进行重构,即是模块的拆分,官方的说法就是组件化. ...

  7. 基于 MVP 的 Android 组件化开发框架实践 1

    一.背景 当我们的项目变得越来越大,代码变得越来越臃肿,耦合会越来越多,编译速度越来越慢,开发效率也会变得越来越低,怎么办?这个时候我们就需要对旧项目进行重构,即是模块的拆分,官方的说法就是组件化. ...

  8. android 组件生命周期,Android组件化开发实践(五):组件生命周期管理

    每个Android应用启动时,都会先创建一个Application.通常在Application里我们会做一些应用初始化的操作,常见的有第三方SDK初始化.在应用组件化之后,组件与壳工程是隔离开来的, ...

  9. android组件化数据生命周期,Android组件化开发实践(五):组件生命周期管理

    每个Android应用启动时,都会先创建一个Application.通常在Application里我们会做一些应用初始化的操作,常见的有第三方SDK初始化.在应用组件化之后,组件与壳工程是隔离开来的, ...

最新文章

  1. libmemcached 1.0.11 发布
  2. Java 爬虫--类似Python的requests库--HttpClient, HttpAsyncClient--Maven
  3. python装饰器作用-Python 装饰器的作用
  4. MyEclipse6.0下代码提示(alt+/)无法使用的解决方法
  5. 老李推荐: 第8章4节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-启动AndroidDebugBridge 2...
  6. SAP Spartacus 服务器端渲染的依赖注入之 ProductPageEventBuilder
  7. k歌的录音伴奏合成技术如何实现_K歌神器,用唱吧麦克风攀登天籁高峰
  8. pythonlocust使用方法_使用python的locust库进行性能测试
  9. bat文件打开一闪就没了_window文件夹目录树的创建
  10. linux用ping命令测试网速,linux下面使用命令测试网速
  11. 2016级算法期末模拟练习赛-B.AlvinZH的青春记忆I
  12. Android Jetpack 之 ViewModel
  13. 开源合规处理方法_经济高效的开源软件许可合规模型
  14. java模拟数据库压测_Jmeter压测工具使用总结
  15. 手机安装python3.5_CentOS 7安装Python3.5
  16. SpringBoot-@ControllerAdvice 拦截异常并统一处理
  17. 学校新机房装系统——联想机房网络同传
  18. Python 符号运算
  19. python——operator详解
  20. 深度学习炼丹术 —— Taoye不讲码德,又水文了,居然写感知器这么简单的内容

热门文章

  1. 初中教师计算机培训方案,初中教师信息技术培训与考核校本培训的实施方案
  2. 自动跟随机器人教程(四)软件部分 树莓派+电脑 控制小车移动
  3. 解决GPU driver is too old
  4. Nginx代理FTP服务器
  5. 鸿蒙系统会和苹果系统一样好用吗,华为的鸿蒙系统会像苹果系统那样不需要杀毒和清理垃圾吗?...
  6. adb从手机拷贝文件到电脑
  7. ES中match和term差别对比
  8. lcx用法之心得总结
  9. setoolkit克隆网站获取用户信息
  10. [春秋云镜]CVE-2022-2073