新装备来了! Butterfly - 全新的Android路由组件化框架, 小而强大!

9,310 阅读4分钟

Butterfly.png

Butterfly - 蝴蝶

Github地址

Butterfly - 小巧而强大的武器,拥有它,让你的Android开发如虎添翼,Carry全场!

只有最强大和最经验的勇士才能挥动蝴蝶,但它在战斗中提供了令人难以置信的灵巧

物品介绍: +30 敏捷 +35% 闪避 +25 攻击 +30 攻速

功能介绍

蝴蝶通过两个不同的注解来实现不同的功能:

  • Agile 用于页面导航
  • Evade 用于组件化通信

Agile

  • 导航

通过给Activity添加Agile注解,并设置对应的scheme,随后即可通过Butterfly进行导航,或者导航并获取返回数据

@Agile("test/scheme")
class AgileTestActivity : AppCompatActivity() {
    //...
}

//导航
Butterfly.agile("test/scheme").carry()

//导航并获取返回数据
Butterfly.agile("test/scheme")
    .carry {
        val result = it.getStringExtra("result")
        binding.tvResult.text = result
    }
  • 传递参数

Agile支持附带参数导航,有两种方式,一种是通过拼接scheme,将参数添加到scheme中 另一种是通过调用params方法手动传入参数,或者两者混合进行, 随后可在导航后的页面中获取对应的参数

//拼接scheme
Butterfly.agile("test/scheme?a=1&b=2").carry()

//调用params
Butterfly.agile("test/scheme?a=1&b=2")
    .params("intValue" to 1)
    .params("booleanValue" to true)
    .params("stringValue" to "test value")
    .carry()
  • 解析参数

在导航目的页面,可通过参数的key字段来获取传递的参数值

@Agile("test/scheme")
class AgileTestActivity : AppCompatActivity() {
    val a by lazy { intent?.getStringExtra("a") ?: "" }
    val b by lazy { intent?.getStringExtra("b") ?: "" }
    val intValue by lazy { intent?.getIntExtra("intValue", 0) ?: 0 }
}

除了手动解析参数以外,还可以装备Bracer来实现全自动进行参数解析

@Agile("test/scheme")
class AgileTestActivity : AppCompatActivity() {
    val a by params<String>()
    val b by params<String>()
    val intValue by params<Int>()
}

Bracer 使用方式详情见: Github 地址 Bracer

  • 拦截器

Agile支持拦截器,可用于在导航前预处理部分逻辑,如进行登录检测 此外拦截器中也可进行导航,但为了避免拦截器套娃,需要添加skipInterceptor()方法以忽略拦截器

//实现自定义拦截器
class TestInterceptor : ButterflyInterceptor {
    override fun shouldIntercept(agileRequest: AgileRequest): Boolean {
        //检测是否需要拦截
        return true
    }

    override suspend fun intercept(agileRequest: AgileRequest) {
        //处理拦截逻辑
        println("intercepting")
        delay(5000)
        println("intercept finish")
    }
}

//注册拦截器
ButterflyCore.addInterceptor(TestInterceptor())

//跳过拦截器
Butterfly.agile("test/scheme").skipInterceptor().carry()
  • Action

Agile除了支持页面导航以外,还支持导航Action,Action无页面,可进行某些逻辑处理 首先让自定义的Class继承Action,然后添加@Agile注解并设置scheme,其余和页面导航一致

@Agile("test/action")
class TestAction : Action {
    override fun doAction(context: Context, scheme: String, data: Bundle) {
        //可从data中获取传入的参数
        Toast.makeText(context, "This is an Action", Toast.LENGTH_SHORT).show()
    }
}

//启动Action
Butterfly.agile("test/action").carry()

//Action同样支持传参
Butterfly.agile("test/action?a=1&b=2").carry()

//params 传参
Butterfly.agile("test/action")
    .params("intValue" to 1)
    .carry()
  • 流程控制

