ARouter原理解析之仿ARouter自定义路由框架

“/dynamic/activity”, // Path

“dynamic”, // Group, 尽量保持和 path 的第一段相同

0, // 优先级,暂未使用

0 // Extra,用于给页面打标

)

);

}

});

ARouter详细API


// 构建标准的路由请求,并指定分组

ARouter.getInstance().build(“/home/main”, “ap”).navigation();

// 构建标准的路由请求,通过Uri直接解析

Uri uri;

ARouter.getInstance().build(uri).navigation();

// 构建标准的路由请求,startActivityForResult

// navigation的第一个参数必须是Activity,第二个参数则是RequestCode

ARouter.getInstance().build(“/home/main”, “ap”).navigation(this, 5);

// 指定Flag

ARouter.getInstance()

.build(“/home/main”)

.withFlags();

.navigation();

// 获取Fragment

Fragment fragment = (Fragment) ARouter.getInstance().build(“/test/fragment”).navigation();

// 对象传递

ARouter.getInstance()

.withObject(“key”, new TestObj(“Jack”, “Rose”))

.navigation();

// 使用绿色通道(跳过所有的拦截器)

ARouter.getInstance().build(“/home/main”).greenChannel().navigation();

原理探索


  • ARouter.init 时,通过获取/data/app/包名/base.apk来筛选出ARouter生成的类,如下图。

  • 对于Activity类型,跳转ARouter.getInstance().build("/login/login").navigation();,最终执行的是,如下:

**

  • Start activity

  • @see ActivityCompat

*/

private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {

if (requestCode >= 0) { // Need start for result

if (currentContext instanceof Activity) {//启动context 为Activity

ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());

} else {

// 启动context 为Application 时,不支持requestCode

logger.warning(Consts.TAG, “Must use [navigation(activity, …)] to support [startActivityForResult]”);

}

} else {//启动context 为Application

ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());

}

if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.

((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());

}

if (null != callback) { // Navigation over.

callback.onArrival(postcard);

}

}

  • 两个无关的module 如何跳转的呢?我们发现最终执行startActivity时,所用的context为Application,思路是这样的,子module启动另外无关子module时,将执行权,交还给主进程/主程序去处理

  • 打开生成路由文档,AROUTER_GENERATE_DOC=“enable”,会生成arouter-map-of-xx.json和3个java文件

// 更新 build.gradle, 添加参数 AROUTER_GENERATE_DOC = enable

// 生成的文档路径 : build/generated/ap_generated_sources/(debug or release)/com/alibaba/android/arouter/docs/arouter-map-of-${moduleName}.json

android {

defaultConfig {

javaCompileOptions {

annotationProcessorOptions {

arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: “enable”]

}

}

}

}

//ARouter映射关系如何生成?Generated出三个文件

//ARouter G r o u p Group Grouplogin

//ARouter P r o v i d e r s Providers Providersloginplugin

//ARouter R o o t Root Rootloginplugin

atlas.put(“/login/login”, RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class, “/login/login”, “login”, new java.util.HashMap<String, Integer>(){{put(“password”, 8); put(“username”, 8); }}, -1, -2147483648));

//map 存映射关系

//static Map<String, RouteMeta> routes = new HashMap<>();

  • 以上三个文件是如何生成的呢?APT是Annotation Processing Tool的简称,即注解处理工具,apt是在编译期对代码中指定的注解进行解析,然后做一些其他处理(如通过javapoet生成新的Java文件)ARouter使用了两个库auto-service javapoet,来实现从注解到代码的注入,其中auto-service为注解处理器的库,javapoet为代码生成器

通过例子了解APT


首先我们了解一下元注解,meta-annotation(元注解)

  • @Target

TYPE, // 类、接口、枚举类

FIELD, // 成员变量(包括:枚举常量)

METHOD, // 成员方法

PARAMETER, // 方法参

CONSTRUCTOR, // 构造方法

LOCAL_VARIABLE, // 局部变量

ANNOTATION_TYPE, // 注解类

PACKAGE, // 可用于修饰:包

TYPE_PARAMETER, // 类型参数,JDK 1.8 新增

TYPE_USE // 使用类型的任何地方,JDK 1.8 新增

  • @Retention

SOURCE, 只在本编译单元的编译过程中保留,并不写入Class文件中。

CLASS, 在编译的过程中保留并且会写入Class文件中,但是JVM在加载类的时候不需要将其加载为运行时可见的(反射可见)的注解==是JVM在加载类时反射不可见。

RUNTIME 在编译过程中保留,会写入Class文件,并且JVM加载类的时候也会将其加载为反射可见的注解。

  • @Documented 注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息.

  • @Inherited 注解的作用是:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)

  • 通过元注解我们定义自己的注解

  • AutoService 注解处理器

