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


    前言

    我们在前面两篇博客学习了ARouter的实现原理,那我们能否参考ARouter的实现,自己动手实现一套简单的路由框架呢?这一篇我们就尝试仿照ARouter,自定义一个属于我们自己的路由框架~~ DXRouter;
    ARouter原理解析之注解处理器
    ARouter原理解析之路由跳转

    浅谈目标以及实现方式

    目标:通过自定义注解实现各个模块间activity跳转以及数据传输
    实现方式:
    1.各个模块定义属于自己的Module中心统一管理activity跳转逻辑,并在注解上添加各个activity的路由地址,例如:

    @DXRouter({"/app/activity1","/app/activity2"})
    public class MainModule extends BaseModule {
    
        private static Map<String, Class> activityMaps = new HashMap<>();
    
        static {
            activityMaps.put("/app/activity1", MainActivity.class);
            activityMaps.put("/app/activity2", Main2Activity.class);
        }
    	//route中完成具体的activity跳转功能
        @Override
        public void route(Context context, String path, Bundle bundle, int requestCode) {
            Class clazz = activityMaps.get(path);
            if (clazz != null) {
                Intent intent = new Intent(context, clazz);
                intent.putExtras(bundle);
                if (requestCode > 0 && context instanceof Activity) {
                    ((Activity) context).startActivityForResult(intent, requestCode);
                } else {
                    context.startActivity(intent);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    其中BaseModule

    public abstract class BaseModule {
    
        /**
         * 跳转逻辑处理
         * @param context 上下文
         * @param path 路由path
         * @param bundle 携带参数
         * @param requestCode requestCode
         */
        public abstract void route(Context context, String path, Bundle bundle, int requestCode);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.注解解析器根据注解动态生成各模块的modulemap集合,apt生成代码如下:

    public class DXRouter$$app$$ModuleMaps implements DXRouterPath {
      @Override
      public Map<String, String> getModuleMaps() {
        Map<String,String> moduleMaps= new HashMap<>();
        moduleMaps.put("/app/activity1","com.dongxian.dxrouter.MainModule");
        moduleMaps.put("/app/activity2","com.dongxian.dxrouter.MainModule");
        return moduleMaps;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.发起路由跳转时,通过传入的path查找对应的module中心管理类【根据包名反射生成并做缓存】,完成对应路由跳转;

    自定义DXRouter框架

    自定义DXRouter注解以及注解解析器

    首先,我们依照ARouter一样,编写我们自己的DXRouter注解,比较简单;

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    public @interface DXRouter {
        String[] value() default {};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接着,我们来实现注解解析器DXRouterProcessor,我们目标是在各个模块下生成文件,这里文件路径我们统一固定为com.dongxian.dxrouter_api.DXRouter$$【模块名】$$ModuleMaps文件名固定为DXRouter$$+【模块名】+$$ModuleMaps,通过模块进行组划分以及分模块进行缓存查找;

    我们先看下DXRouterPath接口定义,主要是为了后续通过反射强转成DXRouterPath方便getModuleMaps方法调用;

    public interface DXRouterPath {
    
        Map<String,String> getModuleMaps();
    }
    
    • 1
    • 2
    • 3
    • 4

    我们简单看下DXRouterProcessor的实现代码,这里使用JavaPoet技术来完成文件写入,相关学习可以参考JavaPoet使用攻略

    /**
     * 路由注解解析器,用于解析自定义DXRouter
     *
     * @author DongXian
     * on 2022/7/26
     */
    @AutoService(Processor.class)
    @SupportedAnnotationTypes(DX_ROUTER_ANNOTATION_PACKAGE_NAME)
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    public class DXRouterProcessor extends AbstractProcessor {
        /**
         * 路由管理类基类
         */
        protected final String packageName = "com.dongxian.dxrouter_api.BaseModule";
    
        private final String TAG = DXRouterProcessor.class.getSimpleName();
        /**
         * 操作Element的工具类(类、函数、属性等对应的都是element)
         */
        private Elements elements;
        /**
         * type(类信息)的工具类,包含用于操作TypeMirror
         */
        private Types types;
        /**
         * 日志打印工具类
         */
        private Messager messager;
        /**
         * 文件写操作
         */
        private Filer filer;
    
        /**
         * module集合map
         */
        private Map<String, List<String>> moduleMaps = new HashMap<>();
    
    
        /**
         * 各个模块传递过来的模块名,如app、order、personal
         */
        private String options;
    
        /**
         * 初始化准备工作
         */
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            elements = processingEnv.getElementUtils();
            types = processingEnv.getTypeUtils();
            messager = processingEnv.getMessager();
            filer = processingEnv.getFiler();
            options = processingEnv.getOptions().get(OPTIONS);
            messager.printMessage(Diagnostic.Kind.NOTE, TAG + " init");
        }
    
        /**
         * 注解处理
         */
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            if (set.isEmpty()) {
                return false;
            }
            //获取DXRouter标注的类
            Set<? extends TypeElement> routesElements = (Set<? extends TypeElement>) roundEnvironment.getElementsAnnotatedWith(DXRouter.class);
            for (Element element : routesElements) {
                TypeElement typeElement = (TypeElement) element;
                String clazzName = typeElement.getQualifiedName().toString();
                if (isExtendsParentClass(typeElement, packageName)) {
                    createModulesFile(typeElement, clazzName);
                } else {
                    messager.printMessage(Diagnostic.Kind.ERROR, "The class:" + clazzName + "should extends " + packageName);
                }
            }
    
            return false;
        }
    
        /**
         * 生成modules map集合文件
         *
         * @param typeElement
         * @param clazzName
         */
        private void createModulesFile(TypeElement typeElement, String clazzName) {
            List<String> paths = new ArrayList<>();
            DXRouter dxRouter = typeElement.getAnnotation(DXRouter.class);
            String[] value = dxRouter.value();
            for (String path : value) {
                if (isLegalPath(path)) {
                    paths.add(path.trim());
                }
            }
            moduleMaps.put(clazzName, paths);
            //返回类型Map
            TypeName methodReturn = ParameterizedTypeName.get(
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(String.class)
            );
            //方法名
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("getModuleMaps")
                    .addAnnotation(Override.class)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(methodReturn);
    
            //代码块 Map moduleMaps= new HashMap<>();
            methodBuilder.addStatement("$T<$T,$T> $N= new $T<>()",
                    ClassName.get(Map.class),
                    ClassName.get(String.class),
                    ClassName.get(String.class),
                    MODULE_MAPS,
                    ClassName.get(HashMap.class));
            for (Map.Entry<String, List<String>> entry : moduleMaps.entrySet()) {
                for (String moduleName : entry.getValue()) {
                    methodBuilder.addStatement("$N.put($S,$S)",
                            MODULE_MAPS,
                            moduleName,
                            entry.getKey()
                    );
                }
    
            }
            //return modulesMaps
            methodBuilder.addStatement("return $N", MODULE_MAPS);
            TypeElement pathTypeElement = elements.getTypeElement(DXROUTER_API_PATH);
            //生成文件
            try {
                //生成文件路径固定写死为 com.dongxian.dxrouter_api.DXRouter$$[模块名称]$$ModuleMaps
                JavaFile.builder(MODULE_MAPS_FILE_PACKAGE_NAME,
                        TypeSpec.classBuilder(MODULE_MAPS_FILE_NAME + options + "$$ModuleMaps")
                                .addSuperinterface(ClassName.get(pathTypeElement))
                                .addModifiers(Modifier.PUBLIC)
                                .addMethod(methodBuilder.build())
                                .build()
                ).build().writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
                messager.printMessage(Diagnostic.Kind.ERROR, "file write exception:" + e.getMessage());
            }
        }
    
    
        /**
         * 查看element是否继承或实现指定的parent
         *
         * @param element
         * @param parent
         * @return
         */
        private boolean isExtendsParentClass(TypeElement element, String parent) {
            TypeElement parentType = elements.getTypeElement(parent);
            if (parentType == null) {
                messager.printMessage(Diagnostic.Kind.WARNING, TAG + element.getSimpleName() + "don't find parent" + parent);
                return false;
            }
            return types.isAssignable(element.asType(), parentType.asType());
        }
    
        /**
         * 检查path路径是否符合/app/activity格式要求
         *
         * @param path
         * @return
         */
        private boolean isLegalPath(String path) {
            if (StringUtils.isEmpty(path) || !path.startsWith("/")) {
                messager.printMessage(Diagnostic.Kind.ERROR, "The" + path + "is illegal,should be like /app/activity");
                return false;
            }
    
            if (path.lastIndexOf("/") == 0) {
                messager.printMessage(Diagnostic.Kind.ERROR, "The" + path + "is illegal,should be like /app/activity");
                return false;
            }
            //截取出group字段
            String finalGroup = path.substring(1, path.indexOf("/", 1));
            if (StringUtils.isEmpty(finalGroup) || !finalGroup.equals(options)) {
                messager.printMessage(Diagnostic.Kind.ERROR, "The " + finalGroup + " should be equals " + options);
                return false;
            }
            return true;
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188

    DXRouter核心路由类封装【dxrouter_api模块】

    public final class DXRouter {
        private final String TAG = DXRouter.class.getSimpleName();
        /**
         * 跳转路径前缀
         */
        private final String TARGET_PREFIX = "com.dongxian.dxrouter_api.DXRouter$$";
        /**
         * 跳转路径后缀
         */
        private final String TARGET_SUFFIX = "$$ModuleMaps";
    
        private static volatile DXRouter instance;
        private Map<String, Map<String, String>> groupMap;
        private Map<String, BaseModule> moduleMap;
    
        private DXRouter() {
            groupMap = new HashMap<>(20);
            moduleMap = new HashMap<>(100);
        }
    
        public static DXRouter getInstance() {
            if (instance == null) {
                synchronized (DXRouter.class) {
                    if (instance == null) {
                        instance = new DXRouter();
                    }
                }
            }
            return instance;
        }
    
        /**
         * 携带Bundle数据传输
         *
         * @param path    目标
         * @param context 上下文参数
         * @param bundle  携带数据
         */
        public void navigation(String path, Context context, Bundle bundle) {
            if (bundle == null) {
                bundle = new Bundle();
            }
            this.navigation(context, path, bundle, -1);
        }
    
    
        /**
         * 直接跳转
         *
         * @param path    目标
         * @param context 上下文参数
         */
        public void navigation(String path, Context context) {
            this.navigation(context, path, new Bundle(), -1);
        }
    
    
        /**
         * 携带Bundle以及requestCode跳转
         *
         * @param context
         * @param path
         * @param bundle
         * @param requestCode
         */
        public void navigation(Context context, String path, Bundle bundle, int requestCode) {
            if (!isLegalPath(path)) {
                //路径非法直接return
                Log.e(TAG, "The path:" + path + "is Illegal!!!");
                return;
            }
            //截图出group名称
            String finalGroup = path.substring(1, path.indexOf("/", 1));
            Map<String, String> moduleMaps = groupMap.get(finalGroup);
            if (moduleMaps == null) {
                //通过反射生成相关类
                String clazzName = TARGET_PREFIX + finalGroup + TARGET_SUFFIX;
                try {
                    Class<?> moduleMap = Class.forName(clazzName);
                    DXRouterPath dxRouterPath = (DXRouterPath) moduleMap.newInstance();
                    moduleMaps = dxRouterPath.getModuleMaps();
                    groupMap.put(finalGroup, moduleMaps);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (moduleMaps == null) {
                Log.e(TAG, "The groupMap is null");
                return;
            }
            String moduleName = moduleMaps.get(path);
            if (TextUtils.isEmpty(moduleName)) {
                Log.e(TAG, "No Find Module by The path:" + path);
                return;
            }
            BaseModule baseModule = moduleMap.get(moduleName);
            if (baseModule == null) {
                try {
                    Class<?> module = Class.forName(moduleName);
                    baseModule = (BaseModule) module.newInstance();
                    moduleMap.put(moduleName, baseModule);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (baseModule == null) {
                Log.e(TAG, "Not Find Module by Path!!!");
                return;
            }
            if (bundle == null) {
                bundle = new Bundle();
            }
            baseModule.route(context, path, bundle, requestCode);
        }
    
    
        /**
         * 检查path路径是否符合/app/activity格式要求
         *
         * @param path
         * @return
         */
        private boolean isLegalPath(String path) {
            if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
                return false;
            }
    
            if (path.lastIndexOf("/") == 0) {
                return false;
            }
            return true;
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    这里使用groupMap以及moduleMap进行缓存,避免二次反射造成的性能损耗,同时也实现了分组按需加载,避免一次全部加载造成的内存浪费,当然上面也只是简单做了一层路由封装,有兴趣的小伙伴可以仿照ARouter_ARouter的装饰模式,进行二次封装,这里不再赘述;

    功能验证

    接下来我们编写测试代码,来验证我们自定义的DXRouter框架是否可以正常运行~~
    1.app模块中相关代码;

    ### MainModule
    @DXRouter({"/app/activity1","/app/activity2"})
    public class MainModule extends BaseModule {
    
        private static Map<String, Class> activityMaps = new HashMap<>();
    
        static {
            activityMaps.put("/app/activity1", MainActivity.class);
            activityMaps.put("/app/activity2", Main2Activity.class);
        }
    
        @Override
        public void route(Context context, String path, Bundle bundle, int requestCode) {
            Class clazz = activityMaps.get(path);
            if (clazz != null) {
                Intent intent = new Intent(context, clazz);
                intent.putExtras(bundle);
                if (requestCode > 0 && context instanceof Activity) {
                    ((Activity) context).startActivityForResult(intent, requestCode);
                } else {
                    context.startActivity(intent);
                }
            }
        }
    }
    
    ### MainActivity
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                	//携带一个name参数
                    Bundle bundle = new Bundle();
                    bundle.putString("name", "DXRouter");
                    DXRouter.getInstance().navigation("/personal/activity2", MainActivity.this, bundle);
                }
            });
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    personal模块相关代码;

    ### PersonalModule
    @DXRouter({"/personal/activity2"})
    public class PersonalModule extends BaseModule {
    
        private static Map<String, Class> activityMaps = new HashMap<>();
    
        static {
            activityMaps.put("/personal/activity2", PersonalActivity.class);
        }
    
        @Override
        public void route(Context context, String path, Bundle bundle, int requestCode) {
            Class clazz = activityMaps.get(path);
            if (clazz != null) {
                Intent intent = new Intent(context, clazz);
                intent.putExtras(bundle);
                if (requestCode > 0 && context instanceof Activity) {
                    ((Activity) context).startActivityForResult(intent, requestCode);
                } else {
                    context.startActivity(intent);
                }
            }
        }
    }
    
    ### PersonalActivity
    public class PersonalActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_personal);
            TextView tv = findViewById(R.id.tv);
            Button btn = findViewById(R.id.btn);
    
            Bundle bundle = getIntent().getExtras();
            String name = bundle.getString("name");
            tv.setText("get Bundle value:" + name);
    
            btn.setOnClickListener(v -> {
                DXRouter.getInstance().navigation("/app/activity1", PersonalActivity.this);
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    最终效果gif:
    DXRouter路由跳转效果

    结语

    最后附上github源码链接:https://github.com/ULooper/DXRouter
    如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

  • 相关阅读:
    数据结构--二叉树(2)
    从双非硕士到大厂工作,优秀
    数据分享|R语言逻辑回归、Naive Bayes贝叶斯、决策树、随机森林算法预测心脏病...
    我眼中的大数据(一)
    不要用第三方日志包了Microsoft.Extensions.Logging功能就很强大
    WPF动画入门教程
    0096 克鲁斯卡尔算法,迪杰斯特拉算法
    机器学习实战练手项目
    React 函数组件
    eNSP学习——RIP的路由引入
  • 原文地址:https://blog.csdn.net/a734474820/article/details/126008898