Android组件化 这可能是最完美的形态吧?
Android组件化的几种方式
一. 前言
Android开发为什么要组件化,有什么好处?可以看看之前的文章。
组件化的过程中其实都大同小异。结构与功能分为不同的层级:
各模块的跳转和业务通信通过路由转发:
这里讲一下常用的两种方案
二. 修改配置文件的方案
我们都知道组件Module是分为Application和library的:
- application属性,可以独立运行的Android程序,常见的App模块就是Application类型。
- library属性,不可以独立运行,一般是程序依赖的库文件。
那么我们就可以在跟gradle文件中配置,指定当前模块是否需要独立运行。
isNewsFeedModule = true
isProfileModule = true
isPartTimeModule = true
isPromotionModule = true
isWalletModule = true
isYYPayModule = true
isYYFoodModule = true
isRewardsModule = true
isResumeModule = true
isFreelancerModule = true
复制代码
在指定的模块如NewsFeed模块中配置是否需要独立运行:
if (isNewsFeedModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
复制代码
一个独立运行的application都是要有指定的appid的,那我们也得指定:
defaultConfig {
(!isNewsFeedModule.toBoolean()){
applicationId "com.mygroup.newsfeed"
}
}
复制代码
还有可能独立运行和依赖库的方式,它们的清单文件有差异导致不同,那么还得指定清单文件的路径:
sourceSets {
main {
if (isModule.toBoolean()) {
manifest.srcFile 'src/main/module/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
复制代码
最后,如果NesFeed模块是独立运行的,那么App模块不可能依赖一个Application吧。所以App的Build.gradle中也得修改:
if (isNeedHomeModule.toBoolean ()){
implementation project (':newsfeed')
}
复制代码
这样每一次想修改对应的模块的时候,就去根目录配置文件修改,然后build之后就能生效。这应该是大多数开发者惯用的组件化方式了吧。
三. 使用框架来实现配置的升级
其实关于配置,关于ApplicationId,清单文件和application与library的判断,都是有迹可循,可以使用代码代替的,由此出现了不少组件化的框架来替我们完成重复的工作。
比较出名的如JIMU。再比如另一个比较火的组件化框架DDComponent,他们替你完成了很大一部分的工作。你只需要引用它的插件
apply plugin: 'com.dd.comgradle'
复制代码
指定他独立运行的applicationName就能实现组件化了
combuild {
applicationName = 'com.luojilab.reader.runalone.application.ReaderApplication'
isRegisterCompoAuto = true
}
复制代码
其中还自带路由,可谓是方便到家了。
但是一些痛点是,他们基于Gradle插件生成代码,由于AGP7的api有变动,有可能升级到AGP7之后出现问题。还有就是多模块的组合测试不方便,比如我想测试NewsFeed,这个模块中关联了很多Profile模块的东西,那我单独测试就要引入这2个组件,但是他们是平级的。也导致测试不方便,只能运行主app模块来测试。
四. 自定义单独的独立运行模块
我们不使用框架,直接把全部的模块都设置为library,由app模块依赖,我们单独的建立runalone的application类型模块,可以单独的调试ProFile模块 ,也可以添加NewsFeed和Profile模块一起测试。
由于app模块没有依赖runalone的模块,所以对应apk的大小和性能也没有影响,可以说单独用于调试是很方便的。
结构如下:
settings.gradle:
include ':app',
':cs_router',
':cs_baselib',
':cs_cptServices',
':cpt_auth',
':cpt_main',
':cpt_parttime',
':cpt_newsfeed',
':cpt_im',
':cpt_ewallet',
':cpt_profile',
':cs_ninegrid',
':lib_xpopup',
':standalone:parttimerunalone',
':standalone:authrunalone',
':standalone:ewalletrunalone',
':standalone:newsfeedrunalone',
':standalone:profilerunalone'
复制代码
优势:
- 同样实现了组件化隔离
- 不需要修改配置反复编译
- 不需要导入第三方库导致开发成本和容错率提高
- 方便不同平级的模块组合调试
内部路由功能的实现:
一些框架都是自带的路由,其实思想都是和ARouter差不多。其他单独的组件化框架也有很多,例如app-joint。另一种方案就是大家耳熟能详的ARouter。
推荐大家使用Arouter,理由还是和上面一样,由gradle生成的代码有风险,AMS生成过程中依赖APG的api,一旦api有变动就无法使用。有可能升级到AGP7之后出现问题。
主要代码如下:
public class ARouterPath {
//App模块路由服务Path
public static final String PATH_SERVICE_APP = "/app/path/service";
//Auth模块路由服务Path
public static final String PATH_SERVICE_AUTH = "/auth/path/service";
//登录页面
public static final String PATH_AUTH_PAGE_LOGIN = "/auth/page/login";
//Main模块路由服务Path
public static final String PATH_SERVICE_MAIN = "/main/path/service";
//首页Main页面
public static final String PATH_MAIN_PAGE_MAIN = "/main/page/main";
//Wallet模块路由服务Path
public static final String PATH_SERVICE_WALLET = "/wallet/path/service";
//IM模块路由服务Path
public static final String PATH_SERVICE_IM = "/im/path/service";
//NewsFeed模块路由服务Path
public static final String PATH_SERVICE_NEWSFEED = "/newsfeed/path/service";
//PartTime模块路由服务Path
public static final String PATH_SERVICE_PARTTIME = "/parttime/path/service";
//Profile模块路由服务Path
public static final String PATH_SERVICE_PROFILE = "/profile/path/service";
//Service模块路由服务Path
public static final String PATH_SERVICE_SERVER = "/service/path/service";
}
复制代码
全局保管每个组件的Serivce对象
object YYRouterService {
var appComponentServer: IAppComponentServer? = ARouter.getInstance().navigation(IAppComponentServer::class.java)
var authComponentServer: IAuthComponentServer? = ARouter.getInstance().navigation(IAuthComponentServer::class.java)
...
}
复制代码
定义接口:
interface IAppComponentServer : IProvider {
fun initSMS(): IAppComponentServer
//Firebase短信服务-发送短信
fun sendSMSCode(
activity: Activity, phone: String,
sendAction: ((isSuccess: Boolean) -> Unit)?,
verifyAction: ((isSuccess: Boolean) -> Unit)?
)
//Firebase短信服务-验证短信
fun verifySMSCode(activity: Activity, code: String, verifyAction: ((isSuccess: Boolean) -> Unit)?)
fun gotoLoginPage()
}
复制代码
ARouter注解标注服务
@Route(path = ARouterPath.PATH_SERVICE_APP, name = "App模块路由服务")
class AppComponentServiceImpl : IAppComponentServer {
override fun initSMS(): IAppComponentServer {
return this
}
override fun sendSMSCode(
activity: Activity, phone: String, sendAction: ((isSuccess: Boolean) -> Unit)?, verifyAction: ((isSuccess: Boolean) -> Unit)?
) {
}
override fun verifySMSCode(activity: Activity, code: String, verifyAction: ((isSuccess: Boolean) -> Unit)?) {
}
override fun gotoLoginPage() {
LoginActivity.startInstance()
}
override fun init(context: Context?) {
}
}
复制代码
当然ARouter默认的页面导航也是能做的
@Route(path = ARouterPath.PATH_MAIN_PAGE_MAIN)
@AndroidEntryPoint
class MainActivity : YYBaseVDBActivity<MainViewModel, ActivityMainBinding>() {
companion object {
fun startInstance() {
val intent = Intent(commContext, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
commContext.startActivity(intent)
}
}
...
}
复制代码
至于为什么使用的是IProvide的方式来定义,是因为便于管理,每一个组件自己需要提供的服务或跳转由组件自己定义。没有完全的通过Activity的跳转来搭建路由,有可能你的应用不是基于Activity构建的呢?
基于单Activity+Fragment的构架的话,使用IProvide的方式也不会有影响。比如我们的项目就是把UI也组件化了,每一个组件都是Activity+多Fragment,总共8个组件就只有8个主要的Activity。
感谢看到这里,如果有不同意见,欢迎评论区讨论。
如果觉得不错还请点赞关注。后面可能会讲单Activity+多Fragment的几种方式。
好了,到处完结!
链接:https://juejin.cn/post/7099636408045961224
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。