kotlin 实现一个简单 Android 路由(1)-> 实现 startactivity

其实本来想做的很简单,写一个简单路由可以在项目中进行使用,在github上使用的一些路由都感觉有这样那样的问题,使用起来并不是非常顺心,于是想自己写一个路由,可是开始以后才发现,把这个工作想的太简单了,这个工作比想象得复杂得多,于是把需求缩减再缩减,两天时间才写了个最最基础得功能,目前实现得就是打开一个新页面,也就是 startactivity() 并且还不能传参数,不过有了这个基础,相信后面的写起来会顺利很多,因为这两天遇到很多问题,所以就想找个地方记录一下,也理一理目前思路

首先,实现的功能是

class MainActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test1)
        var router = Router()
        RxView.clicks(textView1)
                .compose(RxJavaUtils.preventDuplicateClicksTransformer())
                .subscribe {
                    router.start(baseContext,"testactivity")
                }
    }
}
@Route("testactivity")
class TestActivity: Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
    }
}

这里就是

router.start(baseContext,"testactivity")

这样在MainActivity 打开TestActivity,相当于 startActivity(Intent(baseContext,TestActivity::class.java))

如果是有一个这样的需求的话,我们理一理:

1.是需要实现一个 @Route 这样的注解

2.通过@Route 注解获取到所有注解过的activity

3.在调用router的start的时候,通过传入的 字符串匹配到@Route注解的Activity

4.把这个找到的activity打开


ok,那么一步一步做吧

1.是需要实现一个 @Route 这样的注解

对于这个注解第一步是毫无疑问的,它是写在类上面的一个注解,所以 @Target 是 ElementType.TYPE 倒是没什么问题

接下来就是这个注解的生命周期,我们肯定是在编译后甚至运行的时候依然需要使用到注解,那么保险起见就选择

@Retention(RetentionPolicy.RUNTIME) 

那么简单贴一下代码

