IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 安卓APT技术讲解(下)-实现安卓组件化的路由功能 -> 正文阅读

[移动开发]安卓APT技术讲解(下)-实现安卓组件化的路由功能

前言:

组件化是安卓目前很流行的一门技术,其目的是避免复杂的业务逻辑交织到一起,相互影响。通过解耦,让每个子项目都是一个独立的工程,即使其余模块出现问题,也不会影响这个子模块的运行。

本篇系“利用APT技术实现安卓组件化的解耦”的下篇,主要讲解如何利用APT技术,实现路由框架的彻底解耦。

一.为什么组件化需要路由功能?

1.1为什么要实现组件化?

首先为什么要组件化?组件化的目的就是为了使各个模块之间相互不依赖,每个模块都是独立的部分,这样大大降低了逻辑复杂度,也在一定程度上降低了一个模块出错后对其它模块的影响。

1.2什么是路由?

由于模块之间相互不依赖了,那么模块之间如果有通讯的需求怎么办?这时候就要通过路由来实现了,就像是浏览器中请求网站,首先通过一个固定的域名请求DNS服务器,DNS返回一个IP地址,然后根据这个IP地址就可以找到对应的网站,然后访问网站中的内容。

而路由就类似于这个DNS服务器,所有模块向其进行注册。模块之间如果发起请求的时候,也向这个路由发起请求,由路由决定其最终由谁来执行。这一点,和系统中ServiceManager的作用很像。

1.3APT为什么能帮助我们实现组件化?

既然需要路由,那么路由和APT又有什么关系呢?APT是为了帮助我们降低代码编写难度,用最简单的代码完成功能,因为代码少了,使用更简单了,自然出问题的可能性就更小了。

为了方便理解,我们先举一个不使用APT技术的路由例子。

首先,我们每个路由类都要进行手动注册,RouterHanlder提供注册方法。如果忘了注册,那么这个注册类就无效了。

 Map<String, RouterBase> map2 = new HashMap<>();
    public void register(String moduleName,RouterBase routerBase){
        map2.put(moduleName,routerBase);
    }

其次,因为主模块对于子模块没有依赖关系,所以主模块不能直接调用子模块路由中的方法名。所以,只能通过如下形式进行调用,每个路由当中都实现一个统一的方法handleAction

 public void jump2(String moduleName, String actionName, Object args) throws Exception {
        RouterBase routerBase = map2.get(moduleName);
        routerBase.handleAction(actionName,args);
    }

然后,每个路由的handleAction方法当中,在进行具体的分法操作:

public void handleRouter(String action, Object obj) {
        if (action.equals("router1_action1")) {
            String str = String.valueOf(obj);
            handleRouter1Action1(str);
            return;
        }
        if (action.equals("router1_action2")) {
            String str = String.valueOf(obj);
            handleRouter1Action2(str);
            return;
        }
    }

这些代码都需要手动编译,手动编译就容易出错,而且费时费力。而APT技术就是在编译期帮助我们自动去生成这些代码的。降低了出错的可能性,并且编程编的更便捷。

二.APT实现字节码的动态生成

2.1 如何设计?

首先,我们应该通过注解识别到路由类和路由方法;

然后,我们针对每一个路由类,生成一个路由代理类执行相关功能。

这里为什么要使用路由代理类,而不是直接使用路由类呢?因为代理类的可扩展性更高,我们可以通过切面,进行参数转换,以及各种日志的纪录等等。举个例子,路由类如下:

@Route(moduleName = "b_router")
public class MyRouter2 implements RouterBase {
    @RouteMethod(methodKey = "router2_action1")
    public void handleRouter2Action1(String text) {
        ToastUtil.showCenterToast(text);
        Log.i("lxltest", "a_sayhello action,args:" + text);
    }
}

而路由代理类可以如下:

public class MyRouter2Proxy implements RouterProxyInter {
    MyRouter2 mMyRouter2;
    public MyRouter2Proxy(MyRouter2 mMyRouter2){
        this.mMyRouter2 = mMyRouter2;
    }

    public void handleAction(String action, Object args) {
        //这里可以实现埋点纪录等等
        if("router2_action1".equals(action)) {
            //参数转换,转换为代理类所需要的类型
            java.lang.String local=String.valueOf(args);
            mMyRouter2.handleRouter2Action1(local);
        }
    }
}

最后,要动态生成路由注册类。

我们要统计一下所有的路由代理类,形成映射关系,汇总到路由注册类上面进行注册。这样,如果有路由的跳转需求,就可以交由路由注册类进行分发了。路由注册类应该是如下的代码:

