注册

Android组件化 这可能是最完美的形态吧?

Android组件化的几种方式


一. 前言


Android开发为什么要组件化,有什么好处?可以看看之前的文章


组件化的过程中其实都大同小异。结构与功能分为不同的层级:


各模块的跳转和业务通信通过路由转发:


这里讲一下常用的两种方案




二. 修改配置文件的方案


我们都知道组件Module是分为Application和library的:



  1. application属性,可以独立运行的Android程序,常见的App模块就是Application类型。
  2. 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'
复制代码

优势:



  1. 同样实现了组件化隔离
  2. 不需要修改配置反复编译
  3. 不需要导入第三方库导致开发成本和容错率提高
  4. 方便不同平级的模块组合调试



内部路由功能的实现:


一些框架都是自带的路由,其实思想都是和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的几种方式。

好了,到处完结!



作者:newki
链接:https://juejin.cn/post/7099636408045961224
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册