Android 浅解组件化-路由技术(2)

前言

这一篇文章主要讲解路由知识,上一篇文章主要讲解组件化知识。
我打算开车了... 路由实现确实还是有些难度的,尽量讲仔细点。

实现效果:

注释目标活动
跳转实现

可查看的路由表

这样就可以非常简单的实现组件之间的活动跳转了。
解释下实现的效果:

  • 图一是使用自定义的注解来注释我们的某一个活动,注解中的三个属性是为了给这个活动生成对应的URL和路由表。
  • 图二则是实现跳转一个活动,最关键的是Go.readyToJump(this).setUrl("url").launch()这句代码,就是我们的Go类通过url来找到对应的Activity,并实现跳转。
  • 图三这个东西可有可无,就是一个txt文件,专门给协同开发的成员查看的,成员可以从中找到他们想要跳转的活动的URL。

实现思路

思路图

这个思路图可能并不够清晰,看来下面的代码讲解再回来看可能要好些。

代码讲解

组件化就不讲了,先看下项目目录吧,新增加了三个组件。


image.png
  • router_annotation:JavaLibrary,这个组件中只有一个注解,其它什么逻辑都没有,也就是上面提及的Route注解。
  • router_complier:JavaLibrary,这个组件负责的就是查找被注解了的类、生成对应的代码类。
  • router_go:AndroidLibrary,这个组件是暴露给其它开发者使用的组件,就是上面实现跳转Activity的地方,它的作用就是实现“add()”方法,将Activity.class投入Map中去,还有就是通过URL查找到对应的Activity.class,以实现跳转。

router_annotation

router_annotation

这个注解就是有三个变量属性,分别是name->活动别名、module->属于哪一个模块、desc->这个活动的描述。至于为什么注解要单独放在一个组件里面呢?原因我也忘记了...但是这样放置,也许是更加符合组件化吧(勉强...)。

router_go

目录

BaseClassMap主要就两句代码:

public abstract class BaseClassMap {
    protected volatile static HashMap mMap = new HashMap<>();

    public abstract void add();
}

这个类是给生成类继承的,覆写add()方法,生成的add()就应该是向Map中投入URL和Class对象。类中Map就是放置url和Class的容器。
Go类不用多说,放出关键代码:

    /**
     * 注册组件 找到生成类,实例,调用add()方法
     */
    public static void registerModule(String name) {
        String path = "com.module_router." + name + "." + name + "$$Inject";
        try {
            Class cla = Class.forName(path);
            BaseClassMap map = (BaseClassMap) cla.newInstance();
            map.add();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

   /**
    * 通过url在Map中找到对应的Class对象
    */
   private Class getTargetClass(String url) {
        Class a ;
        //判断是否是网址,是网址则去WebActivity
        if (isNetworkUrl(url)) {
            a = BaseClassMap.mMap.get(WEB_ACTIVITY_URL);
            //MatesConfig.TO_WEB_KEY
            mAttr.setExtra(WEB_KEY, url);
        } else {
            a = BaseClassMap.mMap.get(url);
        }
        
        //没能找到
        if (a == null) {
            mAttr.getCallBack().error(parseUrl(url));
            return null;
        }
        //不是Activity对象
        if (!Activity.class.isAssignableFrom(a) ) {
            mAttr.getCallBack().error(CallBack.CLASS_ERROR);
            return null;
        }
        //频繁操作
        long nowTime = System.currentTimeMillis();
        mRecordTime = nowTime;
        if (mRecordCode == a.hashCode() && nowTime - mRecordTime <= MIN_TIME) {
            mAttr.getCallBack().error(CallBack.FREQUENT_OPERATION);
            mRecordCode = a.hashCode();
            return null;
        }
        return a;
    }

   /**
    * 通过找到的Class,实现Intent跳转
    */
  private void realLaunch(Class a) {
        Intent intent = new Intent(mAttr.getContext(), a);
        intent.putExtra(mAttr.getKey(), mAttr.getText());
        if (mAttr.getBundle() != null) {
            mAttr.getContext().startActivity(intent, mAttr.getBundle());
        } else {
            mAttr.getContext().startActivity(intent);
        }
        if (mAttr.getEnterAnim() != -1 && mAttr.getExitAnim() != -1) {
            AppCompatActivity activity = (AppCompatActivity) mAttr.getContext();
            //设置动画
            activity.overridePendingTransition(mAttr.getEnterAnim(), mAttr.getExitAnim());
        }
    }

其实逻辑不难,一句话:使用add()方法将URL和Activity.classy投入Map,通过URL去Map中找Class,找到的Activity.Class用来实现Intent跳转。

router_compiler

目录

先分析AnnotationProcessor类:

@AutoService(Processor.class) 
@SupportedSourceVersion(SourceVersion.RELEASE_7)    
public class AnnotationProcessor extends AbstractProcessor {
    @Override
    public Set getSupportedAnnotationTypes() {
        //select type
        Set types = new LinkedHashSet<>();
        types.add(Route.class.getCanonicalName());
        return types;
    }

    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        return  true;
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
    }  
}

该类中不能含有中文字符,因为中文字符在编译的时候不能够被映射。
该类头部注解分别实现注册、确定编译Java版本的功能,而getSupportedAnnatationTypes()方法则是确定我们要“寻找”的注解,init()方法很简单,就是可以在里面执行一些初始化的东西。重点是process()方法,就是在编译器收集注解的元素之后,会执行process()方法。提供一句关键代码:for (Element e : roundEnvironment.getElementsAnnotatedWith(Route.class)),这样就可以每一个每一个的拿到被注解了的元素。但是有一个“坑”!,就是在组件化中,编译器是按照组件为单位调用process()方法,例如第一次调用process()方法,传递的是组件A中的被注解元素,而第二次调用该方法,传递的便是组件B中的被注解元素,要注意处理的逻辑关系。

上面讲解的类的作用就是主动拿到每个组件中被注解的元素,回顾一下,我们的Activity.class是被注解的元素,因此我们可以拿到跟这个Activity.class相关的信息。接下来我们便是要为Activity.class生成对应的Java文件,将我们的Activity.class投递到Map中去。

Java文件生成器ClassBuilder,关键代码:

public void generateJavaFile() {
        if (mNodes.size() == 0) return;

        ClassName superName = ClassName.get(mElements.getTypeElement(SUPER_CLASS_NAME));
        String className = mNodes.get(0).getModule() + "$$Inject";
        String packageName = mNodes.get(0).getModulePackageName();

        MethodSpec.Builder injectMethod = MethodSpec.methodBuilder("add")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class);
        for (Node n : mNodes) {
            injectMethod.addCode(
                    "mMap.put($S, "+n.getPackageName() + "." + n.getClassName()+");\n",
                    n.getUrl());
        }
        MethodSpec methodSpec = injectMethod.build();

        TypeSpec classSpec =TypeSpec.classBuilder(className)
                .addModifiers(Modifier.PUBLIC)
                .superclass(superName)
                .addMethod(methodSpec)
                .build();

        try {
            //write java file
            JavaFile.builder(packageName, classSpec).build().writeTo(mFiler);
        } catch (IOException e) {
            mMessager.printMessage(Diagnostic.Kind.ERROR,
                    "Router ---> create file error!");
        }
    }

