美文网首页
ARouter 1.5.0

ARouter 1.5.0

作者: 主音King | 来源:发表于2020-10-27 17:02 被阅读0次

    ARouter

    帮助Android App进行组件化改造的框架,支持模块间路由、通信、解耦

    支持功能:

    • 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
    • 支持多模块工程使用
    • 支持添加多个拦截器,自定义拦截顺序
    • 支持依赖注入,可单独作为依赖注入框架使用
    • 支持InstantRun
    • 支持MultiDex(Google方案)
    • 映射关系按组分类、多级管理,按需初始化
    • 用户指定全局降级与局部降级策略
    • 页面、拦截器、服务等组件均自动注册到框架
    • 多种方式配置跳转动画
    • 获取Fragment
    • 完全支持Kotlin以及混编
    • 第三方App加固(使用arouter-register实现自动注册)
    • 生成路由文档
    • IDE插件便捷关联路径和目标类

    应用

    • 从外部URL映射到内部页面,参数传递和解析
    • 跨模块页面跳转,模块解耦
    • 拦截跳转过程,处理登陆、埋点
    • 跨模块API调用,控制反转来组件解耦

    依赖(在app、子module等需要路由跳转的模块添加):

    plugins {
        // module如下,如果是app(id 'com.android.application')
        id 'com.android.library'
       // kotlin 扩展
        id 'kotlin-android'
        id 'kotlin-android-extensions'
        id 'kotlin-kapt'
    }
    
    // kotlin模块(和javamodule模块不一样注意)
    kapt {
        arguments {
            arg("AROUTER_MODULE_NAME", project.getName())
        }
    }
    
    dependencies{
    implementation 'com.alibaba:arouter-api:1.5.0'
    kapt 'com.alibaba:arouter-compiler:1.2.2'
    }
    

    初始化:在Application中进行初始化

    class BaseApplication :Application(){
        override fun onCreate() {
            super.onCreate()
            if (BuildConfig.DEBUG){
                ARouter.openDebug()
                ARouter.openLog()
            }
            ARouter.init(this)
        }
    }
    

    一个包含多个module项目,名为user的module中存在一个UserHomeActivity,路由路径:/account/userHome。从其他module跳转该页面,指定path来跳转。

    // 主app里的
    @Route(path = "/account/userHome")
    class UserHomeActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_user_home)
        }
    }
    
    // module里的
    @Route(path = "/library1/test")
    class Library1Activity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_library1)
    
            tvTest.setOnClickListener{
                // 跨模块跳转
                ARouter.getInstance().build("/account/userHome").navigation()
            }
        }
    }
    

    编译阶段生成辅助代码来实现path跳转。需要拿到Activity的Class对象才行。编译阶段,ARouter根据我们设定的路由跳转规则来自动生成映射文件,包含path和ActivityClass之间的对应关系。
    UserHomeActivity编译阶段自动生成辅助java文件ARouter$$Group$$account类中就将path和ActivityClass作为键值对保存到了Map中,ARouter依靠此进行跳转。

    public class ARouter$$Group$$account implements IRouteGroup {
      @Override
      public void loadInto(Map<String, RouteMeta> atlas) {
        atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
      }
    }
    

    这类自动生成的文件包名路径都是com.alibaba.android.arouter.routes,类名前缀特定规则。虽然ARouter$$Group$$account类实现了对应关系保存到Map中,loadInto方法还是需要有ARouter运行时来调用,ARouter就需要拿到ARouter$$Group$$account这个类才行,ARouter通过扫描com.alibaba.android.arouter.routes这个包名路径来获取所有辅助文件。

    1、程序员自己维护特定path和特定目标类之间对应关系,ARouter只要求开发者使用包含path的@Route 注解修饰目标类
    2、ARouter在编译阶段通过注解处理器自动生成path和特定的目标类之间的对应关系,path作为key,将目标类Class对象为value之一存到Map中
    3、在运行阶段,通过path来发起请求,ARouter根据path从map中取值,拿到目标类

    ARouter类使用单例模式,暴露外部调用的API。实现逻辑交给_ARouter来完成。
    _ARouter类包私有权限,使用了单例,通过init(Application)-->LogisticsCenter.init(mContext, executor)

    LogisticsCenter实现扫描特定包名路径拿到所有自动生成的辅助文件的逻辑。在进行初始化的时候,加载到当前项目一共包含所有group,以及每个group对应的路由信息表。
    1、如果开启debug模式或通过本地SP缓存判断出app的版本前后发生变化,需要重新获取路由信息,否则从使用之前缓存到SP中的数据。
    2、获取全局路由信息是耗时操作,所以ARouter通过将全局路由信息缓存到SP中来实现。开发阶段可能随时添加路由表,每次发布新的版本正常都会加大应用版本号,所以ARouter只开启了debug模式或者版本号发生变化的时候才会重新获取路由信息
    3、获取路由信息包含了com.alibaba.android.arouter.routes包下自动生成的辅助文件的全路径,判断路径名前缀字符串,知道该类什么类型文件,通过反射构建不同类型对象,调用对象的方法将路由信息存到Warehouse的Map中。

    UserHomeActivity,其path为/account/userHome,ARouter默认会将path的第一个单词account作为group,而UserHomeActivity放到名为app(module名字)的module中

    /**
     * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
    public class ARouter$$Root$$app implements IRouteRoot {
      @Override
      public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
        routes.put("account", ARouter$$Group$$account.class);
      }
    }
    
    /**
     * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
    public class ARouter$$Group$$account implements IRouteGroup {
      @Override
      public void loadInto(Map<String, RouteMeta> atlas) {
        atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
      }
    }
    

    LogisticsCenter的init文件名前缀ARouter$$Root$$定位到ARouter$$Root$$app这个类,然后通过反射构建出该对象,通过调用loadInto的页面时,反射调用ARouter$$Group$$account的loadInto方法,按需加载,等到需要的时候再获取详细的路由对应信息。

        ARouter.getInstance().build(RoutePath.USER_HOME).navigation()
    

    build()通过ARouter中转调用_ARouter的build()返回Postcard对象,用于传入跳转配置参数,例如:mBundle、开启绿色通道greenChannel、跳转动画optionsCompat等

    Postcard的navigation()方法会调用_ARouter完成Activity跳转。
    navigation方法的重点在于LogisticsCenter.completion(postcard)。按需加载反射调用ARouterGroupaccount 的 loadInto 方法。completion方法就获取详细路由信息。通过postcard携带的path和group信息从Warehouse取值,如果不是null信息保存到postcard中,为null则抛出NoRouteFoundException

    跳转到Activity并注入参数

                // 带参数跳转
                ARouter.getInstance()
                    .build("/account/userHome")
                    .withLong("userId",13)
                    .withString("userName","小明")
                    .navigation()
    
    // 参数接收
    @Route(path = "/account/userHome")
    class UserHomeActivity : AppCompatActivity() {
        private val TAG = "UserHome-"
        @Autowired(name = "userId")
        @JvmField
        var userId:Long = 0
    
        @Autowired(name = "userName")
        @JvmField
        var userName = ""
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_user_home)
            // 需要注册,否则无法接收到参数传递
            ARouter.getInstance().inject(this)
            Log.d(TAG, "onCreate: userId:$userId userName:$userName")
        }
    }
    

    通过@Autowired注解修饰变量。声明name参数,传递键值对的key对应。通过ARouter.getInstance().inject(this)后,完成参数赋值。
    生产辅助代码:

    public class UserHomeActivity$$ARouter$$Autowired implements ISyringe {
      private SerializationService serializationService;
    
      @Override
      public void inject(Object target) {
        serializationService = ARouter.getInstance().navigation(SerializationService.class);
        UserHomeActivity substitute = (UserHomeActivity)target;
        substitute.userId = substitute.getIntent().getLongExtra("userId", substitute.userId);
        substitute.userName = substitute.getIntent().getStringExtra("userName");
      }
    }
    

    携带的参数放到Intent,inject方法实现了从Intent取值向变量赋值,要求变量是public的,kotlin需要同时向变量加上@JvmField注解的原因。参数注解:ARouter.getInstance().inject(this)
    ARouter通过控制反转拿到AutowiredService对应实现类AutowiredServiceImpl的对象,调用autowire方法完成参数注入。
    由于参数注入辅助类的类名具有固定的包名和类名,目标类类名+$$ARouter$$Autowired, 所以AutowiredServiceImpl传入的instance参数和反射过来生成的对象,最终调用inject方法完成参数注入。

    控制反转

    跳转Activity并自动注入参数属于依赖注入的一种,ARouter同时也支持控制反转:通过接口获取其实现类实例
    存在一个ISayHelloService接口,需要拿到其实现类实例,但是不希望使用的时候和特定实现类SayHelloService绑定在一起造成耦合,使用ARouter控制反转功能,要求ISayHelloService接口继承了IProvider接口才行
    注意:Route(path = "/account/sayhello")在实现的接口和调用的类path要一致,才能实现反转控制。

    // 接口
    interface ISayHelloService :IProvider {
        fun sayHello()
    }
    
    // 具体实现
    @Route(path = "/account/sayhello")
    class SayHelloService :ISayHelloService{
        private val TAG = "SayHello-"
        override fun sayHello() {
            Log.d(TAG, "sayHello: ")
        }
    
        override fun init(context: Context?) {
            Log.d(TAG, "init: ")
        }
    }
    
    // 具体使用
    @Route(path = "/account/sayhello")
    class MainActivity : AppCompatActivity() {
        private val TAG = "Main-网络测试"
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            textNet.setOnClickListener {
            ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()
            }
        }
    }
    

    在使用的时候直接传递ISayHelloService的Class对象即可,ARouter会将SayHelloService以单例形式返回,无需开发者手动构建SayHelloService对象,从而达到解耦。

    ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()
    

    和实现Activity跳转一样,ARouter自动生成几个文件,包含路由表映射关系。

    /**
     * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
    public class ARouter$$Group$$account implements IRouteGroup {
      @Override
      public void loadInto(Map<String, RouteMeta> atlas) {
        atlas.put("/account/sayhello", RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhello", "account", null, -1, -2147483648));
      }
    }
    
    /**
     * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
    public class ARouter$$Providers$$app implements IProviderGroup {
      @Override
      public void loadInto(Map<String, RouteMeta> providers) {
        providers.put("com.george.learnretrofit.test.ISayHelloService", RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhello", "account", null, -1, -2147483648));
      }
    }
    

    LogisticsCenter实现扫描特定包名路径拿到所有自动生成的辅助文件逻辑。最终Warehouse中就会在初始化的时候拿到数据
    Warehouse.groupsIndex:

    • account--->class com.alibaba.android.arouter.routes.ARouter$$Group$$account
      Warehouse.providersIndex:
      com.george.learnretrofit.test.ISayHelloService--->RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhello", "account", null, -1, -2147483648)

    LogisticsCenter.completion(postcard)获取对象实例时候同时将实例缓存起来,以后复用。

    拦截器

    可以通过拦截器来判断用户是否处于登陆状态,还未登陆的话,拦截请求,打开登陆界面。
    可以添加多个拦截器,每个拦截器设置不同优先级

    @Interceptor(priority = 100,name="啥也不做的拦截器")
    class NothingInterceptor :IInterceptor{
        private val TAG = "Nothing-1-"
        override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
            // 不拦截,任其跳转
            Log.d(TAG, "process: ")
            callback?.onContinue(postcard)
        }
    
        override fun init(context: Context?) {
    
            Log.d(TAG, "init: ")
        }
    }
    
    @Interceptor(priority = 200, name = "登陆拦截器")
    class LoginInterceptor: IInterceptor{
        private val TAG = "Nothing-2-"
        override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
    
            if (postcard?.path == "/account/userHome"){
                // 拦截
                Log.d(TAG, "process: 拦截-到主页")
                callback?.onInterrupt(null)
                ARouter.getInstance().build("/account/sayhello").navigation()
            }else{
                Log.d(TAG, "process: 不拦截")
                // 不拦截,任其跳转
                callback?.onContinue(postcard)
            }
        }
    
        override fun init(context: Context?) {
            Log.d(TAG, "init: ")
        }
    
    }
    

    priority越小优先级越高(越先执行)。
    _ARouter的navigation方法,转交给interceptorService,判断有没有开启绿色通道模式。InterceptorServiceImpl实现类,ARouter初始化过程中通过控制反转拿到interceptorService实例。
    1、第一次获取InterceptorServiceImpl实例时候,其init方法会马上被调用,交由线程池来处理。通过反射生成每个拦截器对象,并调用每个拦截器的init方法完成拦截器初始化,将每个拦截器对象都存到Warehouse.interceptors中。如果初始化完成,则唤醒等待的interceptorInitLock上的线程。
    2、doInterceptions方法被调用,如果第一个步骤未执行完,则通过checkInterceptorsInitStatus()方法等待第一次步骤完成。如果10s内都未完成,则走失败流程直接返回。
    3、在线程池中遍历拦截器列表,如果某个拦截器拦截请求则调用callback.onInterrupt方法通知内部,否则调用callback.onContinue()方法继续跳转逻辑。

    注解处理器

    依靠注解器生成辅助文件,ARouter才能完成参数自动注入功能。
    APT(Annotation Processing Tool)注解处理器,用在编译期扫描和处理注解,通过注解生成Java文件。即注解作为桥梁,预先设定好的代码生成规则,自动生成Java文件。例如:ButterKnife/Dragger2/EventBus等

    Java API已经提供了扫描源码并解析注解的框架,通过继承AbstractProcessor类来实现自己的注解解析逻辑。APT的原理就是在注解了某些代码元素(如字段、函数、类等)后。在编译时编辑器会检查AbstractProcess的子类,并且自动调用process()方法,然后将指定注解的所有代码元素作为参数传递给该方法,开发者根据注解元素在编译期输出Java代码。

    ARouter源码中和注解处理器相关的module

    • router-annotation。Java Module,包含了Autowired、Interceptor这些注解及RouteMeta等JavaBean
    • arouter-compiler。Android Module,包含了AbstractProcessor的实现类用于生产代码

    相关文章

      网友评论

          本文标题:ARouter 1.5.0

          本文链接:https://www.haomeiwen.com/subject/fekamktx.html