Android应用组件化各个组件页面之间要实现跳转使用路由是一个很好的选择。本文将实现一个比较轻量级的路由组件,主要涉及以下知识:

  • Annotation (声明路由目标信息)
  • AnnotationProcessor (处理注解)
  • JavaPoet (生成Java文件)
  • UriMatcher (匹配Uri)

本文将使用Java注解来实现一个简单的路由组件,主要从这几方面来讲解:

  1. 注解定义与使用
  2. 注解跳转服务
  3. 使用AnnotationProcessor处理注解、生成文件
  4. Uri的匹配
  5. 安全参数
  6. 注解跳转服务的开发

由于使用AnnotationProcessor,所以整个路由可分为以下模块:

  • lib-component-router (Android工程)
  • lib-component-router-annotation (存放注解)
  • lib-component-router-compiler (注解处理)

注解定义

由于我们的路由组件相对简单,主要定义以下注解:

  • UriDestination (声明路由目标信息)
  • DestinationUri (定义Uri路径)
  • DestinationArgument (参数声明)

声明目标路由注解


@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface UriDestination { String name(); DestinationUri uri(); DestinationArgument[] out() default {}; DestinationArgument[] in() default {};
}

该注解主要用来注解Activity,声明一个路由目标的信息,各参数说明如下:

  • authority (匹配Uri authority)
  • scheme (匹配Uri scheme)
  • path (匹配Uri路径)
  • out (输出参数)
  • in (输入参数)

路由Uri注解


@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.ANNOTATION_TYPE)
public @interface DestinationUri { String authority() default "imxingzhe.com"; String scheme() default "xingzhe"; String path() default ""; }

该路由主要用于声明路由目标的Uri信息,各参数说明如下:

  • authority (匹配android.net.Uri authority)
  • scheme (匹配android.net.Uri scheme)
  • path (匹配路由路径信息)

路由参数注解

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.ANNOTATION_TYPE)
public @interface DestinationArgument { String key(); boolean require() default false; Class<?> type();
}

该注解主要用于声明路由的输入、输出参数信息,各参数说明如下:

  • key (参数的名称)
  • require (是否是必需的参数)
  • type (参数的类型)

路由组件功能实现

目标Action

public interface DestinationAction {

    Context getContext();

    int getFlags();

    int getRequestCode();

    boolean getUriOnly();

    Bundle getArguments();

}

Action可理解为一次跳转动作,使用端通过Builder模式生成Action实例,然后再通过DestinationService执行给定的动作。

跳转服务逻辑

public interface DestinationService {

    void start(DestinationAction destinationAction);

}

此接口只包含一个start方法用于执行DestinationAction逻辑。主要实现跳转方式是使用类式ContentProvider的UriMatcher方式来实现。首先声明一个抽象Service:

public abstract class AbstractUriDestinationService implements DestinationService {

    private final static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
private static boolean isDestinationDefinitionResolved; @Override
public void start(DestinationAction ){
...
} ... protected abstract List<DestinationDefinition> getDestinationDefinitions();
}

方法getDestinationDefinitions由子类来实现,主要提供此路由目标的相关信息, DestinationDefinition类如下:

public class DestinationDefinition {

    private final String name;
private final Class<?> destination;
private final List<DestinationArgumentDefinition> inArgumentDefinitions;
private final List<DestinationArgumentDefinition> outArgumentDefinitions; public DestinationDefinition(String name, Class<?> destination, List<DestinationArgumentDefinition> inArgumentDefinitions, List<DestinationArgumentDefinition> outArgumentDefinitions) {
this.name = name;
this.destination = destination;
this.inArgumentDefinitions = inArgumentDefinitions;
this.outArgumentDefinitions = outArgumentDefinitions;
} public String getName() {
return name;
} public Class<?> getDestination() {
return destination;
} public List<DestinationArgumentDefinition> getInArgumentDefinitions() {
return inArgumentDefinitions;
} public List<DestinationArgumentDefinition> getOutArgumentDefinitions() {
return outArgumentDefinitions;
}
}

AbstractUriDestinationService类中的start方法实现真正的跳转逻辑:

public abstract class AbstractUriDestinationService implements DestinationService {

    private final static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
private static boolean isDestinationDefinitionResolved; @Override
public void start(DestinationAction destinationAction) {
List<DestinationDefinition> destinationDefinitions = getDestinationDefinitions();
resolveDestinationDefinition(destinationDefinitions); Context context = destinationAction.getContext(); if (context == null) {
throw new IllegalArgumentException("content == null");
} PackageManager packageManager = context.getPackageManager(); if (destinationAction instanceof UriDestinationAction) {
Uri uri = ((UriDestinationAction) destinationAction).getUri();
int index = matcher.match(uri); if (UriMatcher.NO_MATCH == index || index >= destinationDefinitions.size()) {
throw new IllegalStateException("Not found destination for : " + uri);
} DestinationDefinition destinationDefinition = destinationDefinitions.get(index);
List<DestinationArgumentDefinition> destinationArgumentDefinitions = destinationDefinition.getInArgumentDefinitions();
for (DestinationArgumentDefinition argumentDefinition : destinationArgumentDefinitions) {
Bundle args = destinationAction.getArguments();
if (argumentDefinition.isRequire() && !args.containsKey(argumentDefinition.getKey())) {
throw new IllegalArgumentException("No such key: " + argumentDefinition.getKey());
} } Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
if (packageManager.resolveActivity(intent, 0) == null) {
if (destinationAction.getUriOnly()) {
throw new IllegalStateException("Not found activity for : " + uri);
} else {
intent = new Intent(context, destinationDefinition.getDestination()); if (packageManager.resolveActivity(intent, 0) == null) {
throw new IllegalStateException("Not found activity for : " + uri);
}
}
} intent.addFlags(destinationAction.getFlags());
Bundle args = destinationAction.getArguments();
if (args != null) {
intent.putExtras(args);
} if (context instanceof Activity) {
((Activity) context).startActivityForResult(intent, destinationAction.getRequestCode());
} else {
context.startActivity(intent);
} } else {
throw new IllegalStateException("Not support operate");
}
} private static void resolveDestinationDefinition(List<DestinationDefinition> destinationDefinitions) {
if (isDestinationDefinitionResolved) {
return;
} int index = 0;
for (DestinationDefinition destinationDefinition : destinationDefinitions) {
if (destinationDefinition instanceof UriDestinationDefinition) {
Uri uri = ((UriDestinationDefinition) destinationDefinition).getUri(); String stringForUri = uri.toString();
String path = uri.getPath(); int pathIndex = stringForUri.indexOf(path);
if (pathIndex != -1) {
path = stringForUri.substring(
pathIndex,
stringForUri.length()
);
} matcher.addURI(uri.getAuthority(), path, index++);
}
} isDestinationDefinitionResolved = true;
} protected abstract List<DestinationDefinition> getDestinationDefinitions();
}

这样通过实现AbstractUriDestinationService类,提供相应的DestinationDefinition就可以实现路由的跳转功能,由于使用的注册我们可以使用AnnotationProcessor来处理注解生成DestinationService的实现类。

源码下载: https://github.com/yjwfn/AndroidRouterSample



《架构文摘》每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性 能、高稳定)、大数据、机器学习等各个热门领域。