​ 注解处理器是一个在javac中的,用来编译时扫描和处理的注解的工具。你可以为特定的注解,注册你自己的注解处理器。到这里,我假设你已经知道什么是注解,并且知道怎么申明的一个注解类型。

一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。

  • 虚处理器AbstractProcessor

  • init(ProcessingEnvironment env): 【核心】

每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements,TypesFiler

  • process(Set< ? extends TypeElement> annotations, RoundEnvironment env):【核心】

这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件

  • getSupportedAnnotationTypes()

这里你必须指定,这个注解处理器是注册给哪个注解的

  • getSupportedSourceVersion()

用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()

  • APT 所用的代码生成器:JavaPoet is a Java API for generating .java source files.(JavaPoet 是一个java api ,为了生成 .java源文件的)

  • 官方helloworld

MethodSpec main = MethodSpec.methodBuilder(“main”)

.addModifiers(Modifier.PUBLIC, Modifier.STATIC)

.returns(void.class)

.addParameter(String[].class, “args”)

.addStatement(“ T . o u t . p r i n t l n ( T.out.println( T.out.println(S)”, System.class, “Hello, JavaPoet!”)

.build();

TypeSpec helloWorld = TypeSpec.classBuilder(“HelloWorld”)

.addModifiers(Modifier.PUBLIC, Modifier.FINAL)

.addMethod(main)

.build();

JavaFile javaFile = JavaFile.builder(“com.example.helloworld”, helloWorld)

.build();

javaFile.writeTo(System.out);

  • 通过以上可生成以下java 文件

package com.example.helloworld;

public final class HelloWorld {

public static void main(String[] args) {

System.out.println(“Hello, JavaPoet!”);

}

}

  • JavaPoet 主要api
  • JavaFile 用于构造输出包含一个顶级类的Java文件

  • TypeSpec 生成类,接口,或者枚举

  • MethodSpec 生成构造函数或方法

  • FieldSpec 生成成员变量或字段

  • ParameterSpec 用来创建参数

  • AnnotationSpec 用来创建注解

  • JavaPoet 主要占位符
  • $L(for Literals) 执行结构的字符或常见类型,或TypeSpec, $S(for Strings) 字符, $T(for Types) 类, $N(for Names) 方法 等标识符

L > L> L>S

//1.Pass an argument value for each placeholder in the format string to CodeBlock.add(). In each example, we generate code to say “I ate 3 tacos”

CodeBlock.builder().add(“I ate $L $L”, 3, “tacos”)

//2.When generating the code above, we pass the hexDigit() method as an argument to the byteToHex() method using $N:

MethodSpec byteToHex = MethodSpec.methodBuilder(“byteToHex”)

.addParameter(int.class, “b”)

.returns(String.class)

.addStatement(“char[] result = new char[2]”)

.addStatement(“result[0] = $N((b >>> 4) & 0xf)”, hexDigit)

.addStatement(“result[1] = $N(b & 0xf)”, hexDigit)

.addStatement(“return new String(result)”)

.build();

//=======================

public String byteToHex(int b) {

char[] result = new char[2];

result[0] = hexDigit((b >>> 4) & 0xf);

result[1] = hexDigit(b & 0xf);

return new String(result);

}

//$T for Types

//We Java programmers love our types: they make our code easier to understand. And JavaPoet is on board. It has rich built-in support for types, including automatic generation of import statements. Just use $T to reference types:

.addStatement(“return new $T()”, Date.class)== return new Date();

实战-自定义简易版路由-CRouter


  • 新建name-annotation javaLib,定义CRoute注解

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.CLASS)

public @interface CRoute {

String path();

}

  • 新建name-compiler javaLib

dependencies {

implementation project(path: ‘:TestRouter-annotation’)

annotationProcessor ‘com.google.auto.service:auto-service:1.0-rc7’

compileOnly ‘com.google.auto.service:auto-service-annotations:1.0-rc7’

implementation ‘com.squareup:javapoet:1.8.0’

}

2.@AutoService(Processor.class)

public class TestRouteProcessor extends AbstractProcessor {

@Override

public synchronized void init(ProcessingEnvironment processingEnvironment) {

super.init(processingEnvironment);

//dosomething

}

@Override

public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

//dosomething

}

}

  • 业务module执行顺序如下
  1. annotationProcessor project(‘:TestRouter-compiler’)

implementation project(‘:TestRouter-annotation’)

2.添加注解@CRoute(path = “/csetting/csetting”)

3.编译运行

4.业务module apt 生成的java 文件,如下:

public final class C c s e t t i n g C csettingC csettingCcsettingHelloWorld {

public static String holder = “/csetting/csetting:com.cnn.settingplugin.SettingsActivity”;

public static void main(String[] args) {

System.out.println(“Hello, JavaPoet!”);

}

}

  • 参考ARouter-init 方法,写出我们CRouter-init

