专栏名称: 郭霖
Android技术分享平台,每天都有优质技术文章推送。你还可以向公众号投稿,将自己总结的技术心得分享给大家。
目录
相关文章推荐
开发者全社区  ·  美团员工:目睹优秀同学因不善表达、不会拍马屁 ... ·  21 小时前  
开发者全社区  ·  原来如此:很多公司都不招大龄程序员了。。。 ·  2 天前  
开发者全社区  ·  小作文频出!某基金经理晕厥 ·  2 天前  
开发者全社区  ·  中国十大古都城市 ·  2 天前  
51好读  ›  专栏  ›  郭霖

Kotlin打造Android路由框架

郭霖  · 公众号  · android  · 2018-04-20 08:00

正文



今日科技快讯


近日,根据乐视网公告,腾讯子公司林芝利创、京东邦能、苏宁体育、TCL集团、深圳佰亿投资拟分别向新乐视智家注入3亿元,此外,世嘉控股、联想控股旗下的弘毅弘欣等也有参股。但乐视能否真正复兴,还需观察。


作者简介


明天就是周六啦,提前祝大家周末愉快!

本篇来自 WuRichard 的投稿,分享了他自己的路由框架,希望对大家有所帮助。

WuRichard 的博客地址:

https://www.jianshu.com/u/fbeab77ee34b


前言


KRouter ( https://github.com/richardwrq/KRouter )路由框架借助gradle插件、kapt( https://blog.jetbrains.com/kotlin/2015/05/kapt-annotation-processing-for-kotlin )实现了依赖注入、为Android平台页面启动提供路由功能。

源码不复杂,在关键地方也有注释说明,建议打算或正在使用kapt+kotlinpoet遇到坑的同学可以fork一下项目,或许能找到你想要的答案,只要将整个流程了解清楚了,相信你自己也能撸一个轮子出来,目前许多开源框架dagger、butter knife、greendao等实现原理都是一致的。


从startActivity说起


在组件化开发的实践过程中,当我完成一个模块的开发后(比如说这个模块中有一个Activity或者Service供调用者调用),其他模块的开发者要启动我这个模块中的Activity的代码我们再熟悉不过了:

val intent = Intent(this, MainActivity::class.java)
intent.putExtra("param1", "1")
intent.putExtra("param2", "2")
startActivity(intent)

当然,其他模块的开发人员需要知道我们这个Activity的类名以及传入的参数对应的key值(上面的param1和param2),这时候我就想,在每一个需要启动这个页面的地方都存在着类似的样板代码,而且被启动的Activity在取出参数对属性进行赋值时的代码也比较繁琐,于是在网上查找相关资料了解到目前主流的路由框架(ARouter、Router等)都支持这些功能,秉着尽量不重复造轮子的观念我fork了ARouter项目,但是阅读源码后发现其暂时不支持Service的启动,而我负责的项目里面全是运行在后台的Service。

紧接着也大概了解了一下其他一些框架,都存在一些不太满意的地方,考虑再三,干脆自己撸一个轮子出来好了。

首先来看一段最简单的发起路由请求的代码(Java调用):

KRouter.INSTANCE.create("krouter/main/activity?test=32")
               .withFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
               .withString("test2", "this is test2")
               .request();

其中krouter/main/activity?test=32为对应的路由路径,可以使用类似http请求的格式,在问号后紧接着的是请求参数,这些参数最终会自动包装在intent的extras中,也可以通过调用with开头的函数来配置请求参数。

上面的代码执行后最终会启动一个Activity,准确来说是一个带有@Route注解的Activity,它长这样:

@Route(path = "krouter/main/activity")
public class MainActivity extends Activity {
   ...
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       getIntent().getIntExtra("test", -1);//这里可以获取到请求参数test
   }
   ...
}

这是一个最基本的功能,怎么样,看起来还不错吧?跟大部分路由框架的调用方式差不多。现在主流的路由框架是怎么做到的呢?下面就看我一一道来。

在使用KRouter的API前首先需要为一些类添加注解:

/**
* User: WuRuiqiang([email protected])
* Date: 18/1/2
* Time: 上午10:53
* Version: v1.0
* Description:用于标记可路由的组件
*/

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class Route(
       /**
        * Path of route
        */

       val path: String,
       /**
        * PathPrefix of route
        */

       val pathPrefix: String = "",
       /**
        * PathPattern of route
        */

       val pathPattern: String = "",
       /**
        * Name of route
        */

       val name: String = "undefined",
       /**
        * Priority of route
        */

       val priority: Int = -1)


/**
* User: WuRuiqiang([email protected])
* Date: 18/1/2
* Time: 上午10:53
* Version: v1.0
* Description:用于拦截路由的拦截器
*/

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class Interceptor(
       /**
        * Priority of interceptor
        */

       val priority: Int = -1,
       /**
        * Name of interceptor
        */

       val name: String = "DefaultInterceptor")


