简易版的Arouter

756 阅读4分钟

之前还简单实现了butterknife编译时注解之butterknife的简单实现

背景

当我们app随着时间的推易,版本的迭代,越来越大,主模块有可能build一下就需要10几分钟。所以我们想出来一个组件化开发,然而组件化开发为了解耦,组件之间一般不做依赖的,组件之间最常见的就是跳转,这里做一个简单的路由跳转,以便熟悉Arouter的原理

动态设置 application 与 library 之间切换

  1. 在工程下面的gradle.properties配置文件中,加入一个变量来决定是否是library
IS_LIBRARY = true
  1. 然后在 library 的 build.gradle 中,来决定是否是库,以及是否配置applicationId,因为库没有applicationId,application才有, 还有就是配置清单文件的位置,因为 library 清单文件 的 application 节点 只有四大组件,其他一般不加其他功能,
//配置是 application 还是 library
if(IS_LIBRARY.toBoolean()){
    apply plugin: 'com.android.library'
}else {
    apply plugin: 'com.android.application'
}


android {
   // 省略代码。。。
    defaultConfig {
    	// 是Application就加入applicationId
        if(!IS_LIBRARY.toBoolean()){
            applicationId "com.nzy.feedlibrary"
        }
        // 省略代码。。。
    }
    sourceSets{
        main{
            if(IS_LIBRARY.toBoolean()){
            	// 在main下面的manifest清单文件夹中的清单文件
                manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
            }else {
            	// 正常的清单文件地址
                manifest.srcFile 'src/main/AndroidManifest.xml'
            }
        }
    }
    // 省略代码。。。
}

开始撸路由,因为我还是用以前butterknife的工程编译时注解之butterknife的简单实现

主要是利用 APT(Annotation Processing Tool) 编译性注解技术(java编译成class的时候),动态生成java类,这个java类里面为咱们做一下东西,比如 ButterKnife 可以 在这个类里面 真正用findViewById来找到控件。 根据 ButterKnife 目录结构,我们一般可以分为4个模块

  1. annotations :自定义注解模块, Java Library模块,不是android library
  2. compiler : 注解处理器模块 并且生成文件, Java Library模块,不是android library
  3. api :框架api模块,供使用者调用,Android Library
  4. demo :实例demo,

先定义注解 annotations 模块

// 注解用在哪里, ElementType.TYPE代表类上
@Target({ElementType.TYPE})
//   注解的生命周期,
//    SOURCE, 只有源码的时候
//    CLASS,  编译型注解,也就是编译成class之后还是会有
//    RUNTIME; 运行时也存在
@Retention(RetentionPolicy.CLASS)
public @interface Route {
    // 如果只有一个参数,最好写成 value ,约定俗称的,并不是强制的
    String value();
}

其次 compiler 用于注解处理器

注解处理器,google已经为咱们准备好了。需要在build.gradle中加入以下依赖

 // google 为咱们提供的注解处理器的依赖
 implementation 'com.google.auto.service:auto-service:1.0-rc2'
 // 因为引用以前的工程
 compile project(':butterknife_annotations')

编写 RouterProcessor 类,生成文件的时候,我直接用的拼接的方法,因为demo比较简单,如果商用的 请使用 javapoet 来生成。