/**

  • Init, it must be call before used router.

*/

public static void init(Application application) {

if (!hasInit) {

CRouter.application=application;

hasInit=true;

try {

getFileNameByPackageName(application, ROUTE_ROOT_PAKCAGE);

} catch (PackageManager.NameNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

  • 利用反射获取到注解对应映射关系,并参考ARouter存入HashMap

  • 通过隐式启动Activity模拟跳转

  • 到此我们模拟出简易版本的ARouter,完整自定义CRouter

/**

  • Created by caining on 7/29/21 16:09

  • E-Mail Address:cainingning@360.cn

*/

public class CRouter {

private volatile static CRouter instance = null;

private volatile static boolean hasInit = false;

private static Application application;

public static final String ROUTE_ROOT_PAKCAGE = “com.cnn.crouter”;

private static Map<String ,String> mapHolder = new HashMap<>();

/**

  • Init, it must be call before used router.

*/

public static void init(Application application) {

if (!hasInit) {

CRouter.application=application;

hasInit=true;

try {

getFileNameByPackageName(application, ROUTE_ROOT_PAKCAGE);

} catch (PackageManager.NameNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

/**

  • Get instance of router. A

  • All feature U use, will be starts here.

*/

public static CRouter getInstance() {

if (!hasInit) {

throw new InitException(“ARouter::Init::Invoke init(context) first!”);

} else {

if (instance == null) {

synchronized (CRouter.class) {

if (instance == null) {

instance = new CRouter();

}

}

}

return instance;

}

}

public void navigation(String path) {

startActivity(path);

}

private void startActivity(String path) {

String classPath

= mapHolder.get(path);

if (!TextUtils.isEmpty(classPath)) {

Intent intent = new Intent();

intent.setClassName(application, classPath);//设置包路径

ActivityCompat.startActivity(application, intent, null);

}else {

Toast.makeText(application, “路径空啦”, Toast.LENGTH_SHORT).show();

}

}

/**

  • 通过指定包名,扫描包下面包含的所有的ClassName

  • @param context U know

  • @param packageName 包名

  • @return 所有class的集合

*/

private static Set getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {

final Set classNames = new HashSet<>();

List paths = getSourcePaths(context);

final CountDownLatch parserCtl = new CountDownLatch(paths.size());

for (final String path : paths) {

DefaultPoolExecutor.getInstance().execute(new Runnable() {

@Override

public void run() {

DexFile dexfile = null;

try {

if (path.endsWith(“EXTRACTED_SUFFIX”)) {

//NOT use new DexFile(path), because it will throw “permission error in /data/dalvik-cache”

dexfile = DexFile.loadDex(path, path + “.tmp”, 0);

} else {

dexfile = new DexFile(path);

}

Enumeration dexEntries = dexfile.entries();

while (dexEntries.hasMoreElements()) {

String className = dexEntries.nextElement();

if (className.startsWith(packageName)) {

classNames.add(className);

try {

Class clazz = Class.forName(className);

Object obj = clazz.newInstance();

Field field03 = clazz.getDeclaredField(“holder”); // 获取属性为id的字段

String value= (String) field03.get(obj);

String[] split = value.split(“:”);

if (split!=null&&split.length==2) {

mapHolder.put(split[0],split[1]);

}

Log.i(“test–>”,mapHolder.toString());

} catch (ClassNotFoundException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (InstantiationException e) {

e.printStackTrace();

} catch (SecurityException e) {

e.printStackTrace();

} catch (NoSuchFieldException e) {

e.printStackTrace();

} catch (IllegalArgumentException e) {

e.printStackTrace();

}

}

}

} catch (Throwable ignore) {

Log.e(“ARouter”, “Scan map file in dex files made error.”, ignore);

} finally {

if (null != dexfile) {

try {

dexfile.close();

} catch (Throwable ignore) {

}

}

parserCtl.countDown();

}

}

});

}

parserCtl.await();

return classNames;

}

private static List getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

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

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

img

img

img

img

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

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

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

总结

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于Flutter的学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

跨平台开发:Flutter.png

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

技术停滞不前!**

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

[外链图片转存中…(img-4qhxpTPs-1712252427463)]

[外链图片转存中…(img-DeLwn2P9-1712252427463)]

[外链图片转存中…(img-gELhaOPM-1712252427463)]

[外链图片转存中…(img-CP2TbgfV-1712252427464)]

[外链图片转存中…(img-NqoMzxrQ-1712252427464)]

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

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

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

总结

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于Flutter的学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

[外链图片转存中…(img-VPrux4J8-1712252427464)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值