/**
* User: WuRuiqiang([email protected])
* Date: 18/1/2
* Time: 上午10:53
* Version: v1.0
* Description:属性注入
*/

@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.SOURCE)
annotation class Inject(
       /**
        * Name of property
        */

       val name: String = "",
       /**
        * If true, app will be throws NPE when value is null
        */

       val isRequired: Boolean = false,
       /**
        * Description of the field
        */

       val desc: String = "No desc.")


/**
* User: WuRuiqiang([email protected])
* Date: 18/1/2
* Time: 上午10:53
* Version: v1.0
* Description:Provider
*/

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class Provider(/**
                          * Path of Provider
                          */

                         val value: String)

被注解的元素的信息最终被保存在对应的数据类中:

/**
* User: WuRuiqiang([email protected])
* Date: 18/1/4
* Time: 上午10:46
* Version: v1.0
* Description:Route元数据,用于存储被[com.github.richardwrq.krouter.annotation.Route]注解的类的信息
*/

data class RouteMetadata(
       /**
        * Type of Route
        */

       val routeType: RouteType = RouteType.UNKNOWN,
       /**
        * Priority of route
        */

       val priority: Int = -1,
       /**
        * Name of route
        */

       val name: String = "undefine",
       /**
        * Path of route
        */

       val path: String = "",
       /**
        * PathPrefix of route
        */

       val pathPrefix: String = "",
       /**
        * PathPattern of route
        */

       val pathPattern: String = "",
       /**
        * Class of route
        */

       val clazz: Class = Any::class.java)

/**
* User: WuRuiqiang([email protected])
* Date: 18/1/8
* Time: 下午10:46
* Version: v1.0
* Description:Interceptor元数据,用于存储被[com.github.richardwrq.krouter.annotation.Interceptor]注解的类的信息
*/

data class InterceptorMetaData(
       /**
        * Priority of Interceptor
        */

       val priority: Int = -1,
       /**
        * Name of Interceptor
        */

       val name: String = "undefine",
       /**
        * Class desc of Interceptor
        */

       val clazz: Class = Any::class.java)


/**
* User: WuRuiqiang([email protected])
* Date: 18/3/14
* Time: 上午1:28
* Version: v1.0
* Description:Injector元数据,用于存储被[com.github.richardwrq.krouter.annotation.Inject]注解的类的信息
*/

data class InjectorMetaData(
       /**
        * if true, throw NPE when the filed is null
        */

       val isRequired: Boolean = false,
       /**
        * key
        */

       val key: String = "",
       /**
        * field name
        */

       val fieldName: String = "")

其中被 @Route 注解的类是Android中的四大组件和Fragment或者它们的子类(目前尚不支持Broadcast以及ContentProvider),被 @Route 注解的对象目前有3种处理方式:

  1. 若被注解的类是Activity的子类,那么最终的处理方式是startActivity;

  2. 若被注解的类是Service的子类,最终的处理方式有两种,也就 是Android中启动Service的两种方式,使用哪种启动方式取决于是否调用了withServiceConn函数添加了ServiceConnection;

  3. 若被注解的类是Fragment的子类,最终的处理方式是调用无参构造函数构造出这个类的实例,并调用setArguments(Bundle args)将请求参数传入Fragment的bundle中,最后返回该实例

@Interceptor 注解的类需实现IRouteInterceptor接口,这些类主要处理是否拦截路由的逻辑,比如某些需要登录才能启动的组件,就可以用到拦截器

@Inject 用于标记需要被注入的属性

@Provider 注解的类最终可以调用KRouter.getProvider(path: String)方法获取该类的对象,如果该类实现了IProvider接口,那么init(context: Context)方法将被调用

这些注解最终都不会被编译进class文件中,在编译时期这些注解会被收集起来最终交由不同的Annotation Processor去处理。

KRouter路由框架分为3个模块:

  • KRouter-api 模块,作为SDK提供API供应用调用,调用 KRouter-compiler 模块生成的类中的方法加载路由表,处理路由请求

  • KRouter-compiler 模块,各种注解对应的Processor的集合,编译期运行,负责收集路由组件,并生成kotlin代码

  • KRouter-gradle-plugin 模块,自定义gradle插件,在项目构建时期添加相关依赖以及相关参数的配置

KRouter-compiler

在介绍该模块之前如果有同学不知道Annotation Processor的话建议先阅读

Annotation Processing-Tool详解

https://blog.csdn.net/hj7jay/article/details/52180023

一小时搞明白注解处理器(Annotation Processor Tool)

https://blog.csdn.net/u013045971/article/details/53509237

这两篇文章,简单来说,APT就是javac提供的一个插件,它会搜集被指定注解所注解的元素(类、方法或者属性),最终将搜集到的这些交给注解处理器 Annotation Processor 进行处理,注解处理器通常会生成一些新的代码(推荐大名鼎鼎的square团队造的轮子javapoet(







请到「今天看啥」查看全文