Android AutoService 解耦实战

在android开发中,解耦一值是一个老生常谈的事情,有了AutoService后,会让我们在处理相关问题上更加得心应手。

实战的效果图如下:

就这?这不是有手就会么,这不是就是一个TAB+ViewPager 或者NavigationBar+NavigationBarItem吗?这也好意思拿的出手?

如果某天,我写好了HomePage模块和Mine模块,如果新增消息、问答模块。我怎么办?这个好办直接添加两个TAB就好了。可是这样就会打破开闭原则了,程序的耦合度太高了。

用AutoService可以完美解决上述问题,无论你是多人协作,还是孤军奋战都能满足。

就目前为止,当前项目有navigation、homepage、mine、app模块,依赖关系如下:

添加相关依赖

ini 复制代码
[versions]
autoservice = "1.1.1"

[libraries]
autoservice-annotations = { group = "com.google.auto.service", name = "auto-service-annotations", version.ref = "autoservice" }
autoservice-processor = { group = "com.google.auto.service", name = "auto-service", version.ref = "autoservice" }

app 

implementation(libs.autoservice.annotations)
kapt(libs.autoservice.processor)

navigation模块拥有最顶级的导航接口

定义顶级TAB接口

kotlin 复制代码
interface BottomPageProvider {
    fun getPageType(): String

    fun createPage(): @Composable () -> Unit

    fun getDisplayName(): String


    @get:DrawableRes
    val selectedIcon: Int

    @get:DrawableRes
    val unSelectedIcon: Int


    @get:ColorRes
    val selectedTextColor: Int
        get() = com.hhx.navigation.R.color.main_bottom_check

    @get:ColorRes
    val unSelectedTextColor: Int
        get() = android.R.color.black


    fun getOrder(): Int

    fun getCurrentShowTextColor(selected: Boolean): Int {
        return if (selected) {
            selectedTextColor
        } else {
            unSelectedTextColor
        }
    }

    
    fun getCurrentShowResource(selected: Boolean): Int {
        return if (selected) {
            selectedIcon
        } else {
            unSelectedIcon
        }
    }

}

BottomPageProvider 非常好理解 一个显示的名字(例如首页、我的)、选中的图标、未选中的图标、具体显示的内容createPage()返回,还有一个getOrder排序的字段(首页排在0号位置,我的排在1号位置)

再创建一个 BottomPageProviderRegistry

kotlin 复制代码
object BottomPageProviderRegistry {

    private val bottomPageproviders:List<BottomPageProvider> by lazy {
        ServiceLoader.load(BottomPageProvider::class.java)
            .iterator()
            .asSequence()
            .sortedBy { it.getOrder() }
            .toList()
            .also { list->
                Log.d("PageProvider", "Loaded ${list.size} page providers in order: " +
                        list.joinToString { "${it.getDisplayName()} (order: ${it.getOrder()})" })
            }
    }



    fun getProviders():List<BottomPageProvider> = bottomPageproviders




    fun getProviderByType(type:String):BottomPageProvider?{
        return  bottomPageproviders.firstOrNull(){it.getPageType()==type}
    }


}

它也非常简单,主要提供了一个BottomPageProvider的子类集合,并按照getOrder进行排序。注意:它主要是通过ServiceLoader去找BottomPageProvider的子类,怎么找,我们后面再说。

HomePage模块

kotlin 复制代码
@AutoService(BottomPageProvider::class)
class HomePageProvider:BottomPageProvider {

    override fun getDisplayName( )= "首页"

    override fun getPageType(): String {

        return "HomePage"
    }

    override fun getOrder(): Int {
        return  0
    }

    override val selectedIcon: Int
        get() = R.drawable.main_main_ic_bottom_press

    override val unSelectedIcon: Int
        get() = R.drawable.main_main_ic_bottom_default

    override fun createPage(): @Composable () -> Unit ={
        HomePage()
    }

}

它非常简单,作为HomePage的模块的入口,返回了选中,未选中的图标,除此之外还返回了需要显示的内容HomePage

kotlin 复制代码
@Composable
fun HomePage(modifier: Modifier = Modifier) {
   Box(
       modifier=Modifier.fillMaxSize(),
   ){
       Text("HomeRecommendPage")

   }
}

这个更简单,只是一个Text。

Mine模块和HomePage模块类似

kotlin 复制代码
@AutoService(BottomPageProvider::class)
class MineProvider:BottomPageProvider {
    override fun getPageType(): String {
        return "mine"
    }

    override fun createPage(): @Composable () -> Unit ={ MinePage() }

    override fun getDisplayName(): String {
       return "mine"
    }
    
    override val selectedIcon: Int
        get() = R.drawable.main_mine_ic_bottom_press
    override val unSelectedIcon: Int
        get() = R.drawable.main_mine_ic_bottom_default

    override fun getOrder(): Int {
       return 5
    }
}
kotlin 复制代码
@Composable
fun  MinePage() {
   Column {
       Text("MinePage")
   }
}

