搭建Android客户端APP架构——《跨组件化路由实现一路由跳转》
背景
本人从事开发工作也有多年,目前坐标湖南长沙,以前在各种平台也发过一些文章但是都没有坚持下来;
我初步规划是写一个完整的项目系列文章期望能坚持下来。
为什么会想到要写呢?
其一、眨眼就到了而立之年,觉得自己记忆力也是下降久做过的东西总是记不起,果然是好记性不如烂笔头。
其二、这么多年白嫖了网上很多的文章,视频,一直觉得应该分享一些东西但总是沉不下心去做。
其三、可能写的不好至少也留下一些东西,也是希望能帮助到一些朋友。
说明
按照我偶尔写一点代码然后偶尔写一篇文章的速度可能,一步步的慢慢搭建一个可用的示例项目可能会比较慢,这篇是系列文章的第三篇开发。组件化开发必然就需要涉及到路由跳转。
当然业界有很多大名鼎鼎的组件化路由工具如:ARouter,虽然我们可能写得不如ARouter全,但是我们一定不能直接拿来主义。我们需要是自己弄清楚实现核心内容。
我们为什么要用优雅的方式进行路由跳转
有人可能会问反正都是会要引用直接Activity类跳转不就得了…那…我们干脆不用组件化开发吧…
在我的想法中组件化开发,插件化开发核心思想应该是解耦,只有解耦之后才能谈我们的组件化和插件化。
既然是解耦当然不能直接引用我们的Activity类进行跳转了。而且试想一个场景我们主APP模块里面已经去掉了一个活动的引用(下架活动了)我们Activity类是不是就找不到了这个时候是不是就得报错呢。我们每次都要去掉再添加吗?当然不应该这样。
我们应该优雅的进行路由跳转。
如何优雅的进行路由跳转实现
(取名凭啥我不能取一个ARouter哈哈…)
创建路由模块
首先三连创建arouter-annotations(JAVA LIB),arouter-compiler(JAVA LIB),
arouter
创建步骤请参考: 搭建Android客户端APP架构——《编译时APT技术》.
添加路由核心注解
package com.lyl.arouter.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.CLASS;
@Retention(CLASS)
@Target(ElementType.TYPE)
public @interface IsActivity {
//使用这个String可以跳转到本Activity
String value();
//要跳转到本Activity需要先验证的事情(拦截)默认是可以没有拦截
Class<? extends IInterceptor> interceptor() default DefaultInterceptor.class;
}
拦截器接口
package com.lyl.arouter.annotations;
public interface IInterceptor {
boolean interceptor(Object context);
}
默认拦截器实现
package com.lyl.arouter.annotations;
/**
* * @Description 默认拦截器
* * @CreateDate 2020/10/7
* * @Version 1.0
* * @Remark TODO
**/
public class DefaultInterceptor implements IInterceptor {
@Override
public boolean interceptor(Object context) {
return false;
}
}
添加Arouter核心
单例ARouter
private static ARouter instance = new ARouter();
//保存路由跳转数据
private Map<String, Class<? extends Activity>> mRouterData = new HashMap<>();
//保存拦截器数据
private Map<String, Class<? extends IInterceptor>> mInterceptorData = new HashMap<>();
//是否初始化
private boolean hasInit = false;
public static ARouter getInstance() {
return instance;
}
初始化
public boolean init(Application context) {
long time = System.currentTimeMillis();
try {
//根据指定需要扫描的包名获取所有类
List<String> classArr = ClassUtils.getFileNameByPackageName(context, AConstant.SCAN_ROUTER_PACKAGE);
time = (System.currentTimeMillis() - time);
Log.e(TAG, "time:" + time);
time = System.currentTimeMillis();
for (String clazz : classArr) {
if (clazz.contains(AConstant.SCAN_ROUTER_CLASS)) {
// mRouterClassName.add(clazz);
Log.e(TAG, "clazz:" + clazz);
//反射生成扫描到的类
ReflexUtils.genObjByClassName(clazz);
}
}
hasInit = true;
return true;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
time = (System.currentTimeMillis() - time);
Log.e(TAG, "time:" + time);
return false;
}
我们路由数据到Map的方法
//添加路由数据
public void addRouterData(String key, Class<? extends Activity> clazz) {
Log.e(TAG, "addRouterData:" + key);
mRouterData.put(key, clazz);
}
//添加拦截器
public void addInterceptor(String key, Class<? extends IInterceptor> clazz) {
Log.e(TAG, "addInterceptor:" + key);
mInterceptorData.put(key, clazz);
}
跳转的方法
//跳转Activity 这样设置是未在Application中初始化时跳转时会初始化一次,但初始化会耗费时间会用demo测试反馈在200ms以内具体看具体项目
public void jumpActivity(Context context, String jumpKey) {
if (hasInit) {
boolean isCanJump = isCanJump(context, mInterceptorData, jumpKey);
if (!isCanJump && !TextUtils.isEmpty(jumpKey) && mRouterData.containsKey(jumpKey)) {
Class<? extends Activity> clazz = mRouterData.get(jumpKey);
if (clazz != null) {
startActivity(context, clazz);
}
}
} else {
//这里没有添加穿透问题处理 一般跳转场景很少会涉及
if (init((Application) context.getApplicationContext())) {
jumpActivity(context, jumpKey);
}
}
}
private void startActivity(Context context, Class clazz) {
Intent intent = new Intent();
intent.setClass(context, clazz);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
private boolean isCanJump(Context context, Map<String, Class<? extends IInterceptor>> interceptorDatas, String key) {
if (!interceptorDatas.containsKey(key)) {
return false;
} else {
Class c = interceptorDatas.get(key);
try {
IInterceptor o = (IInterceptor) c.newInstance();
return o.interceptor(context);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
return false;
}
}
ARouterProcessor 生成添加数据的类
生成思想比较简单就是收集所有的注解,再根据每个模块添加的注解生成相应的添加到Map的类文件。
将数据生成到如下两个Map中
//保存路由跳转数据
private Map<String, Class<? extends Activity>> mRouterData = new HashMap<>();
//保存拦截器数据
private Map<String, Class<? extends IInterceptor>> mInterceptorData = new HashMap<>();
//核心部分
@AutoService(Processor.class)
public class ARouterProcessor extends BaseProcessor {
@Override
protected Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(IsActivity.class);
return annotations;
}
@Override
public boolean process(Set<? extends TypeElement> typeElements, RoundEnvironment env) {
Set<? extends Element> clazzElement = env.getElementsAnnotatedWith(IsActivity.class);
if (clazzElement != null && clazzElement.size() > 0) {
//构建ARouter需要的文件
buildARouterUtilFile(clazzElement);
}
return false;
}
}
//构建ARouterUtil
private void buildARouterUtilFile(Set<? extends Element> clazzElement) {
try {
String activityClassNameStr = "ARouterUtil_" + System.currentTimeMillis();
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityClassNameStr).addModifiers(Modifier.PUBLIC)
.addModifiers(Modifier.FINAL, Modifier.PUBLIC);
System.out.println(activityClassNameStr + clazzElement.size());
MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder();
constructorMethodBuilder.addModifiers(Modifier.PUBLIC);
for (Element object : clazzElement) {
String key = object.getAnnotation(IsActivity.class).value();
String packageName = mElementUtils.getPackageOf(object).getQualifiedName().toString();
String className = object.getSimpleName().toString();
//object.getAnnotation(IsActivity.class).interceptor().getCanonicalName();不能直接这么获取会报错
String interceptorName = getClassNameByAnnotation(object, IsActivity.class, "interceptor");
System.out.println(interceptorName);
constructorMethodBuilder.addStatement("ARouter.getInstance().addRouterData($S,$L.$L.class)", key, packageName, className);
if (interceptorName != null && interceptorName.length() > 0 && !interceptorName.equals("com.lyl.arouter.annotations.DefaultInterceptor")) {
constructorMethodBuilder.addStatement("ARouter.getInstance().addInterceptor($S,$L.class)", key, interceptorName);
}
}
classBuilder.addMethod(constructorMethodBuilder.build());
JavaFile javaFile = JavaFile.builder("com.lyl.arouter", classBuilder.build()).build();
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
//通过Annotation类获取类名
private String getClassNameByAnnotation(Element object, Class<? extends Annotation> clazz, String valueName) {
try {
AnnotationMirror annotationMirror = MoreElements.getAnnotationMirror(object, clazz).get();
Set<? extends Map.Entry<? extends ExecutableElement, ? extends AnnotationValue>> entrySet = annotationMirror.getElementValues().entrySet();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> value : entrySet) {
if (value.getKey().getSimpleName().contentEquals(valueName)) {
DeclaredType declaredType = (DeclaredType) value.getValue().getValue();
TypeElement typeElement = (TypeElement) declaredType.asElement();
return typeElement.getQualifiedName().toString();
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
组件化开发中的使用
//添加注解
//定义的value不重复即可可以自己要求按照某种协议规定好。
//跳转到这个Activity使用com.lyl.practice/MainActivity,跳转会需要先经过 LoginInterceptor拦截
@IsActivity(value = "com.lyl.practice/MainActivity", interceptor = LoginInterceptor.class)
public class MainActivity extends AppCompatActivity {
}
跳转
ARouter.getInstance().jumpActivity(this,"com.lyl.practice/MainActivity");
//这里可以加一个判断如果要到MainActivity就可以先跳转到LoginActivity
public class LoginInterceptor implements IInterceptor {
@Override
public boolean interceptor(Object context) {
ARouter.getInstance().jumpActivity((Context) context, "com.lyl.login/LoginActivity");
return true;
}
}
代码链接
demo链接: 代码链接.
吐槽
其实这个Demo写了一段时间了。本来写了一些关于组件化的内容。想着放这里不合适添加到组件化文章里了。