public class RouterProcessor extends AbstractProcessor {
    private static final String TAG = "RouterProcessor";
    private HashMap<String, String> clazzMap = new HashMap<>();
    private Filer mFileUtils;
    private Messager mMessager;

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 因为这里可能会执行多次,所以这里需要clear
        clazzMap.clear();
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Route.class);
        for (Element element : elements) {
            // 直接拿到的就是类,所以强转成类的type
            TypeElement typeElement = (TypeElement) element;
            // 得到全限定名
            String clazzName = typeElement.getQualifiedName().toString();
            // 拿到注解
            Route route = typeElement.getAnnotation(Route.class);
            // 注解的value
            String key = route.value();

            // 添加到map中
            clazzMap.put(key, clazzName + ".class");

        }

		//下面就是开始写文件了,也可以用 javapoet,因为这里比较简单,直接就是手写的,没有 javapoet
        if (clazzMap.size() > 0) {
            mMessager.printMessage(Diagnostic.Kind.NOTE,TAG+clazzMap.size());
            Writer writer = null;
            // 创建文件的名字
            String clazzName = "RouterUtil" + System.currentTimeMillis();
            try {
                JavaFileObject source = mFileUtils.createSourceFile("com.nzy.arouter.util." + clazzName);
                writer = source.openWriter();
                StringBuilder builder = new StringBuilder();
                builder.append("package com.nzy.arouter.util;\n");
                builder.append("import cn.nzy.butterknife_api.IRouter;\n");
                builder.append("import cn.nzy.butterknife_api.ARouter;\n");
                builder.append("public class " + clazzName + " implements IRouter {\n" +
                        "    @Override\n" +
                        "    public void putActivity() {\n");
                Iterator<String> iterator = clazzMap.keySet().iterator();
                while (iterator.hasNext()) {
                    String key = iterator.next();
                    String value = clazzMap.get(key);
                    builder.append("ARouter.getInstance().addActivity(\"" + key + "\"," + value + ");");

                }
                builder.append("    }\n" +
                        "}\n");
                writer.write(builder.toString());
                writer.flush();
                writer.close();

            } catch (IOException e) {
//                e.printStackTrace();
            } finally {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
        return true;
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        // 用来写文件的
        mFileUtils = processingEnvironment.getFiler();
        // 可以打印日志
        mMessager = processingEnvironment.getMessager();
        super.init(processingEnvironment);
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotationTypes = new LinkedHashSet<String>();
        // 需要添加的注解类型
        annotationTypes.add(Route.class.getCanonicalName());
        return annotationTypes;

    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}


api 模块

public class ARouter {
    private Context mContext;

    private ARouter() {
        routerMap = new HashMap<>();
    }

    private HashMap<String, Class> routerMap;
    private volatile static ARouter sInstance;

    public static ARouter getInstance() {
        if (sInstance == null) {
            synchronized (ARouter.class) {
                if (sInstance == null) {
                    sInstance = new ARouter();
                }
            }
        }
        return sInstance;
    }

    public void initRouter(Context context) {
        mContext = context;
        // 找到某个包名下的所有的文件
        List<String> clazzName = getClassName("com.nzy.arouter.util");
        for (String fileName : clazzName) {
            try {

                Class<?> aClass = Class.forName(fileName);
                // 反射 new出实例
                IRouter iRouter = (IRouter) aClass.newInstance();
                // 调用方法
                iRouter.putActivity();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void addActivity(String key, Class activityClass) {
        if (key != null && activityClass != null && (!routerMap.containsKey(key))) {
            routerMap.put(key, activityClass);

        }
    }

    public void gotoActivity(String key) {
        if (routerMap.containsKey(key)) {
            Intent intent = new Intent(mContext, routerMap.get(key));
            mContext.startActivity(intent);
        } else {
            Toast.makeText(mContext, "没有这个key", Toast.LENGTH_LONG).show();
        }
    }

    public List<String> getClassName(String packageName) {
        List<String> classNameList = new ArrayList<String>();
        try {

            DexFile df = new DexFile(mContext.getPackageCodePath());//通过DexFile查找当前的APK中可执行文件
            Enumeration<String> enumeration = df.entries();//获取df中的元素  这里包含了所有可执行的类名 该类名包含了包名+类名的方式
            while (enumeration.hasMoreElements()) {//遍历
                String className = (String) enumeration.nextElement();

                if (className.contains(packageName)) {//在当前所有可执行的类里面查找包含有该包名的所有类
                    classNameList.add(className);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return classNameList;
    }

}

IRouter类

public interface IRouter {
    void putActivity();
}

在 Application 中 加

把 这个key value的加入到 ARouter中


 ARouter.getInstance().initRouter(this);

使用

ARouter.getInstance().gotoActivity("main2");

demo地址