前面介绍到组件化的ARouter路由设计和参数传递设计。只是功能进行了实现,没有考虑到进一步的封装,比如界面传参,之前是用如下方式实现:
new Main_MainActivity$$Parameter().loadParams(this);
这样看起来会比较难以理解,因为使用者并不知道生成了Main_MainActivity$$Parameter()这个类。同样的界面跳转之前使用如下方式:
ARouter$$Group$$main group = new ARouter$$Group$$main();
Map<String, Class<? extends ARouterLoadPath>> loadMap = group.loadGroup();
Class<? extends ARouterLoadPath> clazz = loadMap.get("main");
ARouterLoadPath aRouterGroupPath = clazz.newInstance();
Map<String, RouterBean> map = aRouterGroupPath.loadPath();
RouterBean routerBean = map.get("/main/Main_MainActivity");
if(routerBean != null){
Intent mIntent = new Intent(this,routerBean.getClazz());
mIntent.putExtra("name","qbbb");
mIntent.putExtra("agex",121);
startActivity(mIntent);
}
使用者也是一脸懵逼,用起来很麻烦。所以就需要对它们进行进一步的封装。
一、ParamsManager
该类主要处理参数传递相关。代码如下:
public class ParamsManager {
public static volatile ParamsManager instance;
//Lru缓存 key:类名 value:参数加载接口
private LruCache<String, ARouterLoadParameters> paramsCache;
//拼接的字符串 如:Main_MainActivity$$$$Parameter
public static final String PARAMS_PREFIX = "$$Parameter";
public static ParamsManager getInstance(){
if(instance == null){
synchronized (ParamsManager.class){
if(instance == null){
instance = new ParamsManager();
}
}
}
return instance;
}
private ParamsManager(){
//最大缓存100
paramsCache = new LruCache<>(100);
}
public void load(Object obj){
if(obj == null) return;
//拿到当前activity的全名。
String className = obj.getClass().getName();
try {
//从缓存中取,避免多次反射
ARouterLoadParameters parametersLoad = paramsCache.get(className);
//缓存中拿不到
if(parametersLoad == null){
Log.e("参数类名",className + PARAMS_PREFIX);
//拼接成类Main_MainActivity$$Parameter
Class<?> clazz = Class.forName(className + PARAMS_PREFIX);
//接口=接口的实现类
parametersLoad = (ARouterLoadParameters) clazz.newInstance();
//放到缓存中
paramsCache.put(className,parametersLoad);
}
parametersLoad.loadParams(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
外部提供单例调用,通过load()方法完成参数注解的取值。需要注意的是,需要将每个activity的加载放到缓存中,避免不必要的反射。这样我们就可以在activity中如下使用:
ParamsManager.getInstance().load(this);
二、BundleManager
为什么要引入BundleManager,这是因为在跳转的时候我们可能需要传递参数,也可能会从下一界面回调信息(比如:startActivityForResult)。代码如下:
/**
* 跳转参数管理类
*/
public class BundleManager {
private Bundle bundle = new Bundle();
//是否结果回调
private boolean isResult = false;
public Bundle getBundle() {
return bundle;
}
public boolean isResult() {
return isResult;
}
public BundleManager withString(String key,String value){
bundle.putString(key,value);
return this;
}
public BundleManager withResultString(String key,String value){
bundle.putString(key,value);
isResult = true;
return this;
}
public BundleManager withInt(String key,int value){
bundle.putInt(key,value);
return this;
}
/**
* 没有回调的跳转
* @param context
* @return
*/
public Object navigation(Context context){
return navigation(context,-1);
}
/**
* 交给ARouterManager处理跳转,将this传过去就可以拿到bundle和isResult
* @param context
* @param code 可以是requestCode ,也可以是resultCode 根据isResult来定
* @return
*/
public Object navigation(Context context,int code){
return ARouterManager.getInstance().navigation(context,code,this);
}
}
分析:
1、类中有一个字段isResult ,它的作用是控制当前是否需要界面回调。比如上面的withResultString()方法,将它置为true。
2、navigation()方法,参数为当前activity的上下文,还有一个重载方法,参数多了一个code,它既可以作为requestCode,也可以作为resultCode。当isResult 为false的时候,如果code大于0,它就作为requestCode;如果isResult 为true,它就作为resultCode。当code为-1的时候,就是普通的跳转,不需要回调。
3、最终会调用ARouterManager.getInstance().navigation(context,code,this)方法,交给它进行跳转。
三、ARouterManager
负责主要的跳转业务
public class ARouterManager {
public static volatile ARouterManager instance;
//组名
private String group;
//path路径
private String path;
//缓存 key:组名 value:路由组接口
private LruCache<String, ARouterLoadGroup> groupCache;
//key:路径地址 value:路由地址接口
private LruCache<String, ARouterLoadPath> pathCache;
private static final String GROUP_PREFIX = "ARouter$$Group$$";
public static ARouterManager getInstance() {
if (instance == null) {
synchronized (ARouterManager.class) {
if (instance == null) {
instance = new ARouterManager();
}
}
}
return instance;
}
private ARouterManager() {
groupCache = new LruCache<>(100);
pathCache = new LruCache<>(100);
}
/**
* 传递路由地址
*/
public BundleManager build(String path) {
if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
throw new IllegalArgumentException("path路径未按照规范,如:/app/MainActivity");
}
group = subGroupName(path);
this.path = path;
return new BundleManager();
}
private String subGroupName(String path) {
//如/MainActivity 不合规范
if (path.lastIndexOf("/") == 0) {
throw new IllegalArgumentException("path路径未按照规范,如:/app/MainActivity");
}
//第一个/和下一个/之间截取group
String finalGroup = path.substring(1, path.indexOf("/", 1));
if (TextUtils.isEmpty(finalGroup)) {
throw new IllegalArgumentException("path路径未按照规范,如:/app/MainActivity");
}
return finalGroup;
}
public Object navigation(Context context, int code, BundleManager bundleManager) {
//group文件生成的路径
try {
ARouterLoadGroup groupLoad = groupCache.get(group);
if (groupLoad == null) {
String className = context.getPackageName() + ".apt." + GROUP_PREFIX+group;
Log.e("group文件路径", className);
Class<?> clazz = Class.forName(className);
//接口=接口的实现类
groupLoad = (ARouterLoadGroup) clazz.newInstance();
groupCache.put(group, groupLoad);
}
if(groupLoad.loadGroup().isEmpty()) {
throw new RuntimeException("路由组加载失败");
}
ARouterLoadPath pathLoad = pathCache.get(path);
if(pathLoad == null){
Class<? extends ARouterLoadPath> clazz = groupLoad.loadGroup().get(group);
if(clazz != null) pathLoad = clazz.newInstance();
pathCache.put(path,pathLoad);
}
if(pathLoad.loadPath().isEmpty()) {
throw new RuntimeException("路由地址加载失败");
}
//拿到routerBean对象
RouterBean routerBean = pathLoad.loadPath().get(path);
if(routerBean != null){
switch (routerBean.getType()){
case ACTIVITY:
Intent intent = new Intent();
intent.putExtras(bundleManager.getBundle());
intent.setClass(context,routerBean.getClazz());
//回调的数据 setResult() 然后finish()
if(bundleManager.isResult()){
((Activity)context).setResult(code,intent);
((Activity)context).finish();
}
//使用startActivityForResult
if(code>0){
((Activity)context).startActivityForResult(intent,code);
}else{
context.startActivity(intent);
}
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
分析:
1、build()方法,参数为当前activity的路径path,从path中能够截取当前的组名group,返回结果是BundleManager对象。
2、拿到BundleManager对象可以进行参数的传递,比如可以链式调用withString()方法。
3、参数设置完成最后调用navigation()方法:根据group拿到路由组接口,通过路由组接口的loadGroup()方法拿到路由地址接 口,进而通过路由地址接口的loadPath()方法拿到RouterBean对象(封装了类信息)。
4、判断当前类是否是Activity类型。满足条件通过intent跳转,根据isResult和code判断是回调结果还是进行界面传值。
五、使用
1、主界面传递参数,调用如下:
2、main/Main_MainActivity回调如下:
3、app/MainActivity的接收结果如下:
4、打印信息如下:
如果发现回调结果为空,请在接受回调结果的界面添加启动模式 android:launchMode=“singleTask”
六、模块间如何实现数据的完全解耦
比如,我想从main模块获取personal模块的一张图片,常规的做法是在common模块复制一张一样的图片放在res/drawable目录下,那如果我想获取personal模块的一个接口返回数据呢?这样就需要其他的方法了。
1、请看下面一个例子
(1)定义一个接口,如下:
(2)定义一个接口实现类,并返回一张图片
(3)在activity中使用如下方式就可以获取这张图片
没错,重点就是:接口=接口的实现类。
2、基于上面的例子,实现的思路大致是这样的:
- 定义可扩展接口Call,是一个空接口,什么也不做,供子类去继承,用于在javapoet生成文件过程中判断接口的类型
- 在common模块,定义接口DrawableCall,实现Call接口,提供可重写方法getDrawable()
- 在main模块,定义接口DrawableCall的实现类MainDrawableImpl,重写方法,并返回该模块一张图片。该类使用ARouter注解,将该实现类添加到path路由表中,这样其他模块才能根据path路径拿到该实现类
- 假如我们是在app主模块需要该图片,就需要定义DrawableCall变量,并且使用参数注解,注解的key与第3步注解参数一致。
3、代码实现
(1)DrawableCall接口
public interface DrawableCall extends Call {
int getDrawable();
}
(2)main模块实现该接口,使用ARouter注解
@ARouter(path = "/main/getDrawable")
public class MainDrawableImpl implements DrawableCall {
@Override
public int getDrawable() {
return R.drawable.ic_add_shopping_cart_black_24dp;
}
}
这样编译出来的代码如下:
(3)ARouterCompiler注解处理器中,构造RouterBean的时候,判断是否为接口类型CALL
(4)ParameterCompiler注解处理器中,在构造loadPath方法过程中添加CALL类型,在ParameterFactory类中添加代码如下:
用到了"接口=接口的实现类"。这样编译出来的代码如下:
(5)ARouterManager管理类中,在navigation()方法,判断为CALL类型
这里的clazz为MainDrawableImpl类,return返回的为Call的实现类MainDrawableImpl。
(6)在app的MainActivity中,添加参数注解
使用如下:
好了,关于组件化的所有内容,至此已全部结束!!!