在路由框架之前,我们先了解什么是APT,并实践ButterKnife绑定findById的小功能。为什么先要讲解apt,因为路由的实现apt是核心的代码.看下面链接 APT 实践。
本文项目地址
为什么需要路由
我们知道路由就是实现页面的跳转,然而Android原生已经支持app页面间的跳转。
一般来说我们会这样写:
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("dataKey", "dataValue");
startActivity(intent);
复制代码
如果在封装一层这样写:
public class ManagerActivity extends BaseActivity {
public static void launch(Context context, int startTab) {
Intent i = new Intent(context, ManagerActivity.class);
i.putExtra(INTENT_IN_INT_START_TAB, startTab);
context.startActivity(i);
}
}
复制代码
其实无论那种实现方式,本质上都是生成Intent,然后通过context.startActivity/context.startActivityForResult来实现页面的跳转。但是这种方法的不足是:当包含多个模块,且模块之间没有相互依赖的跳转就会变的很困难,如果知道模块对应的类名和路径,可以通过Intent.setComponent(Component)方法启动其他模块的页面。同时还要写大量重复代码,要记录每个模块的类名和路径,如果类名和路径发生改变的话。。。。。。我相信你的内心肯定是崩溃的。
存在的一些缺点:
- 显示Intent:项目庞大以后,类依赖耦合太大,不适合组件化拆分
- 隐式Intent:协作困难,调用时候不知道调什么参数
- 每个注册了Scheme的Activity都可以直接打开,有安全风险
- AndroidMainfest集中式管理比较臃肿
- 无法动态修改路由,如果页面出错,无法动态降级
- 无法动态拦截跳转,譬如未登录的情况下,打开登录页面,登录成功后接着打开刚才想打开的页面
- H5、Android、iOS地址不一样,不利于统一跳转
页面路由的意义:
路由最先被应用于网络中,路由的定义可以通过互联的网络把信息从源地址传输到目的地址的过程。每个页面可以定义为一个统一的资源标示符,在网络当中能够被别人访问,也可以访问已经被定义了的页面。
路由常见的使用场景:
- App接收到一个通知,点击通知打开App的某个页面
- 浏览器App中点击某个链接打开App的某个页面
- App的H5活动页面打开一个链接,可能是H5跳转,也可能是跳转到某一个native页面
- 打开页面需要某些条件,先验证完条件,再去打开那个页面(如验证是否登录)
- 不合法的打开App的页面被屏蔽掉
- App内的跳转,可以减少手动构建Intent的成本,同时可以统一携带部分参数到下一个页面
- App存在就打开页面,不存在就去下载页面下载,只有Google的App Link支持
路由框架实现思路
通过上述的路由的应用和APT开发,相信你对APT有了一定的了解,那么路由框架要如何实现呢?实现思路是怎样的?
路由设计的思路
- 通过注解 Activity 类,注解处理器处理注解(APT)动态生成路由信息。
- 收集路由:通过定义的包名,找到所有动态生成的类,将路由信息存储到本地仓库 (rootMap).
- 页面跳转:根据注解的路由地址,从本地仓库中找到相关的路由信息,获取到要跳转的类,然后实现跳转。
只需要在使用路由的页面定义一个类注解@Router即可,页面路由的使用也相当简单
下面我们看下具体代码的实现思路
- 首先实现自定义注解,新建一个java lib 取名:primrouter-annotation
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Router {
/**
* 设置路由地址/main/text
* @return 地址
*/
String path();
/**
* 设置路由组,若为空以路由地址的第一个来作为组名main
* @return 路由组
*/
String group() default "";
}
复制代码
- 然后我们要清楚动态生成的类,其实主要就是存储一些跳转的Activity的类信息和路由地址等,同时为了逻辑更加清晰,给不同的module的进行分组。当路由跳转的时候可以通过路由group 得到分组表,然后通过路由地址path得到分组表中存储的路由对象,来实现跳转。
如以下自动生成的类:
module 存储的分组信息
public class PrimRouter$$Root$$moudle1 implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routers) {
routers.put("module", PrimRouter$$Group$$module.class);
}
}
复制代码
具体分组:
public class PrimRouter$$Group$$module implements IRouteGroup {
@Override
public void loadInto(Map<String, RouterMeta> atlas) {
atlas.put("/module/test",RouterMeta.build(RouterMeta.Type.ACTIVITY,MainActivity.class,"/module/test","module"));
atlas.put("/module/test2",RouterMeta.build(RouterMeta.Type.ACTIVITY,Main2Activity.class,"/module/test2","module"));
}
}
复制代码
RouterMeta 就是存储了一些路由分组表的信息
/**
* 存储的路由对象
*/
public class RouterMeta {
/**
* 路由的类型枚举
*/
public enum Type {
ACTIVITY, INSTANCE
}
/**
* 路由的类型
*/
private Type type;
/**
* 节点 activity
*/
private Element element;
/**
* 注解使用的类对象
*/
private Class<?> destination;
/**
* 路由地址
*/
private String path;
/**
* 路由组
*/
private String group;
public static RouterMeta build(Type type, Class<?> destination, String path, String
group) {
return new RouterMeta(type, null, destination, path, group);
}
public RouterMeta() {
}
public RouterMeta(Type type, Router router, Element element) {
this(type, element, null, router.path(), router.group());
}
public RouterMeta(Type type, Element element, Class<?> destination, String path, String group) {
this.type = type;
this.destination = destination;
this.element = element;
this.path = path;
this.group = group;
}
public void setType(Type type) {
this.type = type;
}
public void setElement(Element element) {
this.element = element;
}
public void setDestination(Class<?> destination) {
this.destination = destination;
}
public void setPath(String path) {
this.path = path;
}
public void setGroup(String group) {
this.group = group;
}
public Type getType() {
return type;
}
public Element getElement() {
return element;
}
public Class<?> getDestination() {
return destination;
}
public String getPath() {
return path;
}
public String getGroup() {
return group;
}
}
复制代码
- 动态生成Java class类,新建一个 java lib 取名:prim_compiler 这一步通过APT来实现。这一步其实很简单分析完要生成的类,然后通过javaopt 来动态生成,如果还不懂APT 可以看这一篇文章APT 实践。
gradle 配置:
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.auto.service:auto-service:1.0-rc4'
implementation 'com.squareup:javapoet:1.11.1'
implementation project(':primrouter-annotation')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
复制代码
新建一个processor类
//AnnotationProcessor register
@AutoService(Processor.class)
/**
* compiled java version {@link AbstractProcessor#getSupportedSourceVersion()}
*/
@SupportedSourceVersion(RELEASE_7)
/**
* Allow AnnotationProcessor process annotation,{@link AbstractProcessor#getSupportedAnnotationTypes()}
*/
@SupportedAnnotationTypes({"com.prim.router.primrouter_annotation.Router"})
/**
* 注解处理器接收的参数 {@link AbstractProcessor#getSupportedOptions()}
*/
@SupportedOptions({Consts.ARGUMENTS_NAME})
/**
* Router 注解处理器
*/
public class RouterProcessor extends AbstractProcessor {
private Messager messager;
/**
* 文件生成器 类/资源
*/
private Filer filer;
/**
*
*/
private Locale locale;
/**
* 获取传递的参数
*/
private Map<String, String> options;
/**
* compiled java version
*/
private SourceVersion sourceVersion;
/**
* 类型工具类
*/
private Types typeUtils;
/**
* 节点工具类
*/
private Elements elementUtils;
/**
* 模块的名称
*/
private String moduleName;
/**
* key 组名 value 类名
*/
private Map<String, String> rootMap = new TreeMap<>();
/**
* 分组 key 组名 value 对应组的路由信息
*/
private Map<String, List<RouterMeta>> groupMap = new HashMap<>();
private Log log;
/**
* init process environment utils
*
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
messager = processingEnvironment.getMessager();
log = Log.newLog(messager);
filer = processingEnvironment.getFiler();
locale = processingEnvironment.getLocale();
options = processingEnvironment.getOptions();
sourceVersion = processingEnvironment.getSourceVersion();
typeUtils = processingEnvironment.getTypeUtils();
elementUtils = processingEnvironment.getElementUtils();
// 获取传递的参数
if (!options.isEmpty()) {
moduleName = options.get(Consts.ARGUMENTS_NAME);
log.i("module:" + moduleName);
}
if (Utils.isEmpty(moduleName)) {
throw new NullPointerException("Not set Processor Parmaters.");
}
}
/**
* process annotation
*
* @param set The set of nodes that support processing annotations
* @param roundEnvironment Current or previous operating environment,annotation that can be found by this object
* @return true already processed ,follow-up will not be dealt with
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (!set.isEmpty()) {
//Set nodes annotated by Router
Set<? extends Element> annotatedWith = roundEnvironment.getElementsAnnotatedWith(Router.class);
if (annotatedWith != null) {
processRouter(annotatedWith);
}
return true;
}
return false;
}
/**
* 处理被注解的节点集合
*
* @param annotatedWith
*/
private void processRouter(Set<? extends Element> annotatedWith) {
RouterMeta routerMeta = null;
//获得Activity类的节点信息
TypeElement activity = elementUtils.getTypeElement(Consts.Activity);
//单个的节点
for (Element element : annotatedWith) {
// 获取类信息 如Activity类
TypeMirror typeMirror = element.asType();
// 获取节点的注解信息
Router annotation = element.getAnnotation(Router.class);
//只能指定的类上面使用
if (typeUtils.isSubtype(typeMirror, activity.asType())) {
//存储路由相关的信息
routerMeta = new RouterMeta(RouterMeta.Type.ACTIVITY, annotation, element);
} else if (typeUtils.isSubtype(typeMirror, activity.asType())) {
} else {
throw new RuntimeException("Just Support Activity Router!");
}
//检查是否配置group如果没有配置 则从path中截取组名
checkRouterGroup(routerMeta);
}
//获取 primrouter-core IRouterGroup 类节点
TypeElement routeGroupElement = elementUtils.getTypeElement(Consts.ROUTEGROUP);
//获取 primrouter-core IRouterRoot 类节点
TypeElement routeRootElement = elementUtils.getTypeElement(Consts.ROUTEROOT);
//生成 $$Group$$ 记录分组表
generatedGroupTable(routeGroupElement);
//生成 $$Root$$ 记录路由表
generatedRootTable(routeRootElement, routeGroupElement);
}
}
复制代码
检查路由地址是否配置正确,这一步可以自己定义相关的路由地址规则
/**
* 检查设置路由组
*
* @param routerMeta
*/
private void checkRouterGroup(RouterMeta routerMeta) {
if (routerVerify(routerMeta)) {
List<RouterMeta> routerMetas = groupMap.get(routerMeta.getGroup());
if (Utils.isEmpty(routerMetas)) {
routerMetas = new ArrayList<>();
routerMetas.add(routerMeta);
groupMap.put(routerMeta.getGroup(), routerMetas);
} else {
routerMetas.add(routerMeta);
}
} else {
log.i("router path no verify,please check");
}
}
/**
* 验证路由地址配置是否正确合法性
*
* @param routerMeta 存储的路由bean对象
* @return true 路由地址配置正确 false 路由地址配置不正确
*/
private boolean routerVerify(RouterMeta routerMeta) {
String path = routerMeta.getPath();
if (Utils.isEmpty(path)) {
throw new NullPointerException("@Router path not to be null or to length() == 0");
}
if (!path.startsWith("/")) {//路由地址必须以/开头
throw new IllegalArgumentException("@Router path must / first");
}
String group = routerMeta.getGroup();
if (Utils.isEmpty(group)) {
String defaultGroup = path.substring(1, path.indexOf("/", 1));
//截取的还是为空
if (Utils.isEmpty(defaultGroup)) {
return false;
}
//设置group
routerMeta.setGroup(defaultGroup);
}
return true;
}
复制代码
生成分组表class 类
/**
* 生成分组表class 类
*
* @param routeGroupElement primrouter-core IRouterGroup 类节点
*/
private void generatedGroupTable(TypeElement routeGroupElement) {
//创建参数类型 Map<String,RouterMeta>
ParameterizedTypeName atlas = ParameterizedTypeName.get(ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouterMeta.class));
//创建参数 Map<String,RouterMeta> atlas
ParameterSpec altlas = ParameterSpec
.builder(atlas, Consts.GROUP_PARAM_NAME)//参数名
.build();//创建参数
//遍历分组 每一个分组 创建一个 $$Group$$类
for (Map.Entry<String, List<RouterMeta>> entry : groupMap.entrySet()) {
MethodSpec.Builder builder = MethodSpec.methodBuilder(Consts.GROUP_METHOD_NAME)//函数名
.addModifiers(PUBLIC)//作用域
.addAnnotation(Override.class)//添加一个注解
.addParameter(altlas);//添加参数
//Group组中
List<RouterMeta> groupData = entry.getValue();
//遍历 生成函数体
for (RouterMeta meta : groupData) {
//$S = String
//$T = class
//添加函数体
builder.addStatement(Consts.GROUP_PARAM_NAME+".put($S,$T.build($T.$L,$T.class,$S,$S))",
meta.getPath(),
ClassName.get(RouterMeta.class),
ClassName.get(RouterMeta.Type.class),
meta.getType(),
ClassName.get((TypeElement) meta.getElement()),
meta.getPath(),
meta.getGroup());
}
MethodSpec loadInto = builder.build();//函数创建完成loadInto();
String groupClassName = Consts.GROUP_CLASS_NAME + entry.getKey();
TypeSpec typeSpec = TypeSpec.classBuilder(groupClassName)//类名
.addSuperinterface(ClassName.get(routeGroupElement))//实现接口IRouteGroup
.addModifiers(PUBLIC)//作用域
.addMethod(loadInto)//添加方法
.build();//类创建完成
//生成Java文件
JavaFile javaFile = JavaFile
.builder(Consts.PAGENAME, typeSpec)//包名和类
.build();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
rootMap.put(entry.getKey(), groupClassName);
}
}
复制代码
生成路由表class 类
/**
* 生成路由表class 类
*
* @param routeRootElement
*/
private void generatedRootTable(TypeElement routeRootElement, TypeElement routeGroupElement) {
类型 Map<String,Class<? extends IRouteGroup>> routes>
ParameterizedTypeName atlas = ParameterizedTypeName.get(ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(routeGroupElement))));
//创建参数 Map<String,Class<? extends IRouteGroup>>> routes
ParameterSpec altlas = ParameterSpec
.builder(atlas, Consts.ROOT_PARAM_NAME)//参数名
.build();//创建参数
//public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
//函数 public void loadInfo(Map<String,Class<? extends IRouteGroup>> routes> routes)
MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder
(Consts.ROOT_METHOD_NAME)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(altlas);
//函数体
for (Map.Entry<String, String> entry : rootMap.entrySet()) {
loadIntoMethodOfRootBuilder.addStatement(Consts.ROOT_PARAM_NAME + ".put($S, $T.class)", entry
.getKey(), ClassName.get(Consts.PAGENAME, entry.getValue
()));
}
//生成 $Root$类
String rootClassName = Consts.ROOT_CLASS_NAME + moduleName;
try {
JavaFile.builder(Consts.PAGENAME,
TypeSpec.classBuilder(rootClassName)
.addSuperinterface(ClassName.get(routeRootElement))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfRootBuilder.build())
.build()
).build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
复制代码
- 检查动态生成的类是否正确
在app的gradle 中配置:
annotationProcessor project(':primrouter-compiler')
implementation project(':primrouter-annotation')
复制代码
在MainActivity中添加注解
@Router(path = "/app/main")
public class MainActivity extends AppCompatActivity {
}
复制代码
会生成以下两个类:
则说明动态生成类正确。
- 最核心的部分,编写核心代码库:primrouter-core 首先我们需要在Application中初始化 路由表,也就是说将生成的路由表存到本地的仓库HashMap 中
如何收集路由表?我们可以根据生成路由表的类的包名来找到路由表的类名,因为生成类的包名是我们自己定制的,那么我们完全可以找到这个包下面的所有的类。 代码如下:
public static final String PAGENAME = "com.prim.router.generated";
public static final String GROUP_CLASS_NAME = "PrimRouter$$Group$$";
public static final String ROOT_CLASS_NAME = "PrimRouter$$Root$$";
复制代码
/**
* 根据包名 找到包下的类
*
* @param application
* @param pageName
* @return
*/
public static Set<String> getFileNameByPackageName(Application application, final String pageName) throws InterruptedException {
final Set<String> classNams = new HashSet<>();
List<String> sourcePath = getSourcePath(application);//apk 的资源路径
//使用同步计数器判断均处理完成
final CountDownLatch countDownLatch = new CountDownLatch(sourcePath.size());
ThreadPoolExecutor threadPoolExecutor = DefaultPoolExecutor.newDefaultPoolExecutor(sourcePath.size());
for (final String path : sourcePath) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
DexFile dexFile = null;
try {
//加载apk中的dex遍历 获得所有包名为pageName的类
dexFile = new DexFile(path);
Enumeration<String> entries = dexFile.entries();
while (entries.hasMoreElements()) {
String className = entries.nextElement();
if (className.startsWith(pageName)) {
classNams.add(className);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != dexFile) {
try {
dexFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//释放1个
countDownLatch.countDown();
}
}
});
}
//等待执行完成
countDownLatch.await();
return classNams;
}
复制代码
找到类之后,我们就可以收集路由来存储到本地仓库了
/**
* 初始化-收集路由表,必须在Application中初始化
*
* @param application
*/
public void initRouter(Application application) {
this.application = application;
try {
loadRouteTable();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 收集加载路由表
*/
private void loadRouteTable() throws InterruptedException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
if (application == null) {
throw new IllegalArgumentException("initRouter(Application application) must Application init");
}
//获取所有APT 生成的路由类的全类名
Set<String> routerMap = Utils.getFileNameByPackageName(application, PAGENAME);
for (String className : routerMap) {
//获取root中注册的路由分组信息,存储到本地仓库中。
if (className.startsWith(PAGENAME + "." + ROOT_CLASS_NAME)) {
Object instance = Class.forName(className).getConstructor().newInstance();
if (instance instanceof IRouteRoot) {
IRouteRoot routeRoot = (IRouteRoot) instance;
routeRoot.loadInto(Depository.rootMap);
}
}
Log.e(TAG, "路由表分组信息 「");
for (Map.Entry<String, Class<? extends IRouteGroup>> entry : Depository.rootMap.entrySet()) {
Log.e(TAG, "【key --> " + entry.getKey() + ": value --> " + entry.getValue() + "]");
}
Log.e(TAG, " 」");
}
}
复制代码
- 路由的跳转实现,这一步同样也很简单,既然路由的信息我们都存储起来了,那么就可以根据路由地址来找到相关的Activity全类名,然后就可以跳转了,核心逻辑如下:
public JumpCard jump(String path) {
if (TextUtils.isEmpty(path)) {
throw new RuntimeException("路由地址无效");
} else {
return new JumpCard(path, getGroupName(path));
}
}
public JumpCard jump(String path, String group) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new RuntimeException("路由地址无效");
} else {
return new JumpCard(path, group);
}
}
public JumpCard jump(String path, String group, Bundle bundle) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new RuntimeException("路由地址无效");
} else {
return new JumpCard(path, group, bundle);
}
}
public Object navigation(final Context context, final JumpCard jumpCard, final int requestCode, Object o1) {
if (context == null) {
return null;
}
produceJumpCard(jumpCard);
switch (jumpCard.getType()) {
case ACTIVITY:
final Intent intent = new Intent(context, jumpCard.getDestination());
intent.putExtras(jumpCard.getExtras());
if (jumpCard.getFlags() != -1) {
intent.setFlags(jumpCard.getFlags());
} else if (!(context instanceof Activity)) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
mHandler.post(new Runnable() {//在主线程中跳转
@Override
public void run() {
//可能需要返回码
if (requestCode > 0) {
ActivityCompat.startActivityForResult((Activity) context, intent,
requestCode, jumpCard.getOptionsBundle());
} else {
ActivityCompat.startActivity(context, intent, jumpCard
.getOptionsBundle());
}
if ((0 != jumpCard.getEnterAnim() || 0 != jumpCard.getExitAnim()) &&
context instanceof Activity) {
//老版本
((Activity) context).overridePendingTransition(jumpCard
.getEnterAnim()
, jumpCard.getExitAnim());
}
//跳转完成
// if (null != callback) {
// callback.onArrival(postcard);
// }
}
});
break;
}
return null;
}
/**
* 准备跳卡
*/
private void produceJumpCard(JumpCard card) {
//获取仓库中存储的 具体每个组的信息
RouterMeta routerMeta = Depository.groupMap.get(card.getPath());
if (routerMeta == null) {//没有记录在仓库中,从路由表的分组信息中查找
Class<? extends IRouteGroup> groupClass = Depository.rootMap.get(card.getGroup());
if (groupClass == null) {
throw new RuntimeException("没有找到对应的路由表信息:" + card.getGroup() + ":" + card.getPath());
}
IRouteGroup routeGroup = null;
try {
routeGroup = groupClass.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("路由映射表信息记录失败");
}
routeGroup.loadInto(Depository.groupMap);
produceJumpCard(card);//再次进入
} else {
//设置要跳转的类
card.setDestination(routerMeta.getDestination());
//设置要跳转的类型
card.setType(routerMeta.getType());
}
}
/**
* 通过路由地址获取分组名
*
* @param path
* @return
*/
private String getGroupName(String path) {
if (!path.startsWith("/")) {
throw new RuntimeException(path + ": 不能有效的提取group");
}
try {
String group = path.substring(1, path.indexOf("/", 1));
if (TextUtils.isEmpty(group)) {
throw new RuntimeException("不能有效的提取group");
}
return group;
} catch (Exception e) {
return null;
}
}
复制代码
- 核心代码基本写完那么我们来测试以下:
app 跳转到一个到module Activity,和 module 之间的跳转。
@Router(path = "/app/main")
public class MainActivity extends AppCompatActivity {
private Button appToModule;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
appToModule = findViewById(R.id.appToModule);
appToModule.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
module1();
}
});
}
public void module1() {
PrimRouter.getInstance().jump("/module/test2").navigation(this);
}
}
复制代码
module
@Router(path = "/module/test2")
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
}
复制代码