@Target(ElementType.TYPE, ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
annotation class Route(vararg val value: String)

这里对于@Route("testactivity")这样的注解,参数需要一个字符串就可以了,没什么问题


2.通过@Route 注解获取到所有注解过的activity

这里是比较头大的一块,参考了一些其他路由之后,决定在编译期间完成注解路由的映射表,然后在运行时,对Route进行初始化的时候完成Map的初始化

首先在编译的时候,需要生成一个map,map生成的话,我们需要一个class,class中维护一个map,将所有的@Route注解的Activity添加到map中好了,我们希望的效果是这样的两个class就ok了,当然这里使用的是java,为啥用java呢,主要后面我们需要生成代码的时候用的是一个java代码生成的lib javapoet ,没有找到kotlin生成的lib比较遗憾 

public interface RouteTable {
    void handle(Map> map);
}
import com.bruce.brucerouter.template.RouteTable;
import com.bruce.neo.filemanager.MainActivity;
import com.bruce.neo.filemanager.TestActivity;
import java.util.Map;

public class AppRouteTable implements RouteTable {
    public AppRouteTable() {
    }
    public void handle(Map> map) {
        map.put("test", MainActivity.class);
        map.put("testactivity", TestActivity.class);
    }
}

那么我们可以在我们的路由lib中定义 RouteTable 这个接口,然后在app module中生成 AppRouteTable这个类来实现这个接口。

这里有两个知识点

第一个是 AbstractProcessor 这个类,通过它我们可以在编译时得到所有注解过的Activity

第二个是 javapoet  这个库,可以使用它来进行java代码的生成

这里就不对这两块进行详细展开了,推荐几个博客

AbstractProcessor注解处理器

JavaPoet - 优雅地生成代码

Android中使用AbstractProcessor在编译时生成代码



过程中遇到了各种各样的问题,当然问题来自于这些东西的不太熟悉

这里记录个卡住比较麻烦的问题

AbstractProcessor 需要在java lib中才能导入,当然,这属于项目的配置问题,参考上面的AbstractProcessor 注解处理器可以慢慢解决,其他小问题就不进行记录了,生成代码部分还未做优化,一些地方都做了注释,代码比较丑,先贴上来,优化后再进行更新

@SupportedAnnotationTypes("com.bruce.neo.annotation.Route")// 注册给哪个注解使用的
@SupportedSourceVersion(SourceVersion.RELEASE_7)//支持的jdk
class RouteProcessor : AbstractProcessor(){
    lateinit var messager :Messager
    private var mModuleName: String? = ""

    override fun init(processorEnvironment: ProcessingEnvironment?) {
        super.init(processorEnvironment)
        messager = processingEnv.messager
        mModuleName = processorEnvironment?.getOptions()!!.get("moduleName")!!
    }

    override fun process(set: MutableSet?, roundEnvironment: RoundEnvironment?): Boolean {
        messager.printMessage(Diagnostic.Kind.NOTE, "start: --------------")
        val elements = roundEnvironment?.getElementsAnnotatedWith(Route::class.java)
        if (elements == null || elements!!.isEmpty()) return true

        val typeElements = HashSet()
        for (element in elements!!) {
            if (element.getKind().isClass() && validateClass(element as TypeElement)) { // 注解在Class上的Route
                typeElements.add(element)
            }
        }
        if (mModuleName != null) {
            val validModuleName = mModuleName.toString().replace(".", "_").replace("-", "_")
            generateRouteTable(validModuleName, typeElements)
        } else {
            throw RuntimeException(String.format("No option `%s` passed to Route annotation processor.", "moduleName"))
        }
        return true
    }

    /**
     * RouteTable.
     */
    private fun generateRouteTable(moduleName: String, elements: Set) {

        val mapTypeName = ParameterizedTypeName.get(ClassName.get(Map::class.java),
                ClassName.get(String::class.java), ParameterizedTypeName.get(ClassName.get(Class::class.java),
                WildcardTypeName.subtypeOf(Any::class.java)))// 生成 Map> map
        val mapParameterSpec = ParameterSpec.builder(mapTypeName, "map").build()

        val methodHandle = MethodSpec.methodBuilder("handle")  // 需要生成的方法
                .addAnnotation(Override::class.java)
                .addModifiers(Modifier.PUBLIC)
                .addParameter(mapParameterSpec)

        // 记录path,防止重复的route path
        val pathRecorder = HashMap()
        for (element in elements) {
            val paths = element.getAnnotation(Route::class.java).value
            for (path in paths) {
                if (pathRecorder.containsKey(path)) {
                    throw RuntimeException(String.format("Duplicate route path: %s[%s, %s]",path, element.qualifiedName, pathRecorder[path]))
                }
                methodHandle.addStatement("map.put(\$S, \$T.class)", path, ClassName.get(element))// map 中添加数据
                pathRecorder[path] = element.qualifiedName.toString()
            }
        }
        val interfaceType = processingEnv.elementUtils.getTypeElement("com.bruce.brucerouter.template.RouteTable") // 实现的接口

        val type = TypeSpec.classBuilder(capitalize(moduleName) + "RouteTable")
                .addSuperinterface(ClassName.get(interfaceType)) // 继承接口
                .addModifiers(Modifier.PUBLIC)//class 为public
                .addMethod(methodHandle.build()) //实现的方法
                .addJavadoc("Generated by Router. Do not edit it!\n")//生成注释
                .build()

        JavaFile.builder("com.bruce.neo", type).build().writeTo(processingEnv.filer)
        messager.printMessage(Diagnostic.Kind.NOTE, "end: --------------")
    }

    /**
     * Verify the annotated class. Must be a subtype of Activity
     */
    private fun validateClass(typeElement: TypeElement): Boolean {
        if (!isSubtype(typeElement, "android.app.Activity")) {
            return false
        }
        val modifiers = typeElement.modifiers
        // abstract class.
        if (modifiers.contains(Modifier.ABSTRACT)) {
            return false
        }
        return true
    }

    private fun isSubtype(typeElement: Element, type: String): Boolean {
        return processingEnv.typeUtils.isSubtype(typeElement.asType(),
                processingEnv.elementUtils.getTypeElement(type).asType())
    }

}

ok,这步结束了,然后next


3.在调用router的start的时候,通过传入的 字符串匹配到@Route注解的Activity

这里需要理清楚一些,我们生成的 AppRouteTable 是在app这个module下的,我们init  router 的时候是在router这个lib下,app引入了router,但是 router并没有引入 app,所以在router中是不能直接访问AppRouteTable这个类的,所以这里需要使用反射进行访问,下面简单贴下代码,顺便把start的代码也一并贴上来了,未做优化,例如Router应该是个单例,还有其他的一些容错都没有做,只是最简单的代码查看

class Router {
    // 获取到注册的activity
    val routeTable: Map> = HashMap()
    init {
        var routeTableName = "com.bruce.neo.AppRouteTable"
        val clz = Class.forName(routeTableName)
        val instance = clz.newInstance() as RouteTable
        instance.handle(routeTable)
    }
    fun start(context:Context,target:String){
        //打印
        routeTable.forEach({
            Log.e("test",it.key)
            Log.e("test",it.value.simpleName)
        })
        var intent = Intent(context,routeTable.get(target))
        context.startActivity(intent)

    }
ok,到这里就大功告成了,比较抽象,项目目前太过demo,就不上传了,后面进一步完善优化,能达到可以使用的时候再进行上传


后续要做的事情还有很多,至少需要支持参数的传递  startactivityForResult 实现,Fragment页面切换实现,甚至添加拦截器等等等等,一步一步做


 
  


你可能感兴趣的:(android)