Android开源框架系列-组件化之ARouter Activity页面参数传递源码分析

前言

在上一篇中我们分析了ARouter无参数页面跳转的实现方式,今天我们来看看有参数传递的页面跳转是如何实现的,今天的分析基于上一篇的源码,上一篇分析过的内容本篇不再进行重复分析,感兴趣的读者请自行移步Android开源框架系列-组件化之ARouter Activity页面无参数跳转源码分析,先了解基本的页面跳转逻辑。

我们以如下示例代码开始今天的分析。

scss 复制代码
//页面跳转
ARouter.getInstance().build("/kotlin/test").withString("name", "老王").withInt("age", 23).navigation();

//参数接收
ARouter.getInstance().inject(this)

withString、withInt

build、navigation等方法的实现我们已经分析过,所以我们直接看不同的点,ARouter传递参数和intent的putString、putInt有点类似,我们来看一下:

less 复制代码
public Postcard withString(@Nullable String key, @Nullable String value) {
    mBundle.putString(key, value);
    return this;
}
public Postcard withInt(@Nullable String key, int value) {
    mBundle.putInt(key, value);
    return this;
}

上一篇分析build方法时,我们提到过,build方法的返回值是一个Postcard类型的对象,所以withString、withInt方法是Postcard内的方法,Postcard内部有一个mBundle成员变量,它就是android的Bundle类的对象,可以看到当withString、withInt方法调用的时候,就是将参数存入了bundle中。

参数存入之后执行到navigation方法,然后通过如下的方式存入intent,再通过startActivity方法打开目标页面。

ini 复制代码
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
startActivity(requestCode, currentContext, intent, postcard, callback);

所以参数传递的源码就到此为止了,那么页面是如何接收这些参数的?既然参数本质上还是通过intent传递的,那么我们推测一下,接收的逻辑是不是最终还是通过intent?

inject

我们开始分析参数接收的逻辑,ARouter.getInstance().inject(this)方法是在目标Activity页面调用的,所以传入的this对象其实就是目标Activity。

在分析inject方法之前,注意到在目标Activity中有这么几个变量定义:

less 复制代码
@Autowired
@JvmField var name: String? = null
@Autowired
@JvmField var age: Int? = 0

也正是和传递的参数一一对应,并且每个参数都添加了Autowired注解。这里我们要补充一点@Autowired注解的逻辑,上一篇我们在分析无参数页面跳转的时候,分析了@Route注解,被@Route注解的类会在编译期间生成辅助代码,那么@Autowired注解是否也有对应的辅助类生成呢?确实是有的,我们看一下生成的代码。

ini 复制代码
public class KotlinTestActivity$$ARouter$$Autowired implements ISyringe {
  private SerializationService serializationService;

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    KotlinTestActivity substitute = (KotlinTestActivity)target;
    substitute.name = substitute.getIntent().getExtras() == null ? substitute.name : substitute.getIntent().getExtras().getString("name", substitute.name);
    substitute.age = substitute.getIntent().getIntExtra("age", substitute.age);
  }
}

分析一下这个类生成的规则,大概可以知道,首先命名上是以类名+$$ARouter$$Autowired的固定格式来命名的,每个要接收参数的页面都会生成一个这样的类。在KotlinTestActivity$$ARouter$$Autowired中创建了一个SerializationService对象,它是一个继承了IProvider接口的接口,是ARouter内置的一个服务,当页面传递的数据为对象时,用来将对象和json进行相互转换,本次我们用不到这个类,所以暂不做关注。

我们再去看name和age的读取,就很直观了,直接拿到Activity对象获取intent,然后从intent上读到对应的值,再存入到Activity事先定义好的变量上。这就是@Autowired注解生成的辅助类所做的工作。所以看到这里我们也能猜到,ARouter.getInstance().inject(this)方法肯定是要找到这个辅助类。

再回到inject方法中,跟进到_ARouter类的inject方法,这里又创建了一个AutowiredService类型的对象,它也是一个继承了IProvider接口的接口,ARouter内置了一个实现类AutowiredServiceImpl。

scss 复制代码
static void inject(Object thiz) {
    AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
    if (null != autowiredService) {
        autowiredService.autowire(thiz);
    }
}

