基于ARouter做的少量扩展(ARouter-Extend)
ARouter是一个用于帮助 Android App 进行组件化改造的框架 —— 支持板块间的路由、通信、解耦
ARouter-Extend源码地址
前言
最近在做全面的页面URL化,之前引入了ARouter,但是并没有真正的用起来,页面跳转还是通过以下方式
@Route(path = "/app/test")public class TestActivity extends BaseActivity { public static final String EXTRA_HOUSE_ID = "extra_house_id"; //小区id public static final String EXTRA_CITY_ID = "extra_city_id"; //城市id private int mHouseId; private int mCityId; //... @Override protected void initIntentParams(Intent intent) { super.initIntentParams(intent); mHouseId = intent.getIntExtra(EXTRA_HOUSE_ID, 0); mCityId = intent.getIntExtra(EXTRA_CITY_ID, 0); } //... public static void launchActivity(Context context, int cityId, int houseId) { Bundle bundle = new Bundle(); if (cityId <= 0) { //如传递的cityId无效,则使用全局的cityId cityId = CityManager.getInstance().getCityId(); } bundle.putInt(EXTRA_CITY_ID, cityId); bundle.putInt(EXTRA_HOUSE_ID, houseId); ARouter.getInstance().build("/app/test").with(bundle).navigation(); }}
目前这种方法的最大问题就是==>重复的模板代码
launchActivity
中的参数赋值initIntentParams
中的参数解析cityId
的有效性检测
ARouter的官方用法:
// 在支持路由的页面上增加注解(必选)// 这里的路径需要注意的是至少需要有两级,/xx/xx@Route(path = "/test/activity")public class YourActivity extend Activity { ...}//-------------------// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)ARouter.getInstance().build("/test/activity").navigation();// 2. 跳转并携带参数ARouter.getInstance().build("/test/1") .withLong("key1", 666L) .withString("key3", "888") .withObject("key4", new Test("Jack", "Rose")) .navigation();
其实ARouter对于我使用过程遇到的这些问题都能处理,为什么我一直都没有真正的用起来呢?
最主要的起因就是ARouter的path路径和页面参数定义,也就是我不喜欢ARouter.getInstance().build("/test/activity").navigation()
这种跳转方式,起因有以下几点:
- path路径和页面参数定义最清楚的应该是板块开发者(A),对于调用方(B)并不是很清楚path和params是什么?
- B调用A的页面时候,并不确定哪些参数是可选的,哪些是必选?
- 当A开发的板块需要新添加个必选参数时候,B并不知道,而且编译不会报错。
当然,上面说的这些,你都可以说B不知道去看开发文档么、A会告诉B的…,这些都是建立在理想情况下,再说每次开发都看文档那得多累,不是么….
假如能把目前项目中用到的launchActivity
方法自动生成的话,那该多爽啊….
说干就干!!!
目标
- 自动生成
launchActivity
方法 launchActivity
方法中区分必选参数和可选参数- 参数的有效性检测
- URL跳转参数类型转换
自动生成launchActivity
方法
实现方案:APT,整体参考ARouter-compiler中的实现,关于APT有很多文章详情,就不多说了,直接上代码:
依赖库
apply plugin: 'java-library'sourceCompatibility = "8"targetCompatibility = "8"dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.alibaba:arouter-annotation:1.0.6' implementation 'com.google.auto.service:auto-service:1.0-rc3' implementation 'com.squareup:javapoet:1.8.0'}
com.alibaba:arouter-annotation:1.0.6 —> 基于ARouter的注解扩展
com.google.auto.service:auto-service:1.0-rc3 —>方便生成编译依赖
com.squareup:javapoet:1.8.0 —>方便生成java文件
Processor API
public interface Processor { //支持的可选参数,可通过getOptions()获取==>AROUTER_MODULE_NAME Set<String> getSupportedOptions(); //支持的注解类型==>com.alibaba.android.arouter.facade.annotation.Route Set<String> getSupportedAnnotationTypes(); //支持的源码版本==>SourceVersion.RELEASE_8 SourceVersion getSupportedSourceVersion(); //初始化,可以获取运行的少量环境参数 void init(ProcessingEnvironment var1); //对注解进行解决,返回true的话,后续阻拦器无法解决(阿里自己的RouteProcessor就返回了true,蛋疼!!!) boolean process(Set<? extends TypeElement> var1, RoundEnvironment var2); Iterable<? extends Completion> getCompletions(Element var1, AnnotationMirror var2, ExecutableElement var3, String var4);}
实现Processor
那些Processor的方法,可以使用下列注解实现
@AutoService(Processor.class)@SupportedOptions({KEY_MODULE_NAME, KEY_GENERATE_DOC_NAME})@SupportedSourceVersion(SourceVersion.RELEASE_8)@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE})public class RouterProcessor extends AbstractProcessor { //... @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { if (set != null && !set.isEmpty()) { Set<? extends Element> routeElements = roundEnvironment.getElementsAnnotatedWith(Route.class); try { logInfo("Found routes, start..."); this.parseRoutes(routeElements); } catch (Exception e) { logger.error(e); } } //肯定要返回false,不然就会想ARouter自己的RouterProcessor一样,别人就没办法继续解决此Annotation了 return false; } //...}
parseRoutes
private void parseRoutes(Set<? extends Element> routeElements) { if (routeElements != null && !routeElements.isEmpty()) { logInfo("Found routes, size is " + routeElements.size()); //新建一个统一的跳转工具类 TypeSpec.Builder activityLaunchBuilder = TypeSpec.classBuilder(generateClassName()) .addJavadoc(WARNING_TIPS) .addModifiers(Modifier.PUBLIC, Modifier.FINAL); //增加方法 List<MethodSpec> methodList = new ArrayList<>(); for (Element element : routeElements) { //暂时只解决Activity TypeMirror activityTm = elements.getTypeElement(Consts.ACTIVITY).asType(); boolean isActivity = false; if (types.isSubtype(element.asType(), activityTm)) { isActivity = true; } if (!isActivity) { continue; } Route route = element.getAnnotation(Route.class); //获取使用@Autowired标注的参数 List<FieldModel> requiredNames = new ArrayList<>(); List<FieldModel> noRequiredNames = new ArrayList<>(); for (Element field : element.getEnclosedElements()) { if (field.getKind().isField() && field.getAnnotation(Autowired.class) != null) { Autowired paramConfig = field.getAnnotation(Autowired.class); String injectName = TextUtils.isEmpty(paramConfig.name()) ? field.getSimpleName().toString() : paramConfig.name(); boolean required = paramConfig.required(); if (required) { requiredNames.add(new FieldModel(injectName, field.asType(), paramConfig.desc())); } else { noRequiredNames.add(new FieldModel(injectName, field.asType(), paramConfig.desc())); } } } //增加方法 List<MethodSpec> methodSpecs = generateLaunchMethodList("launch" + element.getSimpleName(), route.path(), requiredNames, noRequiredNames); methodList.addAll(methodSpecs); } for (MethodSpec method : methodList) { activityLaunchBuilder.addMethod(method); } //写入包名,生成文件 JavaFile javaFile = JavaFile.builder(PACKAGE_OF_GENERATE_FILE, activityLaunchBuilder.build()) .build(); try { javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } }}
生成launchActivity方法
generateLaunchMethodList
生成Activity支持带参数启动的方法列表,目前支持三种模式
Autowired
中required = true
的所有参数Autowired
修饰的所有参数(必选+非必选参数)Autowired
中desc = "ALONE"
的独立参数
private List<MethodSpec> generateLaunchMethodList(String methodName, String routerPath, List<FieldModel> requiredNames, List<FieldModel> norequiredNames) { List<MethodSpec> methodSpecList = new ArrayList<>(); //必选参数方法 methodSpecList.add(generateLaunchMenthod(methodName, routerPath, requiredNames)); //必选+非必选参数方法 List<FieldModel> paramNames = new ArrayList<>(); if (norequiredNames != null && norequiredNames.size() > 0) { paramNames.addAll(requiredNames); paramNames.addAll(norequiredNames); methodSpecList.add(generateLaunchMenthod(methodName, routerPath, paramNames)); } //能否有独立参数ALONE for (int i = 0; i < paramNames.size(); i++) { FieldModel fieldModel = paramNames.get(i); if (TextUtils.equals(fieldModel.getDesc(), Consts.ALONE_DESC)) { methodSpecList.add(generateLaunchMenthod(methodName, routerPath, Collections.singletonList(fieldModel))); } } return methodSpecList;}
generateLaunchMenthod
根据入参list,生成launchActivity方法
private MethodSpec generateLaunchMenthod(String methodName, String routerPath, List<FieldModel> paramNames) { TypeMirror type_Context = elements.getTypeElement(CONTEXT).asType(); MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class); //增加context参数 builder.addParameter(ClassName.get(type_Context), "context"); //增加自己设置参数 for (int i = 0; i < paramNames.size(); i++) { builder.addParameter(TypeName.get(paramNames.get(i).getTypeMirror()), paramNames.get(i).getName()); } //语法 TypeMirror type_bundle = elements.getTypeElement(Consts.BUNDLE).asType(); builder.addStatement("$T bundle = new $T()", ClassName.get(type_bundle), ClassName.get(type_bundle)); for (int i = 0; i < paramNames.size(); i++) { int type = typeUtils.typeExchange(paramNames.get(i).getTypeMirror()); String buildStatement = buildStatement(type, paramNames.get(i).getName()); if (!TextUtils.isEmpty(buildStatement)) { builder.addStatement(buildStatement); } } TypeMirror routerManager = elements.getTypeElement(ROUTER_MANAGER).asType(); //这部分可替换成ARouter的跳转方法 builder.addStatement("$T.getInstance().navigation(context, \"" + routerPath + "\", bundle)", ClassName.get(routerManager)); return builder.build();}
最终效果
==>源文件
@Route(path = "/app/test")public class TestActivity extends Activity { @Autowired(required = true) public int cityId; @Autowired public int houseId; @Autowired(desc = "ALONE") public Object test; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //肯定要写 ARouter.getInstance().inject(this); setContentView(R.layout.activity_test); }}
==>生成的launchActivity文件
public final class AppActivityLaunch { public static void launchTestActivity(Context context, int cityId) { Bundle bundle = new Bundle(); bundle.putString("cityId", cityId); RouterManager.getInstance().navigation(context, "/app/test", bundle); } public static void launchTestActivity(Context context, int cityId, int houseId, Object test) { Bundle bundle = new Bundle(); bundle.putString("cityId", cityId); bundle.putInt("houseId", houseId); RouterManager.getInstance().navigation(context, "/app/test", bundle); } public static void launchTestActivity(Context context, Object test) { Bundle bundle = new Bundle(); RouterManager.getInstance().navigation(context, "/app/test", bundle); }}
后续跳转直接就通过AppActivityLaunch
中的launchXXXActivity
方法,方便、直接、明了、不易出错!
遇到的少量坑
自己独立编译能生成文件,和ARouter集成后无法生成文件
注意以下的顺序,是不是写在ARouter_compiler后面了
//必需写在arouter_compiler前面,由于arouter_compiler对注解解决返回true,后续没办法解决kapt dependencies.router_compiler //自己的route_compilerkapt dependencies.arouter_compiler //阿里的ARouter_compiler
使用
@Autowired
标识的属性无法获取可以从以下几方面继续排查:
- 能否在
onCreate
方法中写了:ARouter.getInstance().inject(this);
- 注意属性的名称能否一致
- 注意属性的类型能否一致,Float和double,int和String是无法接受的
- Serializable类型的属性无法获取,注意router-api的版本能否是最新的1.4.1,之前使用的1.3.1的版本中Serializable被当做对象,需要自己写解析器才能解析。
- 再无法获取,可以全局搜索ARoute_complier的生成类
TestActivity$$ARouter$$Autowired
,debug一下具体情况
- 能否在
全局参数有效性的补充逻辑
例如
cityId
是一个全局参数,当入参的cityId
不存在或者者无效,需要填充全局的cityId
作为入参。刚开始是考虑在
route_compiler
中填充这部分判断代码,后面考虑了一下,耦合性太大,于是借用了@Route中的extras
属性,使用ARouter
中提供的阻拦器(IInterceptor)。@Interceptor(priority = 1, name = "CityID有效性检测")public class CityIdInterceptor implements IInterceptor { @Override public void process(Postcard postcard, InterceptorCallback callback) { int extra = postcard.getExtra(); if ((extra & RouterExtraCons.CITY_ID_CHECK) == RouterExtraCons.CITY_ID_CHECK) { //检测cityId能否有效 int cityId = postcard.getExtras().getInt("cityId"); if (cityId <= 0) { //填充全局的cityId postcard.getExtras().putInt("cityId", CityManager.getInstance().getCityId()); } } callback.onContinue(postcard); } @Override public void init(Context context) { }}
外部Uri跳转进来的时候,因为通过Uri通过
getQueryParameter
获取的参数都是String,假如目标Activity
需要的入参是Int的时候,就没办法匹配上了,咋办?目前没有比较好的方法,用了个兼容性差但是能满足业务要求的方法==>获取到String后强转,代码如下:
/** * 默认强制转换格式Int,Float,Boolean * * @param param * @return */ private static Object parseParamType(String param) { if (param == null) return ""; //int try { return Integer.parseInt(param); } catch (NumberFormatException e) { LogUtils.d("parseParamType is not Integer"); } //Float try { return Float.parseFloat(param); } catch (NumberFormatException e) { LogUtils.d("parseParamType is not Float"); } //boolean try { if (TextUtils.equals(param, "true") || TextUtils.equals(param, "false")) return Boolean.parseBoolean(param); } catch (NumberFormatException e) { LogUtils.d("parseParamType is not Boolean"); } return param; }
后记
目前做的这些,还是很有限的,只是对ARouter
的一点点扩展,基于自己的业务和第三方库的配套使用,底层技术点都是相通的,在注解的扩展性上ARouter
还是有待增强的。
欢迎交流~ ~
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 基于ARouter做的少量扩展(ARouter-Extend)