Android 组件化路由框架设计(仿Arouter)

Android 组件化路由框架设计(仿Arouter)

经验文章nimo972025-02-24 15:56:1412A+A-


前言

在组件化开发中一个必须要面对的问题就是组件间页面跳转,实现的方法有很多,简单的可以通过反射获取,但是比较耗费性能,也可以通过隐式跳转,但是随着页面的增多,过滤条件会随之增多,后期维护麻烦。那还有什么方法呢,没错,就是接下来要介绍的Arouter路由框架,该框架是阿里巴巴开源项目,大厂出品,必属精品。使用过Arouter得同学都知道Arouter是通过给每个页面添加@Route注解然后调用一定的方法实现跳转的,而Arouter的核心就是这个注解。
??这里要介绍一个概念,APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,它用来在编译时扫描和处理注解,注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。简单来说就是在编译期,通过注解生成.java文件。
??Arouter的路由表就是在该工具下在编译期生成的,说简单了,就是利用注解在编译期生成了一些java文件,我们在这些新的java文件中将所有被注解的页面添加进了路由表中。



设计思路

arouter-compiler:注解编译处理器,引入“arouter-annotation”,在编译器把注解标注的相关目标类生成映射文件,包含路由框架所使用的全部注解,及其相关类
arouter-api:实现路由控制



实现效果



步骤

  • 新建module java library(router_compiler)(因为在主 Module 中无法找到 AbstractProcessor 类)





  • 新建module android library(router_api),步骤如上,但是要注意选择Android library。
  • 在route-compiler的gradle文件中导入依赖和jdk版本支持
apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    api 'com.squareup:javapoet:1.11.1'
    api 'org.apache.commons:commons-collections4:4.4'
    api 'org.apache.commons:commons-lang3:3.5'
}

