之前还简单实现了butterknife编译时注解之butterknife的简单实现
背景
当我们app随着时间的推易,版本的迭代,越来越大,主模块有可能build一下就需要10几分钟。所以我们想出来一个组件化开发,然而组件化开发为了解耦,组件之间一般不做依赖的,组件之间最常见的就是跳转,这里做一个简单的路由跳转,以便熟悉Arouter的原理
动态设置 application 与 library 之间切换
- 在工程下面的gradle.properties配置文件中,加入一个变量来决定是否是library
IS_LIBRARY = true
- 然后在 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个模块
- annotations :自定义注解模块, Java Library模块,不是android library
- compiler : 注解处理器模块 并且生成文件, Java Library模块,不是android library
- api :框架api模块,供使用者调用,Android Library
- 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");