Android组件化路由实践的更多相关文章

  1. Android 组件化最佳实践 ARetrofit 原理

    本文首发于 vivo互联网技术 微信公众号 https://mp.weixin.qq.com/s/TXFt7ymgQXLJyBOJL8F6xg作者:朱壹飞 ARetrofit 是一款针对Android ...

  2. Android组件化最佳实践 ARetrofit原理

    ARetrofit原理讲原理之前,我想先说说为什么要ARetrofit.开发ARetrofit这个项目的思路来源其实是Retrofit,Retrofit是Square公司开发的一款针对Android网 ...

  3. Android组件化开发实践

    转载请注明出处:http://blog.csdn.net/crazy1235/article/details/76533115 http://mdsa.51cto.com/art/201707/544 ...

  4. Android组件化

    附:Android组件化和插件化开发 App组件化与业务拆分那些事 Android项目架构之业务组件化 Android组件化核心之路由实现 Android组件化开发实践

  5. Android组件化框架设计与实践

    在目前移动互联网时代,每个 APP 就是流量入口,与过去 PC Web 浏览器时代不同的是,APP 的体验与迭代速度影响着用户的粘性,这同时也对从事移动开发人员提出更高要求,进而移动端框架也层出不穷. ...

  6. 我所理解的Android组件化之通信机制

    之前写过一篇关于Android组件化的文章,<Android组件化框架设计与实践>,之前没看过的小伙伴可以先点击阅读.那篇文章是从实战中进行总结得来,是公司的一个真实项目进行组件化架构改造 ...

  7. 教你打造一个Android组件化开发框架

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 CC:Component Caller,一个android组件化开发框架, 已开源,github地址:https://github ...

  8. 得到、微信、美团、爱奇艺APP组件化架构实践

    一.背景 随着项目逐渐扩展,业务功能越来越多,代码量越来越多,开发人员数量也越来越多.此过程中,你是否有过以下烦恼? 项目模块多且复杂,编译一次要5分钟甚至10分钟?太慢不能忍? 改了一行代码 或只调 ...

  9. Android组件化demo实现以及遇坑分享

    首先贴出demo的github地址:GitHub - TenzLiu/TenzModuleDemo: android组件化demo 作者:TenzLiu原文链接:https://www.jianshu ...

随机推荐

  1. unity之截屏功能

    1.全屏截图 方法一:在unity的API中,unity给我们提供了一个现成的API  :  Application.CaptureScreenshot(imagename). 但是这个API虽然简单 ...

  2. Azure Devops: COPY failed: stat /var/lib/docker/tmp/docker-builder268095359/xxxxxxx.csproj no such file or directory

    在Azure Devops中部署docker镜像时,  出现COPY failed: stat /var/lib/docker/tmp/docker-builder268095359/xxxxxxx. ...

  3. Codeforces 975D

    题意略. 思路:我们来写一下公式: P1:(x1 + t * Vx1,y1 + t * Vy1)                P2:(x2 + t * Vx2,y2 + t * Vy2) x1 + ...

  4. Java Server Page

    Java Server Page 一.JSP起源 在很多动态网页中,绝大部分内容都是固定不变的,只有局部内容需要动态产生和改变. 如果使用Servlet程序来输出只有局部内容需要动态改变的网页,其中所 ...

  5. MSIL实用指南-struct的生成和操作

    struct(结构)是一种值类型,用于将一组相关的信息变量组织为一个单一的变量实体.所有的结构都继承自System.ValueType类,因此是一种值类型,也就是说,struct实例分配在线程的堆栈( ...

  6. 分布式任务调度框架 Azkaban —— Flow 2.0 的使用

    一.Flow 2.0 简介 1.1 Flow 2.0 的产生 Azkaban 目前同时支持 Flow 1.0 和 Flow2.0 ,但是官方文档上更推荐使用 Flow 2.0,因为 Flow 1.0 ...

  7. javaScript 基础知识汇总(六)

    1.基本类型与对象的区别 基本类型:是原始类型的中的一种值. 在JavaScript中有6中基本类型:string number  boolean  symbol  null  undefined 对 ...

  8. 到底什么是故事点(Story Point)?

    故事点是一个度量单位,用于表示完成一个产品待办项或者其他任何某项工作所需的所有工作量的估算结果. 当采用故事点估算时,我们为每个待办项分配一个点数.待办项估算结果的原生数据并不重要,我们只关注最后得到 ...

  9. POJ 3207 Ikki's Story IV - Panda's Trick 2-sat模板题

    题意: 平面上,一个圆,圆的边上按顺时针放着n个点.现在要连m条边,比如a,b,那么a到b可以从圆的内部连接,也可以从圆的外部连接.给你的信息中,每个点最多只会连接的一条边.问能不能连接这m条边,使这 ...

  10. hdu 3709 Balanced Number(数位dp)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3709 题意:给定区间[a,b],求区间内平衡数的个数.所谓平衡数即有一位做平衡点,左右两边数字的力矩相 ...