sourceCompatibility = "8"
targetCompatibility = "8"
  • 在app工程gradle文件中添加jdk版本支持
  • compileOptions {
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
    • 在route_api和app模块等其他组件化模块的gradle文件中导入route_compiler模块
    annotationProcessor project(':router_compiler')
    api project(':router_compiler')
    • 在每个module模块中的gradle文件中添加下列语句用来获取每个module的包名
    javaCompileOptions {
        annotationProcessorOptions {
            arguments = [ROUTER_MODULE_NAME: project.getName()]
        }
    }
    • 在router_compiler模块中创建RouteProcessor类并继承自AbstractProcessor
    • 在router_compiler模块中的main文件夹下创建文件夹resources/META-INF/services,然后创建javax.annotation.processing.Processor文件,并添加下列语句
    com.nsyw.routerdemo.router_compiler.RouteProcessor
    • 在router-compiler根目录下新建注解类Route
    public @interface Route {
    
        /**
         * Path of route
         */
        String path();
    
    }
    • 创建接口IRoute,自动生成的java文件都要继承自该接口
    public interface IRoute {
    
        /**
         *
         * @param routes 模块下的路由集合
         */
        void loadInto(Map routes);
    }
    • RouteProcessor.java
    /**
     * @SupportedAnnotationTypes表示支持的注解类型
     */
    @SupportedAnnotationTypes("com.nsyw.routerdemo.router_compiler.annotation.Route")
    public class RouteProcessor extends AbstractProcessor {
        
        ......
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            ......
        }
    
        @Override
        public boolean process(Set set, RoundEnvironment roundEnvironment) {
           
                ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
                        ClassName.get(Map.class),
                        ClassName.get(String.class),
                        ClassName.get(RouteMeta.class)
                );
    
                ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "routes").build();
    
                /*
                  methodBuilder 方法名
                  addAnnotation 方法添加注解
                  addModifiers  方法访问限制类型
                  addParameter  添加参数
                 */
                MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                        .addAnnotation(Override.class)
                        .addModifiers(PUBLIC)
                        .addParameter(groupParamSpec);
    
                ClassName routeMetaCn = ClassName.get(RouteMeta.class);
                ClassName routeTypeCn = ClassName.get(RouteType.class);
    
                //遍历@Route注解的所有Activity
                for (Element element : routeElements) {
                    TypeMirror tm = element.asType();
                    //获取注解
                    Route route = element.getAnnotation(Route.class);
                    RouteMeta routeMeta = null;
                    if (types.isSubtype(tm, type_Activity)) {
                        routeMeta = new RouteMeta(route.path(), RouteType.ACTIVITY);
                    }
    
                    //获取被注解的类的类名
                    ClassName className = ClassName.get((TypeElement) element);
    
                    /*
                      方法内的添加路由语句
                      routes.put(routeMeta.getPath(),RouteMeta.build(routeMeta.getPath(),RouteType.ACTIVITY,className.class))
                     */
                    loadIntoMethodOfGroupBuilder.addStatement(
                            "routes.put($S,$T.build($S,$T." + routeMeta.getRouteType() + ", $T.class))",
                            routeMeta.getPath(),
                            routeMetaCn,
                            routeMeta.getPath(),
                            routeTypeCn,
                            className);
                }
                /*
                  构建java文件
                 */
                try {
                    JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                            TypeSpec.classBuilder(NAME_OF_ROUTE + moduleName)
                                    .addJavadoc(WARNING_TIPS)
                                    .addSuperinterface(ClassName.get(mElementsUtil.getTypeElement(IROUTE_LOAD)))
                                    .addModifiers(PUBLIC)
                                    .addMethod(loadIntoMethodOfGroupBuilder.build())
                                    .build()
                    ).build().writeTo(mFiler);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return true;
            }
            return false;
        }
    }
    • 在router_api模块下创建Router
    public class Router {
    
        private static volatile Router mInstance = new Router();
        private Context mContext;
        private String path;
        private Map map = new HashMap<>();
    
        public static void init(Application application) {
            mInstance.mContext = application;
            Set routerMap;
            try {
                routerMap = ClassUtils.getFileNameByPackageName(mInstance.mContext, consts.PACKAGE_OF_GENERATE_FILE);
                Log.e("Router", routerMap.toString());
                for (String className : routerMap) {
                    ((IRoute) (Class.forName(className).getConstructor().newInstance())).loadInto(mInstance.map);
                }
            } catch (PackageManager.NameNotFoundException | InterruptedException | IOException | ClassNotFoundException | NoSuchMethodException | InstantiationException | InvocationTargetException | IllegalAccessException e) {
                e.printStackTrace();
            }
    
        }
    
        public static synchronized Router getInstance() {
            return mInstance;
        }
    
        public Router build(String path) {
            mInstance.path = path;
            return mInstance;
        }
    
        public void navigation(Context context) {
            RouteMeta routeMeta = mInstance.map.get(mInstance.path);
            if (routeMeta != null) {
                context.startActivity(new Intent(context, routeMeta.getClazz()));
            }
        }
    }
    • Router需要在Application中初始化
    public class MyApplication extends Application {
    
        @Override
        public void onCreate() {
            super.onCreate();
            Router.init(this);
        }
    }

    在AndroidManifest文件的application节点添加下列语句

    android:name=".MyApplication"
    • 项目build的完之后会在各个模块的相应的文件夹下生成java文件,这些文件会被Router依次获取将路由信息存入路由表中。


    以下代码是编译器自动生成的

    package com.nsyw.routerdemo.routes;
    
    import com.nsyw.routerdemo.MainOneActivity;
    import com.nsyw.routerdemo.MainTwoActivity;
    import com.nsyw.routerdemo.router_compiler.IRoute;
    import com.nsyw.routerdemo.router_compiler.RouteMeta;
    import com.nsyw.routerdemo.router_compiler.RouteType;
    import java.lang.Override;
    import java.lang.String;
    import java.util.Map;
    
    /**
     * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
    public class Router$$App$$app implements IRoute {
      @Override
      public void loadInto(Map routes) {
        routes.put("/main/one",RouteMeta.build("/main/one",RouteType.ACTIVITY, MainOneActivity.class));
        routes.put("/main/two",RouteMeta.build("/main/two",RouteType.ACTIVITY, MainTwoActivity.class));
      }
    }
    • 使用
    Router.getInstance().build("/main/one").navigation(MainActivity.this);

    小结

    Router只是参照ARouter手动实现的路由框架,剔除掉了很多东西,只实现了组件间Activity之间的跳转,如果想要用在项目里,建议还是用ARouter更好,毕竟这只是个练手项目,功能也不够全面,当然有同学想对demo扩展后使用那当然更好,遇到什么问题可以及时联系我。我的目的是通过自己手动实现路由框架来加深对知识的理解,如这里面涉及到的知识点apt、javapoet和组件化思路、编写框架的思路等。看到这里,如果感觉干货很多,欢迎关注我的github,里面会有更多干货!

    点击这里复制本文地址 以上内容由nimo97整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
    qrcode

    尼墨宝库 © All Rights Reserved.  蜀ICP备2024111239号-7