这里解释下Node类,它就是一个节点类,包含着一个被注解元素的部分信息,例如它的name、module、desc、报名......
上面这段代码就是实现生成一个.java文件,说一下关键之处。这里的SUPER_CLASS_NAME其实是BaseClassMap的完整包路径名,就是上面讲的那一个BaseClassMap类。在下面,生成类覆写了add()方法,贴上Demo生成类:

image.png

这里的key是一个URL,仔细观察,它其实来源于@Route(name = "home_activity", module = "home", desc = "home组件的主活动")这一句代码,也就是为当前的Activity.class生成了唯一对应的URL,并且add()就是要实现将这个URL和Activity.clas投入Map中去,后面我们就根据URL在这个Map中寻找对应得Activity.class,并且用来Intent跳转。
最后再解释RouterFileUtil类,它就是实现生成路由表(table.txt)的一个帮助类而已

image.png

路由表

接下来再贴出导库的步骤:


记住,路由具有特殊性,要求必须在每一个组件下都导入路由组件,也不要使用api导入。
然后,就可以rebuild一下项目,这样路由就会工作,为你生成对应的代码。

现在还不能直接使用跳转,还记得文章上面说的那个Go类吗?里面有一个registerModule(),现在再好好解一下它。我们在生成了代码文件之后,代码是没有执行的,因此我们需要在程序运行的时候执行生成的代码,也就是我们所谓的“注册组件”。建议下面这段代码放在程序初始化工作中去:

    private void registerModule() {
        //app壳组件
        Go.registerModule(AppConfig.APP_MODULE);
        //home组件 AppConfig.HOME_MODULE = "home"
        Go.registerModule(AppConfig.HOME_MODULE);
        //聊天组件
        Go.registerModule(AppConfig.CHAT_MODULE);
    }

这一步就是将URL和Activity.class全部投入Map中去。

最后,我们就可以实现跳转了。

总结

说实话,我觉得自己讲路由确实讲得很差,我是想将主要的思想说出来。将就啦...
还有就是本文中的路由的功能是很少的,但是我觉得路由得主要思想都掌握了的话,其它的实现也不会是多难得事儿,例如实现拦截器、埋点等等,也并不是太难。所以,我还是写了一个Demo,很简单,欢迎clone Demo地址

笔者水平有限,有写得不好的地方,请大胆指出~
转载请注明出处~

你可能感兴趣的:(Android 浅解组件化-路由技术(2))