動手實現一個路由框架
前言
-
組件化或者模塊化開發模式,已逐漸成爲熱浪的形式,使用這些模式可以讓我們程序更容易的擴展、更方便的測試與維護
-
ARouter
是:阿里巴巴自研路由框架,主要解決組件間、模塊間的 界面跳轉 問題。 -
我們將自己簡單實現一個路由
這個項目對你有什麼幫助?
讓我更加熟悉註解與註解編譯器的過程,也讓我們對以後項目也有了更大的可能延展性
構思
讓我們自己先想着如何實現,本身實現並不複雜。 新建一個Arouter類
Arouter
- 既然在使用Arouter的時候我們的調用方式是根據Path來的,那麼我們肯定需要一個Map<String,Class<? extend Activity> class> 來裝載這個集合
- 然後我們要對外開啓一個方法來把Activity Put進來。
- 處理跳轉,根據map.get(Path) 來跳到相應的Activity 中去。
就這三步,是不是看起來很容易。 問題就只在於如何生成一個自動添加Activity的工具類,然後自動的去執行這個方法。
項目結構
分爲4個模塊
-
annotation 模塊:用來定義註解
-
compiler 模塊:用來定義Annotation Processor
-
app 模塊 : 主模塊,也是Activity測試模塊
-
Arouter模塊: 路由模塊
爲什麼要分這麼多模塊?其中 app 模塊是用來測試的,測試新定義的 Annotation Processor 能否成功運行。兩者的目的並不一樣,annotation 是專門定義註解的,而 compiler 是處理註解的。最重要的是,annotation(RetentionPolicy.CLASS,RetentionPolicy.RUNTIME)是需要被編譯到 app 項目的 class 文件中的,而 compiler 沒有必要進入 app 中。所以在gradle中依賴爲compileOnly
-
在annotation模塊中創建一個註解文件,通過這個註解獲取path
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface BindPath {
String value();
}
- 有了註解我們還需要一個compiler 替我們去生成對應的代碼, 我們在創建一個AnotationCompiler類. 編寫註解處理器,在編譯期找到加入註解的類文件,進行處理
@AutoService(Processor.class) //註冊註解處理器
public class AnotationCompiler extends AbstractProcessor {
private Filer mFiler;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
System.out.println("jsc");
}
/**
* 聲明這個註解處理器要識別處理的註解
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindPath.class.getCanonicalName());
return types;
}
/**
* 支持java的源版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return processingEnv.getSourceVersion();
}
/**
* 核心方法
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
System.out.println("123");
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindPath.class);
Map<String,String> map =new HashMap<>();
for (Element element : elementsAnnotatedWith) {
TypeElement typeElement = (TypeElement) element;
BindPath bindPath = typeElement.getAnnotation(BindPath.class);
//對應的key
String value = bindPath.value();
//獲取帶包名的類名
String activityName = typeElement.getQualifiedName().toString();
map.put(value,activityName);
}
if (map.size()>0) {
Writer writer=null;
String activityNmae = "ActivityUtil"+System.currentTimeMillis();
try {
JavaFileObject classFile = mFiler.createSourceFile("com.example.util." + activityNmae);
writer = classFile.openWriter();
writer.write("package com.example.util;\n" +
"\n" +
"import com.example.arouter2.ARouter;\n" +
"import com.example.arouter2.IARouter;\n" +
"\n" +
"/**\n" +
" * @author AlienChao\n" +
" * @date 2019/11/19 17:34\n" +
" */\n" +
"public class "+activityNmae+" implements IARouter {\n" +
" @Override\n" +
" public void putActivity() {");
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String path = iterator.next();
String value = map.get(path);
writer.write("\n ARouter.getInstance().putActivity(\""+path+"\","+value +".class);");
}
writer.write(" \n}\n" +
"}");
} catch (Exception e) {
e.printStackTrace();
}finally {
if (null!=writer) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
}
如代碼中所示,要想在編譯期對註解做處理,就需要AnnotationCompiler繼承自AbstractProcessor並通過@AutoService註解進行註冊,然後實現process()方法。process()方法裏的set集合就是編譯期掃描代碼得到的加入了BindPath註解的文件集合
這裏需要注意的是谷歌的 auto service, 在As 3.4- 之前是用1.0-rc3 在As3.4.1+ gradle5.1.1 之後用1.0-rc4。
-
compileOnly 所需的只是 @AutoService 這個註解。表示只參與編譯過程並不打包到最終產物 jar 文件中,而註解 @AutoService 的 RetentionPolicy 就是 SOURCE,不會編譯生成到 class 文件中。即便使用 implementation依賴,最終生成的 jar 包中只會多了 auto service 提供的註解,其他 class 文件部分不受影響,因此可以使用 compileOnly 只參與編譯過程。
-
annotationProcessor 這個是新版 gradle 提供的 java plugin 內置的配置項,代替了早期第三方提供的 android-apt 插件。而且,敲重點,在 gradle 5.+ 中將 Annotation Processor 從編譯期 classpath 中去除了,javac 也就無法發現 Annotation Processor。此處如果按照 gradle 4.+ 的寫法,只寫一個 compileOnly 是無法使用 auto service 的 Annotation Processor 的。必須要使用 annotationProcessor 來配置 Annotation Processor 使其生效。
生成完了,那怎麼去執行這個代碼呢??
這裏就比較粗暴一點, 直接 根據完整路徑獲取dex,遍歷dex獲取所有帶包名的類名, 有了類名我們就好辦了,可以根據類名 判斷是否是IARouter 的實現類,去調用 putActivity() 方法
private List<String> getClassName(String packageName) {
//創建一個class對象的集合
List<String> classList = new ArrayList<>();
String path = null;
try {
//通過包管理器 獲取到應用信息類然後獲取到APK的完整路徑
path = context.getPackageManager().getApplicationInfo(
context.getPackageName(), 0).sourceDir;
//根據APK的完整路徑獲取到編譯後的dex文件目錄
DexFile dexfile = new DexFile(path);
// 獲得編譯後的dex文件中的所有class
Enumeration entries = dexfile.entries();
//然後進行遍歷
int index = 0;
while (entries.hasMoreElements()) {
index ++;
//通過遍歷所有的class的包名
String name = (String) entries.nextElement();
// 判斷類的包名是否符合 com.dongnao.util
if(name.contains(packageName)){
//如果符合 就添加到集合中
classList.add(name);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return classList;
}
public void init(Application application){
context = application;
List<String> className = getClassName("com.example.util");
//遍歷
for (String s : className) {
try {
Class<?> aClass = Class.forName(s);
//判斷這個類是不是IRouter這個接口的子類
if(IARouter.class.isAssignableFrom(aClass)){
//通過接口的引用 指向子類的實例
IARouter iRouter = (IARouter) aClass.newInstance();
iRouter.putActivity();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
參考資料:
https://blog.csdn.net/ajey2005/article/details/100652260