public class RegisterRouter extends RegisterRouterInter {
  @Override
  public void init() {
      com.xt.client.function.route.MyRouter2Proxy myrouter2 = new com.xt.client.function.route.MyRouter2Proxy(new com.xt.client.function.route.MyRouter2());
      map.put("b_router", myrouter2);
      com.xt.client.function.route.MyRouter1Proxy myrouter1 = new com.xt.client.function.route.MyRouter1Proxy(new com.xt.client.function.route.MyRouter1());
      map.put("a_router", myrouter1);
      com.xt.client.function.route.MyRouter3Proxy myrouter3 = new com.xt.client.function.route.MyRouter3Proxy(new com.xt.client.function.route.MyRouter3());
      map.put("c_router", myrouter3);
  }
}

整个链路流程图如下:

2.1?创建相关注解类

首先,我们创建两个注解Route和RouteMethod,分别作用于路由类和路由方法。

//路由类注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {

    String moduleName();

}

//路由方法注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface RouteMethod {

    String methodKey();

}

理论上,可以不需要注解类,凡是被@RouteMethod标记的方法都可以认为是路由的方法,但是实际编写中,我们还是希望模块对外提供的功能有一个收口,所以创建@Route注解,只有被这个注解声明的类,才会扫描其中的@RouteMethod注解。

2.2?路由注册类的生成

首先,我们创建RouterProcessor类来处理所有带@Route注解的路由类。

在process方法中,扫描项目中所有的带@Route标签的类,汇总产生一个集合,然后依次遍历这个集合。

Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(Route.class);
Map<String, String> map = new HashMap<>();
for (Element e : elementsAnnotatedWith) {
            Route annotation = e.getAnnotation(Route.class);
            Element enclosingElement = e.getEnclosingElement();
            String packageName = ProcessorUtils.getPackage(enclosingElement);
            //生成代理类
            generatedProxyClass(packageName, e.getSimpleName().toString(), e);

            map.put(annotation.moduleName(), packageName + "." + e.getSimpleName().toString());
}
generatedClass("RegisterRouter", map);

每次遍历的过程中,首先生成这个路由类的代理类(代理类生成介绍在2.3中),然后纪录其注册字和类名路径,最后通过generatedClass方法去生成路由注册类。

一样有几个参数是要动态去获取的,如下:

变量名变量描述获取方式
moduleMap路由注册字和路由类的映射关系注册字:annotation.moduleName()

路由类地址:

packageName + "." + e.getSimpleName().toString()

完整生成代码如下:

