概況
曾經(jīng)的多模塊項(xiàng)目已有一套路由系統(tǒng)椰棘,已有各種上幾百條不同風(fēng)格的路由别洪。各種不滿足需求。同時(shí)ARouter也不能完全滿足携栋,并且遷移成本很大搭盾。 所以決定開發(fā)一套類似ARouter的路由系統(tǒng)并且可以兼容舊系統(tǒng)。新路由系統(tǒng)走不通走舊系統(tǒng)婉支。
需求 & 各方案比較
-
舊方案
- 模塊依賴不好鸯隅,所有路由解析都實(shí)在公用的模塊中,不是真正跨模塊調(diào)用
- 需要逐一配置路由
- 要逐一解析參數(shù)
- 不支持startActivityForResult
- 不支持?jǐn)r截
-
阿里的ARouter
- 支持編譯期注解
- 無需注意解析參數(shù)
- 支持?jǐn)r截
- 支持跨模塊調(diào)用
- 不支持Restful風(fēng)格
- 不支持一個(gè)Activity多路由
- 不支持一級(jí)路由(如: meijiabang://live_list ),只支持二級(jí)以上( meijiabang://live/list )
-
需求
- 具有ARouter的絕大部分功能
- 支持restful風(fēng)格路由
- 支持一個(gè)Activity多路由
- 支持一級(jí)路由
- 兼容舊的方案蝌以,跑不通新路由跑舊路由
- 有攔截器炕舵,有其他沒有UI的純命令性路由
- Activity內(nèi)有很多goActivity方法代碼可以不需要修改,直接兼容
怎樣做到兼容舊的路由模塊?
- 典型場(chǎng)景要到代理設(shè)計(jì)模式跟畅,兼容兩套錄用路由咽筋,抽象出同一個(gè)接口
public interface IMJBRoute {
void routeTo(Activity activity, String routeUrl);
}
@Deprecated
public class AppRoute implements IMJBRoute
@Override
public void routeTo(final Activity activity, String routeUrl) {
try {
if (XTextUtil.isEmpty(routeUrl)) {
return;
}
try {
EventStatisticsUtil.onPageHit(new UserTrackerModel.Builder("")
.pageParam("").
action("點(diǎn)擊路由").actionParam("app_route", URLDecoder.decode(routeUrl, "utf-8")).build());
} catch (Exception e) {
XLogUtil.log().e("UnsupportedEncodingException");
}
ActivityRoute activityRoute = (ActivityRoute) Router.getRoute(routeUrl);
activityRoute.withOpenMethodStart(activity);
boolean newModuleOpenSuccess = activityRoute.open();
if (!newModuleOpenSuccess) {
EventStatisticsUtil.onEvent("openRoute", "old", routeUrl);
XLogUtil.log().i("new module callbackIntercept failure , use old module , route : " + routeUrl);
oldModule.routeTo(activity, routeUrl);
} else {
XLogUtil.log().i("new module callbackIntercept success , use new module , route :" + routeUrl);
}
} catch (Exception e) {
EventStatisticsUtil.onEvent("openRoute", "exception", routeUrl);
XLogUtil.log().e("new module callbackIntercept exception , use old module , route :" + routeUrl);
oldModule.routeTo(activity, routeUrl);
}
}
路由攔截器
- 解決某些路由頁面進(jìn)入前需要先登錄 (解決方法:登錄攔截器)
- 解決某些沒有UI型,純邏輯型的路由(解決方法:邏輯路由攔截器)
- 解決黑名單路由等等 (解決辦法: 黑名單攔截器)
說的攔截器徊件,這里用了典型的責(zé)任鏈設(shè)計(jì)模式
// 登錄攔截器
Router.setInterceptor(new Interceptor() {
@Override
public boolean intercept(Intent intent, Context context, String url, String matchedRoute, Class<? extends Activity> matchedActivity) {
if (ActivityRouter.getInstance().getExtraList().contains(matchedRoute) && !UserDataUtil.getInstance().getLoginStatus()) {
if (context instanceof Activity) {
LoginActivity.goActivity((Activity) context);
}
return true;
} else {
return false;
}
}
});
//沒有UI路由攔截器
Router.setInterceptor(noUiInterceptor);
//招聘模塊路由攔截器
Router.setInterceptor(jobModuleInterceptor);
//教育模塊路由攔截器
Router.setInterceptor(educationModuleInterceptor);
//商城模塊路由攔截器
Router.setInterceptor(mallModuleInterceptor);
//社區(qū)模塊路由攔截器
Router.setInterceptor(communityModuleInterceptor);
//meijalove模塊路由攔截器
Router.setInterceptor(mainModuleInterceptor);
反射靜態(tài)方法攔截器
需求1:需要適配以前的業(yè)務(wù)
需求2: 跳轉(zhuǎn)前各種邏輯
需求3: 除了傳遞基本數(shù)據(jù)類型奸攻,還要傳遞對(duì)象
需求4: 一些舊邏輯寫在舊Acitivty,不想遷移代碼怎么破虱痕?
解決辦法:使用反射靜態(tài)方法攔截器睹耐。@MJBRouteIntercept 注解于相應(yīng)的Activity方法上,會(huì)在跳轉(zhuǎn)前攔截部翘。返回true代表攔截掉Intent不再往下走硝训。返回false會(huì)繼續(xù)往下運(yùn)行
原理:找到Actiivty被@MJBRouteIntercept注解的方法,解析方法的路由略就,通過反射調(diào)用回Activity里的靜態(tài)方法捎迫。反向控制了Activity. (其實(shí)就是EventBus的那一套!)
@MJBRouteIntercept
@JvmStatic
fun interceptRoute(activity: Activity, intent: Intent): Boolean {
val extras = intent.extras
if (!extras.containsKey("group_id") || !extras.containsKey("title")) {
return true
}
TopicGroupActivity.goActivity(activity, extras.getString("title"), GroupModel(extras.get("group_id") as String, MJLOVE.TopicList.IMAGE_TEXT_BIG))
return true
}
//反射靜態(tài)方法攔截器
Router.setInterceptor(new Interceptor() {
@Override
public boolean intercept(Intent intent, Context context, String url, String matchedRoute, Class<? extends Activity> matchedActivity) {
XLogUtil.log().i(String.format("[route intercept] , matchedRoute : %s ,matchedActivity : %s", matchedRoute, matchedActivity.getSimpleName()));
boolean result = false;
Method[] methods = matchedActivity.getDeclaredMethods();
for (Method method : methods) {
if (method.getAnnotation(MJBRouteIntercept.class) != null) {
String value = method.getAnnotation(MJBRouteIntercept.class).value();
if (XTextUtil.isEmpty(value) || matchedRoute.equals(value)) {
try {
result = (boolean) method.invoke(null, context, intent);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
return result;
}
});
如何調(diào)用注解生成的代碼表牢?
//添加不同模塊的自生產(chǎn)代碼
String[] moduleNames = new String[]{"meijiaLove", "Community", "BusinessCenter", "Education", "Job", "Mall", "Support", "dwtapplet"};
for (String moduleName : moduleNames) {
try {
//利用反射調(diào)用注解生成的代碼
Constructor<?> constructor = Class.forName(String.format("com.meijialove.router.router.AnnotatedRouterTableInitializer$$%s", moduleName)).getConstructor();
IActivityRouteTableInitializer initializer = (IActivityRouteTableInitializer) constructor.newInstance();
mActivityRouter.initActivityRouterTable(initializer);
} catch (Exception e) {
Log.e(TAG, String.format("init %s AnnotatedRouterTableInitializer!", moduleName));
}
}
如何支持RestFul路由
private Intent setKeyValueInThePath(String routeUrl, String givenUrl, Intent intent) {
List<String> routePathSegs = getPathSegments(routeUrl);
List<String> givenPathSegs = getPathSegments(givenUrl);
for (int i = 0; i < routePathSegs.size(); i++) {
String seg = routePathSegs.get(i);
if (seg.startsWith(":")) {
int indexOfLeft = seg.indexOf("{");
int indexOfRight = seg.indexOf("}");
String key = seg.substring(indexOfLeft + 1, indexOfRight);
char typeChar = seg.charAt(1);
switch (typeChar) {
//integer type
case 'i':
try {
int value = Integer.parseInt(givenPathSegs.get(i));
intent.putExtra(key, value);
} catch (Exception e) {
Log.e(TAG, "解析整形類型失敗 " + givenPathSegs.get(i), e);
if (BuildConfig.DEBUG) {
throw new InvalidValueTypeException(givenUrl, givenPathSegs.get(i));
} else {
//如果是在release情況下則給一個(gè)默認(rèn)值
intent.putExtra(key, 0);
}
}
break;
case 'f':
//float type
try {
float value = Float.parseFloat(givenPathSegs.get(i));
intent.putExtra(key, value);
} catch (Exception e) {
Log.e(TAG, "解析浮點(diǎn)類型失敗 " + givenPathSegs.get(i), e);
if (BuildConfig.DEBUG) {
throw new InvalidValueTypeException(givenUrl, givenPathSegs.get(i));
} else {
intent.putExtra(key, 0f);
}
}
break;
case 'l':
//long type
try {
long value = Long.parseLong(givenPathSegs.get(i));
intent.putExtra(key, value);
} catch (Exception e) {
Log.e(TAG, "解析長整形失敗 " + givenPathSegs.get(i), e);
if (BuildConfig.DEBUG) {
throw new InvalidValueTypeException(givenUrl, givenPathSegs.get(i));
} else {
intent.putExtra(key, 0l);
}
}
break;
case 'd':
try {
double value = Double.parseDouble(givenPathSegs.get(i));
intent.putExtra(key, value);
} catch (Exception e) {
Log.e(TAG, "解析double類型失敗 " + givenPathSegs.get(i), e);
if (BuildConfig.DEBUG) {
throw new InvalidValueTypeException(givenUrl, givenPathSegs.get(i));
} else {
intent.putExtra(key, 0d);
}
}
break;
case 'c':
try {
char value = givenPathSegs.get(i).charAt(0);
} catch (Exception e) {
Log.e(TAG, "解析Character類型失敗" + givenPathSegs.get(i), e);
if (BuildConfig.DEBUG) {
throw new InvalidValueTypeException(givenUrl, givenPathSegs.get(i));
} else {
intent.putExtra(key, ' ');
}
}
break;
case 's':
default:
intent.putExtra(key, givenPathSegs.get(i));
}
}
}
return intent;
}
注解生成代碼
AbstractProcessor
AbstractProcess (在編譯時(shí)編譯器會(huì)檢查AbstractProcessor的子類,并且調(diào)用該類型的process函數(shù)崔兴,然后將添加了注解的所有元素都傳遞到process函數(shù)中彰导,使得開發(fā)人員可以在編譯器進(jìn)行相應(yīng)的處理,例如敲茄,根據(jù)注解生成新的Java類)
ProcessingEnviroment參數(shù)提供很多有用的工具類Elements, Types和Filer位谋。 processingEnv.getMessager(),processingEnv.getFiler(),processingEnv.getElementUtils()
Types是用來處理TypeMirror的工具類
Message用于打印
Filer用來創(chuàng)建生成輔助文件。
process() 這個(gè)方法的作用主要是掃描堰燎、評(píng)估和處理我們程序中的注解掏父,然后
生成Java文件,處理所有事情的入口
getSupportedAnnotationTypes() 定義 需要處理的注解秆剪,需要逐一添加
/**
* 編譯時(shí)編譯器會(huì)檢查AbstractProcessor的子類赊淑,并且調(diào)用該類型的process函數(shù),然后將添加了注解的所有元素都傳遞到process函數(shù)中仅讽,使得開發(fā)人員可以在編譯器進(jìn)行相應(yīng)的處理陶缺,例如,根據(jù)注解生成新的Java類
*/
@AutoService(Processor.class)
public class RouterProcessor extends AbstractProcessor {
private Messager mMessager;
private Filer mFiler;
private Elements elementUtils;
private String moduleName = null;
private List<ClassName> needLoginClassNames = new ArrayList<>();
private Map<ClassName, String> needLoginRouteMap = new HashMap<>();
private boolean loggerEnable = true;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 打印用的
mMessager = processingEnv.getMessager();
// Filer用來創(chuàng)建生成輔助文件
mFiler = processingEnv.getFiler();
elementUtils = processingEnv.getElementUtils();
// 區(qū)別不同的模塊
Map<String, String> options = processingEnv.getOptions();
if (options != null && !options.isEmpty()) {
moduleName = options.get("moduleName");
log("RouteProcessor:[init] moduleName : " + moduleName);
}
}
/**
* 定義需要處理的注解洁灵,需要逐個(gè)添加
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> set = new LinkedHashSet<>();
set.add(MJBRoute.class.getCanonicalName());
set.add(NeedLogin.class.getCanonicalName());
return set;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
private void initNeedLoginTypeElements(RoundEnvironment roundEnv) {
needLoginClassNames.clear();
Set<? extends Element> needLoginElements = roundEnv.getElementsAnnotatedWith(NeedLogin.class);
for (Element element : needLoginElements) {
needLoginClassNames.add(ClassName.get((TypeElement) element));
}
}
AutoService
Google 為我們提供了更便利的工具饱岸,叫 AutoService,此時(shí)只需要為注解處理器增加 @AutoService 注解就可以了,如下
JavaPoet
JavaPoet github地址 & 詳細(xì)用法 : https://github.com/square/javapoet
MethodSpec 代表一個(gè)構(gòu)造函數(shù)或方法聲明
TypeSpec 代表一個(gè)類苫费,接口汤锨,或者枚舉聲明
FieldSpec 代表一個(gè)成員變量,一個(gè)字段聲明
JavaFile包含一個(gè)頂級(jí)類的Java文件
private TypeSpec getRouterTableInitializer(Set<? extends Element> elements) throws ClassNotFoundException, TargetErrorException {
if (elements == null || elements.size() == 0) {
return null;
}
TypeElement activityType = elementUtils.getTypeElement("android.app.Activity");
//構(gòu)造 void initRouterTable(Map<String, Class<? extends Activity>> router) 方法
ParameterizedTypeName mapTypeName = ParameterizedTypeName
.get(ClassName.get(Map.class), ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName.get(activityType))));
ParameterSpec mapParameterSpec = ParameterSpec.builder(mapTypeName, "router")
.build();
MethodSpec.Builder routerInitBuilder = MethodSpec.methodBuilder("initRouterTable")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(mapParameterSpec);
for (Element element : elements) {
if (element.getKind() != ElementKind.CLASS) {
throw new TargetErrorException();
}
MJBRoute router = element.getAnnotation(MJBRoute.class);
String[] routerUrls = router.value();
if (routerUrls != null) {
for (String routerUrl : routerUrls) {
log("RouteProcessor:[getRouterTableInitializer]" + routerUrl);
ClassName clsName = ClassName.get((TypeElement) element);
routerInitBuilder.addStatement("router.put($S, $T.class)", routerUrl, clsName);
if (needLoginClassNames.contains(clsName)) {
needLoginRouteMap.put(clsName, routerUrl);
}
}
}
}
MethodSpec routerInitMethod = routerInitBuilder.build();
//構(gòu)造 void initExtraRouteList(List<String> extraRouteList)方法
ParameterizedTypeName listTypeName = ParameterizedTypeName
.get(ClassName.get(List.class), ClassName.get(String.class));
ParameterSpec listParameterSpec = ParameterSpec.builder(listTypeName, "extraRouteList")
.build();
MethodSpec.Builder initExtraRouteListBuilder = MethodSpec.methodBuilder("initExtraRouteList")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(listParameterSpec);
for (String loginRoute : needLoginRouteMap.values()) {
log("RouteProcessor:[initExtraRouteList] " + loginRoute);
initExtraRouteListBuilder.addStatement("extraRouteList.add($S)", loginRoute);
}
TypeElement routerInitializerType = elementUtils.getTypeElement("com.meijialove.router.router.IActivityRouteTableInitializer");
//構(gòu)建好TypeSpec
return TypeSpec.classBuilder("AnnotatedRouterTableInitializer" + "$$" + moduleName)
.addSuperinterface(ClassName.get(routerInitializerType))
.addModifiers(Modifier.PUBLIC)
.addMethod(routerInitMethod)
.addMethod(initExtraRouteListBuilder.build())
.build();
}
分析一些ButterKnife的源碼
parseBindView() , 針對(duì)@BindView這個(gè)注解生成代碼
logParsingError(), 打印錯(cuò)誤的log .
isInaccessibleViaGeneratedCode() , 對(duì)注解作檢查百框,是否有private, static修飾 泥畅,是否存在在class內(nèi)。
isBindingInWrongPackage()琅翻,是否綁錯(cuò)包
isSubtypeOfType() 判斷注解的類是否View
BindingSet這個(gè)類是用JAVAPOET生成代碼的核心類