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将变得更加强大。

相关推荐
用户2018792831672 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子2 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜82272 小时前
安卓接入Max广告源
android
齊家治國平天下2 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO2 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel2 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢2 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱
IT酷盖2 小时前
Android解决隐藏依赖冲突
android·前端·vue.js
努力学习的小廉3 小时前
初识MYSQL —— 数据库基础
android·数据库·mysql
风起云涌~4 小时前
【Android】浅谈androidx.startup.InitializationProvider
android