我们来到它的autowire方法。

typescript 复制代码
@Override
public void autowire(Object instance) {
    doInject(instance, null);
}

我们知道instance就是Activity对象,在这个方法中,会递归Activity的父类,直到父类为android的Activity为止。

scss 复制代码
private void doInject(Object instance, Class<?> parent) {
    Class<?> clazz = null == parent ? instance.getClass() : parent;

    ISyringe syringe = getSyringe(clazz);
    if (null != syringe) {
        syringe.inject(instance);
    }

    Class<?> superClazz = clazz.getSuperclass();
    // has parent and its not the class of framework.
    if (null != superClazz && !superClazz.getName().startsWith("android")) {
        doInject(instance, superClazz);
    }
}

看下getSyringe方法,此方法会的到一个ISyringe类型的对象,然后调用了它的inject方法,我们发现KotlinTestActivity$$ARouter$$Autowired也实现了ISyringe接口,这个inject方法和KotlinTestActivity$$ARouter$$Autowired中的inject方法有可能是同一个,继续往下看getSyringe的实现。

ini 复制代码
private ISyringe getSyringe(Class<?> clazz) {
    String className = clazz.getName();

    try {
        if (!blackList.contains(className)) {
            ISyringe syringeHelper = classCache.get(className);
            if (null == syringeHelper) {  // No cache.
                syringeHelper = (ISyringe) Class.forName(clazz.getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
            }
            classCache.put(className, syringeHelper);
            return syringeHelper;
        }
    } catch (Exception e) {
        blackList.add(className);    // This instance need not autowired.
    }

    return null;
}

我们看到这里通过反射创建了clazz.getName() + SUFFIX_AUTOWIRED的对象并返回了,clazz.getName() + SUFFIX_AUTOWIRED是什么?clazz是Activity的class,所以得到的name就是KotlinTestActivity,SUFFIX_AUTOWIRED是定义的常量,拼接起来之后正好是 <math xmlns="http://www.w3.org/1998/Math/MathML"> A R o u t e r ARouter </math>ARouterAutowired,所以如我们猜想的一样,这里得到的正是KotlinTestActivity$$ARouter$$Autowired这个辅助类对象,这样一来,读取传递参数的逻辑就通了。

总结

我们来总结一下参数传递和接收的实现逻辑。首先,通过ARouter传递参数时,会将参数存入到Postcard上的Bundle对象中,最后通过intent跳转Activity的时候,将bundle中的数据交给intent,然后执行页面跳转;进入目标页面后,需要接收的参数都被@Autowired注解,代码编译期间,会根据这个注解生成辅助类类名+$$ARouter$$Autowired,在目标页面调用inject方法找到这个辅助类并创建对象,执行其inject方法,即可在此通过intent将传递的参数取出,并赋值给页面定义的参数。如此一来,参数的传递和接收就完成了。

相关推荐
problc1 小时前
Android中的引用类型:Weak Reference, Soft Reference, Phantom Reference 和 WeakHashMap
android
IH_LZH1 小时前
Broadcast:Android中实现组件及进程间通信
android·java·android studio·broadcast
去看全世界的云1 小时前
【Android】Handler用法及原理解析
android·java
QGC二次开发1 小时前
Vue3 : Pinia的性质与作用
前端·javascript·vue.js·typescript·前端框架·vue
机器之心1 小时前
o1 带火的 CoT 到底行不行?新论文引发了论战
android·人工智能
机器之心2 小时前
从架构、工艺到能效表现,全面了解 LLM 硬件加速,这篇综述就够了
android·人工智能
AntDreamer2 小时前
在实际开发中,如何根据项目需求调整 RecyclerView 的缓存策略?
android·java·缓存·面试·性能优化·kotlin
A_aspectJ2 小时前
前端框架对比和选择
前端框架
运维Z叔3 小时前
云安全 | AWS S3存储桶安全设计缺陷分析
android·网络·网络协议·tcp/ip·安全·云计算·aws
多多*3 小时前
OJ在线评测系统 登录页面开发 前端后端联调实现全栈开发
linux·服务器·前端·ubuntu·docker·前端框架