單向依賴的module
組件化中兩個單向依賴的module之間需要互相啓動對方的Activity時,因爲沒有相互引用,startActivity()是實現不了的,必須需要一個協定的通信方式,此時類似ARouter的路由框架就派上用場了。
互不依賴的module
兩個module之間沒有依賴,便不能通過startActivity()的顯示啓動進行通信,那麼如何進行通信呢?
- 隱式跳轉:這是一種解決方法,但是一個項目中不可能所有的跳轉都是隱式的,這樣Manifest文件會有很多過濾配置,而且非常不利於後期維護。
- 反射:當然你用反射拿到Activity的class文件也可以實現跳轉,但是大量的使用反射跳轉對性能會有影響。
- 路由框架。
ARouter
概述
在每個需要對其他module提供調用的Activity中,都會聲明類似下面@Route註解,我們稱之爲路由地址。
@Route(path = "/main/main")
public class MainActivity extends AppCompatActivity {}
@Route(path = "/module1/module1main")
public class Module1MainActivity extends AppCompatActivity {}
編譯期時生成路由映射
路由框架會在項目的編譯期通過註解處理器apt掃描所有添加@Route註解的Activity類,然後將Route註解中的path地址和Activity.class文件映射關係保存到它自己生成的java文件中,只要拿到了映射關係便能拿到Activity.class。
route.put("/main/main", MainActivity.class);
route.put("/module1/module1main", Module1MainActivity.class);
route.put("/login/login", LoginActivity.class);
public void login(String name, String password) {
LoginActivity.class classBean = route.get("/login/login");
Intent intent = new Intent(this, classBean);
intent.putExtra("name", name);
intent.putExtra("password", password);
startActivity(intent);
}
ARouter背後的實現原理其實跟上面講解是一樣的。
apt & javapoet
apt是在編譯期對代碼中指定的註解進行解析,然後做一些其他處理(如通過javapoet生成新的Java文件)。我們常用的ButterKnife,其原理就是通過註解處理器在編譯期掃描代碼中加入的@BindView、@OnClick等註解進行掃描處理,然後生成XXX_ViewBinding類,實現了view的綁定。javapoet是用來生成java文件的一個library,它提供了簡便的api供你去生成一個java文件。
路由映射文件生成原理
定義註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Route {
//Path of route
String path();
//Used to merger routes
String group() default "";
}
ARouter中會要求路由地址至少需要兩級,如"/xx/xx",其中第一級是group。因爲當項目變得越來越龐大的時候,爲了便於管理和減小首次加載路由表過於耗時的問題,我們對所有的路由進行分組。
使用註解
@Route(path = "/main/main")
public class MainActivity extends AppCompatActivity {}
@Route(path = "/main/main2")
public class Main2Activity extends AppCompatActivity {}
註解處理器
@AutoService(Processor.class)
@SupportedOptions({"AROUTER_MODULE_NAME", "AROUTER_GENERATE_DOC" })
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends AbstractProcessor {
// ModuleName and routeMeta.
//分組 key:組名 value:對應組的路由信息
private Map<String, Set<RouteMeta>> groupMap = new HashMap<>();
// Map of root metas, used for generate class file in order.
private Map<String, String> rootMap = new TreeMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//
// Attempt to get user configuration [moduleName]
Map<String, String> options = processingEnv.getOptions();
if (MapUtils.isNotEmpty(options)) {
moduleName = options.get("AROUTER_MODULE_NAME");
}
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//
}
}
說明如下
- @AutoService(Processor.class)——AutoService會自動在META-INF文件夾下生成Processor配置信息文件,該文件裏就是實現Processor接口的具體實現類。而當外部程序裝配這個模塊的時候,就能通過該jar包META-INF/services/裏的配置文件找到具體的實現類名,並裝載實例化,完成模塊的注入。
- @SupportedOptions({KEY_MODULE_NAME, KEY_GENERATE_DOC_NAME})——SupportedOptions指定支持的選項(即處理器接收的參數),AbstractProcessor的getSupportedOptions()會得到它支持的選項。
- @SupportedSourceVersion(SourceVersion.RELEASE_7)——指定使用的Java版本。
- @SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})——指定註解處理器是註冊給哪一個註解的,它是一個字符串的集合,意味着可以支持多個類型的註解,並且字符串是合法全名。
- process()方法裏的set集合就是編譯期掃描代碼得到的加入了Route註解的文件集合。
- moduleName = options.get(“AROUTER_MODULE_NAME”);——以上代碼會得到一個moduleName,這個moduleName需要我們在要使用註解處理器生成路由映射文件的模塊gradle文件裏配置,而@SupportedOptions(“AROUTER_MODULE_NAME”)會拿到當前module的名字,用來生成唯一對應module下存放分組信息的類文件名。我們需要在每個組件module的gradle下配置如下:
android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: "enable"] } } } }
使用javapoet生成java類
定義變量
/**
* key:組名 value:該組名對應的group類名
*/
private Map<String, String> rootMap = new TreeMap<>();
/**
* key:組名 value:該組下的路由信息
*/
private Map<String, List<RouteMeta>> groupMap = new HashMap<>();
定義接口IRouteGroup
public interface IRouteGroup {
void loadInto(Map<String, RouteMeta> atlas);
}
定義接口IRouteRoot
public interface IRouteRoot {
void loadInto(Map<String, Class<? extends IRouteGroup>> routes);
}
生成group類
private void generatedGroup(TypeElement iRouteGroup) {
//創建參數類型 Map<String, RouteMeta>
ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouteMeta.class));
ParameterSpec altas = ParameterSpec.builder(parameterizedTypeName, "atlas").build();
for (Map.Entry<String, List<RouteMeta>> entry : groupMap.entrySet()) {
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constant.METHOD_LOAD_INTO)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(altas);
String groupName = entry.getKey();
List<RouteMeta> groupData = entry.getValue();
for (RouteMeta routeMeta : groupData) {
//函數體的添加
methodBuilder.addStatement("atlas.put($S,$T.build($T.$L,$T.class,$S,$S))",
routeMeta.getPath(),
ClassName.get(RouteMeta.class),
ClassName.get(RouteMeta.Type.class),
routeMeta.getType(),
ClassName.get(((TypeElement) routeMeta.getElement())),
routeMeta.getPath(),
routeMeta.getGroup());
}
String groupClassName = Constant.NAME_OF_GROUP + groupName;
TypeSpec typeSpec = TypeSpec.classBuilder(groupClassName)
.addSuperinterface(ClassName.get(iRouteGroup))
.addModifiers(Modifier.PUBLIC)
.addMethod(methodBuilder.build())
.build();
JavaFile javaFile = JavaFile.builder(Constant.PACKAGE_OF_GENERATE_FILE, typeSpec).build();
try {
javaFile.writeTo(filerUtils);
} catch (IOException e) {
e.printStackTrace();
}
rootMap.put(groupName, groupClassName);
}
}
概括:
- 根據groupMap分組信息,每一組生成一個group類,每一個group類裏包含該組所有<path, routemeta>信息。
- rootMap.put(groupName, groupClassName); //存放組名和該組的group類名的映射關係。
生成Root類
private void generatedRoot(TypeElement iRouteRoot, TypeElement iRouteGroup) {
//創建參數類型 Map<String,Class<? extends IRouteGroup>> routes>
//Wildcard 通配符
ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(iRouteGroup))
));
//參數 Map<String,Class<? extends IRouteGroup>> routes> routes
ParameterSpec parameter = ParameterSpec.builder(parameterizedTypeName, "routes").build();
//函數 public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constant.METHOD_LOAD_INTO)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(parameter);
//函數體
for (Map.Entry<String, String> entry : rootMap.entrySet()) {
methodBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(Constant.PACKAGE_OF_GENERATE_FILE, entry.getValue()));
}
//生成$Root$類
String className = Constant.NAME_OF_ROOT + moduleName;
TypeSpec typeSpec = TypeSpec.classBuilder(className)
.addSuperinterface(ClassName.get(iRouteRoot))
.addModifiers(Modifier.PUBLIC)
.addMethod(methodBuilder.build())
.build();
try {
JavaFile.builder(Constant.PACKAGE_OF_GENERATE_FILE, typeSpec).build().writeTo(filerUtils);
log.i("Generated RouteRoot:" + Constant.PACKAGE_OF_GENERATE_FILE + "." + className);
} catch (IOException e) {
e.printStackTrace();
}
}
概括:
- 根據rootMap信息,生成一個root類,該root類以moduleName命名。
- root類裏包含<groupName, groupClass>信息(即組名與組對應的group類的映射關係)。
路由跳轉
ARouter.init(this)是一個初始化的地方。一路下去,會調用到LogisticsCenter的init方法。路由表的生成有兩種方式,我們看ARouter最傳統的方式。如果是設置了debug模式或者App版本與記錄的不同的情況下,掃描所有Dex文件查找所有com.alibaba.android.arouter.routes開頭的文件,然後更新到SharePreferences。否則直接從SharePreferences讀緩存,減少解析時間。
解析完成後,進行類名遍歷,將所有com.alibaba.android.arouter.routes.Arouter$$Root開頭的類反射出來,實例化,並調用loadInto方法加到Warehouse.groupsIndex這個Map中,也就是拿到了所有group和對應的IRouteGroup。
Warehouse.groupsIndex結構如下:
這些IRouteGroup所對應的類什麼時候解析呢?我們來看會發生跳轉的navigation方法,最終會走到navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback),裏邊會立馬調用LogisticsCenter.completion(postcard)方法。方法裏會先嚐試根據path去取解析過的RouteMeta,但是很明顯,我們這邊還沒有解析過。
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
routeMeta 爲null,於是從Warehouse.groupsIndex去取對應的IRouteGroup,拿到他就可以通過他的loadInto去加載對應的Activity信息了。
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
可以看出,同一分組下的所有RouteMeta的註冊是在其中一個第一次被請求跳轉的時候完成的。Warehouse.routes的結構如下:
有了IRouteGroup就能拿到路由表,爲什麼還需要IRouteRoot再吧IRouteGroup包一層呢?
就解釋了這個問題,我們就當App是首次運行的最壞情況。啓動應用,然後立馬遍歷所有com.alibaba.android.arouter.routes開頭的文件,分析出是Root的文件,反射實例化,調用loadInto方法,將IRouteGroup.class載入。這幾步裏好多是耗時的方法,如果工程很龐大,像淘寶這樣,如果一上來直接加載所有的IRouteGroup中的內容,恐怕要非常長的時間,會導致啓動慢,或者ANR。這也是爲什麼ARouter需要IRouteRoot存在的原因,他可以延時加載所需要的分組,第一次使用的時候加載可以分攤加載時間,很細膩。
最後拿到完整信息的Postcard會創建Intent,進行跳轉。
final Intent intent = new Intent(currentContext, postcard.getDestination());
跳轉會被安排到主線程
// Navigation in main looper.
runInMainThread(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
以上就是ARouter路由大致的實現邏輯。好煩,有些地方還沒徹底弄明白。。。