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将传递的参数取出,并赋值给页面定义的参数。如此一来,参数的传递和接收就完成了。

相关推荐
NiceCloud喜云7 小时前
Claude Files API 深入:从上传、复用到配额管理的工程化指南
android·java·数据库·人工智能·python·json·飞书
ujainu7 小时前
CANN pto-isa:虚拟指令集如何连接编译与执行
android·ascend
赏金术士8 小时前
第六章:UI组件与Material3主题
android·ui·kotlin·compose
TechMerger9 小时前
Android 17 重磅重构!服役 20 年的 MessageQueue 迎来无锁改造,卡顿大幅优化!
android·性能优化
yuhuofei202112 小时前
【Python入门】Python中字符串相关拓展
android·java·python
dalancon12 小时前
Android Input Spy Window
android
dalancon13 小时前
InputDispatcher派发事件,查找目标窗口
android
我命由我1234514 小时前
Android Framework P3 - MediaServer 进程、认识 ServiceManager 进程
android·c语言·开发语言·c++·visualstudio·visual studio·android runtime
天才少年曾牛15 小时前
Android14 新增系统服务后,应用调用出现 “hidden api” 警告的原因与解决方案
android·frameworks
赏金术士15 小时前
Jetpack Compose 底部导航实战教程(完整版)
android·kotlin·compose