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的几种方式。
好了,到处完结!

猜你喜欢

转载自juejin.im/post/7099636408045961224