Android 组件化最佳实践 ARetrofit 原理

.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插件在AInject中,实现源码TransformPluginLaunch如下,贴出关键部分:

/**

  • 标准transform的格式,一般实现transform可以直接拷贝一份重命名即可

  • 两处todo实现自己的字节码增强/优化操作

*/

class TransformPluginLaunch extends Transform implements Plugin {

@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) {

}

}

( 滑 动 查 看 ) (滑动查看) (滑动查看)

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

  • 第一步:扫描

这一部分主要是扫描的内容有:

注入类和方法的信息,是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 && 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 create(final Class 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 serviceMethod = (ServiceMethod) loadServiceMethod(method, args);

if (!TextUtils.isEmpty(serviceMethod.uristring)) {

Call call = (Call) 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 call = (Call) new ActivityCall(serviceMethod);

return call.execute();

} else if (RouterUtil.isSpecificClass(serviceMethod.clazz, Fragment.class)

|| RouterUtil.isSpecificClass(serviceMethod.clazz, android.app.Fragment.class)) {

Call call = new FragmentCall(serviceMethod);

return call.execute();

} else if (serviceMethod.clazz != null) {

Call 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 的核心原理有了很清晰的理解.简单来说 ARetrofit 通过 annotationProcessor 在编译时获取路由相关内容,通过 ASM 实现了可跨模块获取对象,最终通过动态代理实现面向切面编程(AOP)。

ARetrofit 相对于其他同类型的路由框架来说,其优点是提供了更加简洁的 API,其中高阶用法对开发者提供了更加灵活扩展方式,开发者还可以结合 RxJava 完成复杂的业务场景。具体可以参考 ARetrofit 的基本用法,以及 Issues。

如果还想了解更多Android 相关的更多知识点,可以点进我的大家可以加入Android 粉丝裙:872206502联系管理员进行免费获取资料,里面记录了许多的Android 知识点。最后还请大家点点赞支持下!!!

QQ扫码快速通道

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。

选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!

面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

你会不会,也就是岗位匹配度。

选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!

面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。

金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。

附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-iPpZoWre-1712162903349)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值