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