前言:

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

本篇系“利用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);
  }
}

整个链路流程图如下:

android 组件化后分享 android组件化原理_mybatis

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页面显示如下:

android 组件化后分享 android组件化原理_java_02

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

android 组件化后分享 android组件化原理_android_03

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

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