/**
     * 针对注解中的内容生成类
     * 注册key,类名
     */
    private void generatedClass(String fileName, Map<String, String> moduleMap) {
        StringBuilder builder = new StringBuilder();
        addLine(builder, "package com.xt.client;");

        addLine(builder, "import java.util.HashMap;");
        addLine(builder, "import java.util.Map;");
        addLine(builder, "import java.util.Map;");
        addLine(builder, "import com.xt.client.function.route.RouterBase;");
        addLine(builder, "import com.xt.client.function.route.RegisterRouterInter;");

        for (String value : moduleMap.values()) {
            addLine(builder, "import " + value + ";");
        }


        addLine(builder, "public class " + fileName + " extends RegisterRouterInter {");
        addLine(builder, "  @Override");
        addLine(builder, "  public void init() {");

        for (String key : moduleMap.keySet()) {
            String name = moduleMap.get(key);
            String className = name.substring(name.lastIndexOf(".") + 1).toLowerCase();
            String createLine = "      " + name + "Proxy " + className + " = new " + name + "Proxy(new " + name + "());";
            String addLine = "      map.put(\"" + key + "\", " + className + ");";
            addLine(builder, createLine);
            addLine(builder, addLine);
        }

        addLine(builder, "  }");
        builder.append("}");
//        System.out.println(TAG + ":RouterProcessor:" + builder.toString());
        try {
            JavaFileObject sourceFile = filer.createSourceFile("RegisterRouter");
            Writer writer = sourceFile.openWriter();
            writer.append(builder.toString());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

?2.3?路由代理类的生成

我们的目标是生成如下形式的代理类,当然,我们还可以进行添加埋点纪录等等操作,这些都是后续的进一步扩展,这里不展开了,有兴趣的小伙伴可以自己进行相关操作。

public class MyRouter1ProxyDemo implements RouterProxyInter {
    MyRouter1 mMyRouter1;
    public MyRouter1ProxyDemo(MyRouter1 myRouter1) {
        this.mMyRouter1 = myRouter1;
    }
    public void handleAction(String action, Object args) {
        if ("router1_action1".equals(action)) {
            String text = String.valueOf(args);
            mMyRouter1.handleRouter1Action1(text);
        } else if ("router1_action2".equals(action)) {
            mMyRouter1.handleRouter1Action2(args);
        }
    }
}

我们要在APT的过程中,识别到一个@Route声明的类,就去动态生成一个这样的代理类。

这里我们有几个参数是要动态去获取的,如下:

变量名变量描述获取方式
packageName包名参考ProcessorUtils.getPackage()方法
className路由类名e.getSimpleName()
proxyName代理类名className+"proxy"
paramsType方法入参类型下面会讲
methodName路由方法名e.getSimpleName().toString()
methodKey路由关键字annotation.methodKey();
localName方法内变量名"m" + className;

其中我们讲一下如何获取路由方法的参数类型和参数名:

路由方法如下:

 @RouteMethod(methodKey = "router1_action1")
    public void handleRouter1Action1(String text) {
        ToastUtil.showCenterToast(text);
        Log.i("lxltest", "a_sayhello action,args:" + text);
    }

APT中,所有的类型,比如类,方法,参数等等都是element对象。但是element对象的具体实现类是不一样的。所以,我们首先识别到带有注解的注解类对象classElement,然后通过classElement.getEnclosedElements()方法获到类中所有的方法。然后依次识别方法中是否@RouteMethod注解,如果有,则获取对应方法的参数名和类型。

首先仍然通过方法的Element,方法的Element的最终实现类是Symbol.MethodSymbol对象。强转对象,然后获取其变量params,返回是一个数组,就就代表着参数集合类型,具体代码如下:

private String getType(Element methodElement) {
        Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) methodElement;
        com.sun.tools.javac.util.List<Symbol.VarSymbol> params = methodSymbol.params;
        for (Symbol.VarSymbol varSymbol : params) {
            return varSymbol.type.toString();
        }
        return Object.class.toString();
    }

有了各种参数之后,我们就可以做最后的代码拼装了,我这里使用的是createSourceFile的方式,直接使用文本生成java类,完整代码如下:

private void generatedProxyClass(String packageName, String className, Element classElement) {
        //获取所有方法
        List<? extends Element> enclosedElements = classElement.getEnclosedElements();
        StringBuilder builder = new StringBuilder();
        String localName = "m" + className;
        String proxyName = className + "Proxy";

        addLine(builder, "package " + packageName + ";");
        addLine(builder, "public class " + proxyName + " implements RouterProxyInter {");
        addLine(builder, "    " + className + " " + localName + ";");
        addLine(builder, "    public " + proxyName + "(" + className + " " + localName + "){");
        addLine(builder, "        " + "this." + localName + " = " + localName + ";");
        addLine(builder, "    }");
        addLine(builder, "");
        addLine(builder, "    public void handleAction(String action, Object args) {");
        boolean isElse = false;
        for (Element e : enclosedElements) {
            RouteMethod annotation = e.getAnnotation(RouteMethod.class);
            if (annotation == null) {
                continue;
            }
            /**
             * 参数类型转换
             * 首先,获取注解标记的方法类型
             *
             */
            String paramsType = getType(e);

            String typeTransforLine = "";
            if (paramsType.equals("java.lang.String")) {
                typeTransforLine = paramsType + " local=String.valueOf(args);";
            } else if (paramsType.equals("java.lang.Integer")) {
                typeTransforLine = paramsType + " local=Integer.parseInt(args);";
            } else {
                typeTransforLine = "Object local=args;";
            }
            String methodName = e.getSimpleName().toString();
            String methodKey = annotation.methodKey();
            //方法中如果带RouteMethod注解,则生成
            if (!isElse) {
                isElse = true;
                builder.append("        if");
            } else {
                builder.append("        else if");
            }
            addLine(builder, "(\"" + methodKey + "\".equals(action)) {");
            addLine(builder, "            " + typeTransforLine);
            addLine(builder, "            m" + className + "." + methodName + "(local);");
            addLine(builder, "        }");
        }
        addLine(builder, "    }");
        addLine(builder, "}");
//        System.out.println(TAG + ",builder:\n" + builder);
        JavaFileObject sourceFile = null;
        try {
            sourceFile = filer.createSourceFile(proxyName);
            Writer writer = sourceFile.openWriter();
            writer.append(builder.toString());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

三.实验

完成了通过APT实现代理类的自动生成之后,我们就可以进行实验了。

RouterHandle类注册

首先构建RouterHandle类,由这个类进行模块之间功能的分发,这个功能在主模块中,其余的子模块都应该依赖这个主模块。

这个类中,通过注册反射生成的RegisterRouter类,获取到所有的路由类。

public class RouterHandle {

    Map<String, RouterProxyInter> map = new HashMap<>();
    RegisterRouterInter routerInter;

    public RouterHandle() {
        try {
            Class<?> aClass = Class.forName("com.xt.client.RegisterRouter");
            routerInter = (RegisterRouterInter) aClass.newInstance();
            routerInter.init();
            map.putAll(routerInter.getRouteMap());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void jump(String moduleName, String actionName, Object args) throws Exception {
        RouterProxyInter proxyInter = map.get(moduleName);
        if (proxyInter == null) {
            throw new Exception("RouterBase is unregistered.");
        }
        routerInter.handleAction(proxyInter, actionName, args);

    }
}

我们看一下动态生成的RegisterRouter类:

public class RegisterRouter extends RegisterRouterInter {
  @Override
  public void init() {
      com.xt.client.function.route.MyRouter2Proxy myrouter2 = new com.xt.client.function.route.MyRouter2Proxy(new com.xt.client.function.route.MyRouter2());
      map.put("b_router", myrouter2);
      com.xt.client.function.route.MyRouter1Proxy myrouter1 = new com.xt.client.function.route.MyRouter1Proxy(new com.xt.client.function.route.MyRouter1());
      map.put("a_router", myrouter1);
      com.xt.client.function.route.MyRouter3Proxy myrouter3 = new com.xt.client.function.route.MyRouter3Proxy(new com.xt.client.function.route.MyRouter3());
      map.put("c_router", myrouter3);
  }
}

这个类完成了所有路由类的注册。

RouterHandle中进行事件分发

模块之间进行通讯时,最终都会调用到jump方法,如下:

 public void jump(String moduleName, String actionName, Object args) throws Exception {
        RouterProxyInter proxyInter = map.get(moduleName);
        if (proxyInter == null) {
            throw new Exception("RouterBase is unregistered.");
        }
        routerInter.handleAction(proxyInter, actionName, args);
    }


RegisterRouterInter.class:
public void handleAction(RouterProxyInter routerBase, String actionName, Object args) {
        routerBase.handleAction(actionName, args);
    }

理论上,完全可以去掉moduleName,直接根据actionName的唯一性来找到对应的routerProxy,但是不同模块之间,也允许actionName相同的场景,所以这里添加了moduleName来区分。

最后交给routerProxy的handleAction方法来处理,而RouterProxyInter的实现类,也是动态生成的,如下:

public class MyRouter1Proxy implements RouterProxyInter {
    MyRouter1 mMyRouter1;
    public MyRouter1Proxy(MyRouter1 mMyRouter1){
        this.mMyRouter1 = mMyRouter1;
    }

    public void handleAction(String action, Object args) {
        if("router1_action1".equals(action)) {
            java.lang.String local=String.valueOf(args);
            mMyRouter1.handleRouter1Action1(local);
        }
        else if("router1_action2".equals(action)) {
            Object local=args;
            mMyRouter1.handleRouter1Action2(local);
        }
    }
}

典型的代理类模式,其持有MyRouter1,最后把请求转交到MyRouter1中的具体方法中,并且还完成了参数类型的转换。当然这里,我们还可以做更多的事,比如增加日志监控,埋点等等。

效果验证:

四个按钮,分别按照下面关系调用

按钮调用代码实现类实现方法
button1
routerHandle.jump("a_router", "router1_action1", "button1 click in a_router")
MyRouter1
handleRouter1Action1
button2
routerHandle.jump("a_router", "router1_action2", "button2 click in a_router")
MyRouter1
handleRouter1Action2
button3
routerHandle.jump("b_router", "router2_action1", "button3 click in b_router")
MyRouter2
handleRouter2Action1
button4
routerHandle.jump("c_router", "router3_action1", "button4 click in c_router")
MyRouter3
handleRouter3Action1

RouteFragment页面显示如下:

点击按钮:执行路由1中方法1后,提示如下:

?这个toast正是MyRouter1中的handleRouter1Action1()方法执行的:

 @RouteMethod(methodKey = "router1_action1")
    public void handleRouter1Action1(String text) {
        ToastUtil.showCenterToast(text);
        Log.i("lxltest", "a_sayhello action,args:" + text);
    }

四.备注和声明:

1.项目地址:

本文中所有的使用到的代码都在demo项目中,项目地址如下,欢迎start,fork和pr。

GitHub - aa5279aa/android_all_demo: 一直觉得研究各种技术,一个个demo的下载运行太费劲了,为什么不能有一个所有新技术的融合体demo呢?项目为此而生

测试入口类:

android_all_demo/RouteFragment.kt at master · aa5279aa/android_all_demo · GitHub

路由注解定义library:

android_all_demo/DemoClient/annotationLibrary at master · aa5279aa/android_all_demo · GitHub

APT路由生成类:

android_all_demo/RouterProcessor.java at master · aa5279aa/android_all_demo · GitHub

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-09-24 21:08:24  更:2022-09-24 21:11:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 20:51:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码