Agile除了直接调用carry导航以外,还可以调用flow返回Flow对象, 利用Flow对象可对导航流程进行处理

Butterfly.agile("test/scheme").flow()
    .onStart { println("start") }
    .onCompletion { println("complete") }
    .onEach { println("process result") }
    .launchIn(lifecycleScope)

Evade

蝴蝶使用简单的两个注解即可实现任意组件之间进行通信,而组件之间无需任何直接或间接依赖

例如有两个组件:Module Foo 和Module Bar 需要通信

在Module Foo中,定义接口,并添加Evade注解:

@Evade
interface Home {
    //定义方法
    fun showHome(fragmentManager: FragmentManager, container: Int)
}

在Module Bar中,定义实现,,并添加EvadeImpl注解:

//实现类名必须以Impl结尾
@EvadeImpl
class HomeImpl {
    val TAG = "home_tag"

    //实现Home接口中的方法, 方法名和方法参数必须相同
    fun showHome(fragmentManager: FragmentManager, container: Int) {
        val homeFragment = HomeFragment()
        fragmentManager.beginTransaction()
            .replace(container, homeFragment, TAG)
            .commit()
    }
}

由于Evade使用类名称作为定义和实现关联的重要依据,因此接口类名和实现类名必须相同,并且实现类名以Impl结尾. 如无法以类名作为关联,也可使用相同的字符串类型作为关联key

@Evade(identity = "same key")
interface Home

@EvadeImpl(identity = "same key")
class OtherNameImpl

随后即可在Module Foo中,使用evade方法获取Home并调用:

val home = Butterfly.evade<Home>()
home.showHome(supportFragmentManager, R.id.container)

除此之外, Evade也支持通过下沉依赖的形式, 进行强关联类型的通信

例如以下三个组件:公共组件Module Base,Module Foo,Module Bar

首先将Home接口下沉至公共组件Module Base中:

@Evade
interface Home {
    fun showHome(fragmentManager: FragmentManager, container: Int)
}

然后在Module Bar中,实现接口:

//同样需要使用相同的命名规则, 实现类名必须以Impl结尾
@EvadeImpl
class HomeImpl : Home {
    val TAG = "home_tag"

    override fun showHome(fragmentManager: FragmentManager, container: Int) {
        val homeFragment = HomeFragment()
        fragmentManager.beginTransaction()
            .replace(container, homeFragment, TAG)
            .commit()
    }
}

之后便可在Module Foo中,使用evade方法获取Home并调用:

val home = Butterfly.evade<Home>()
home.showHome(supportFragmentManager, R.id.container)

路由表

Butterfly会为每个使用了注解的Module生成一个路由表, 命名规则为: Butterfly[模块名称]Module

手动注册:

class DemoApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        //注册
        ButterflyCore.addModule(ButterflyHomeModule())
        ButterflyCore.addModule(ButterflyFooModule())
        ButterflyCore.addModule(ButterflyBarModule())
    }
}

使用插件自动注册:

  1. 添加插件依赖
//使用 plugins DSL:
plugins {
  id "io.github.ssseasonnn.butterfly" version "1.0.1"
}

//或者使用legacy plugin application:
buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "io.github.ssseasonnn:plugin:1.0.1"
  }
}

//添加plugin
apply plugin: "io.github.ssseasonnn.butterfly"
  1. 实现自己的Application类
class DemoApplication : Application() {
    override fun onCreate() {
        super.onCreate()
    }
}

混淆配置

-keep public class zlc.season.butterfly.module.**
-keep public class zlc.season.butterfly.annotation.**
-keep public class zlc.season.butterfly.ButterflyCore {*;}
-keep public class * extends zlc.season.butterfly.Action

-keep @zlc.season.butterfly.annotation.Agile class * {*;}
-keep @zlc.season.butterfly.annotation.Evade class * {*;}
-keep @zlc.season.butterfly.annotation.EvadeImpl class * {*;}

俗话说得好,蝴蝶在手,天下我有! 这么好的装备你不来一把?

Github地址