可以看到HomePageProvider和MineProvider都使用了AutoService 它其实就是一个注解,盲猜它肯定和前面的ServiceLoader有联系,我们稍后再说。

Main模块

ini 复制代码
@Composable
private fun MainScreen() {
    val pageProviders = remember { BottomPageProviderRegistry.getProviders() }

    var selectedPage by remember { mutableStateOf(0) }

    Scaffold(
        modifier = Modifier
            .background(color = Color.White),
        bottomBar = {
            NavigationBar(
                containerColor = colorResource(R.color.main_bottom_color),
                modifier = Modifier
                    .height(60.dp)
            ) {
                pageProviders.forEachIndexed { index, provider ->
                    val isSelected = selectedPage == index

                    NavigationBarItem(
                        colors = NavigationBarItemDefaults.colors(
                            indicatorColor = Color.Transparent,
                            selectedTextColor = colorResource(R.color.main_bottom_check),
                            unselectedTextColor = colorResource(R.color.main_bottom_uncheck),
                            selectedIconColor = Color.Unspecified,
                            unselectedIconColor = Color.Unspecified,
                        ),
                        icon = {
                            Icon(
                                painterResource(
                                    provider.getCurrentShowResource(isSelected)
                                ),
                                contentDescription = provider.getDisplayName(),
                                tint = Color.Unspecified,
                            )
                        },
                        label = { Text(provider.getDisplayName(), color = colorResource(provider.getCurrentShowTextColor(isSelected))) },
                        selected = isSelected,
                        onClick = {
                            selectedPage = index
                        },

                        )
                }
            }
        }
    ) { innerPadding ->
        Box(modifier = Modifier.padding(innerPadding)) {
            pageProviders.getOrNull(selectedPage)?.createPage()?.invoke()

        }
    }
}

main模块呢也比较简单,它相当于是一个容器。通过 BottomPageProviderRegistry.getProviders() 拿到应用中所有实现了BottomPageProvider的子类集合,然后将providers中的数据一一对应显示在了NavgiationBarItem上。最后再将pageProviders.getOrNull(selectedPage)?.createPage()?.invoke() 中的内容,显示在了Scaffold中。

App模块就更简单了,将MainScreen渲染出来就行。

说说上面代码的优点:

1、HomePage和Mine模块相互独立,互不干扰,无论是协同开发还是单人模块开发都非常方便。

2、代码的可移值性强,例如那天想不开,新增消息模块只需要单独写一个消息模块,然后消息模块实现BottomPageProvider即可。

3、Main模块只是作为容器,它并不参与业务流程,扩展性更强。完全可以将这一套代码移植到另外的项目中去。

好了,是时候来揭开AutoService的神秘面纱了。

前面提了一个疑问ServiceLoader是什么?

ServiceLoader就是一个Java类,使用该类,可以加载一个接口的所有实现类。比如上面的接口BottomPageProvider,我们通过ServiceLoader找到了HomePageProvider和MineProvider。

那么他是怎么找到的呢,我们需要手动配置。在META-INF/services 目录下创建一个以服务接口全限定名命名的文件(见图),例如:xxxx.xxxx.HomePageProvider和xxxx.xxxx.MineProvider。

可是我们刚刚的代码中并没有去手动创建啊?先思考一下手动创建文件有什么弊端。

  • 手动创建配置文件:你需要自己记住并创建这个配置文件。
  • 容易出错:容易写错文件名、类名,或者忘记创建文件。
  • 难以维护:当实现类改名、删除或增加时,必须手动同步更新这个配置文件,否则会导致运行时错误。 Google想到了上面的问题,于是乎AutoService就应运而生了,它帮我们自动去干上面手动配置的事情,如何证明:找到模块下build/tmp/kapt3/stubs/debug/META-INF/services文件。 请看图片:注意home模块的配置在home的build目录下,mine模块的配置在mine的build目录下。

从上面的实践我们可以看出,最终干事的还是ServiceLoader,AutoService它只是一个工具,帮我们处理了简单但极易出错的配置环节,可以说有了AutoService ServiceLoader将变得更加强大。

相关推荐
顾林海7 小时前
OkHttp拦截器:Android网络请求的「瑞士军刀」
android·面试·性能优化
jctech7 小时前
解构ComboLite:0 Hook的背后,是哪些精妙的架构设计?
android
jctech7 小时前
从0到1,用`ComboLite`构建一个“万物皆可插拔”的动态化App
android
jctech7 小时前
你的App越来越“胖”了吗?给Android应用“减肥”的终极秘诀——插件化
android
jctech7 小时前
告别Hook!ComboLite:为Jetpack Compose而生的下一代Android插件化框架
android
LiuYaoheng8 小时前
【Android】Notification 的基本使用
android·java·笔记·学习
上等猿9 小时前
JUC多线程个人笔记
android·java·笔记
fatiaozhang952711 小时前
创维LB2004_安装软件教程
android·网络·电视盒子·刷机固件·机顶盒刷机
来来走走17 小时前
Flutter MVVM+provider的基本示例
android·flutter