• 这可能是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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在指定的模块如NewsFeed模块中配置是否需要独立运行:

    if (isNewsFeedModule.toBoolean()) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    一个独立运行的application都是要有指定的appid的,那我们也得指定:

        defaultConfig {
              (!isNewsFeedModule.toBoolean()){
                applicationId "com.mygroup.newsfeed"
         }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    还有可能独立运行和依赖库的方式,它们的清单文件有差异导致不同,那么还得指定清单文件的路径:

      sourceSets {
            main {
                if (isModule.toBoolean()) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    最后,如果NesFeed模块是独立运行的,那么App模块不可能依赖一个Application吧。所以App的Build.gradle中也得修改:

       if (isNeedHomeModule.toBoolean ()){
            implementation project (':newsfeed') 
        } 
    
    • 1
    • 2
    • 3

    这样每一次想修改对应的模块的时候,就去根目录配置文件修改,然后build之后就能生效。这应该是大多数开发者惯用的组件化方式了吧。

    三. 使用框架来实现配置的升级

    其实关于配置,关于ApplicationId,清单文件和application与library的判断,都是有迹可循,可以使用代码代替的,由此出现了不少组件化的框架来替我们完成重复的工作。

    比较出名的如JIMU。再比如另一个比较火的组件化框架DDComponent,他们替你完成了很大一部分的工作。你只需要引用它的插件

    apply plugin: 'com.dd.comgradle'
    
    • 1

    指定他独立运行的applicationName就能实现组件化了

    combuild {
        applicationName = 'com.luojilab.reader.runalone.application.ReaderApplication'
        isRegisterCompoAuto = true
    }
    
    • 1
    • 2
    • 3
    • 4

    其中还自带路由,可谓是方便到家了。

    但是一些痛点是,他们基于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
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    优势:

    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";
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    全局保管每个组件的Serivce对象

    object YYRouterService {
    
        var appComponentServer: IAppComponentServer? = ARouter.getInstance().navigation(IAppComponentServer::class.java)
    
        var authComponentServer: IAuthComponentServer? = ARouter.getInstance().navigation(IAuthComponentServer::class.java)
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    定义接口:

    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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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?) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    当然ARouter默认的页面导航也是能做的

    @Route(path = ARouterPath.PATH_MAIN_PAGE_MAIN)
    @AndroidEntryPoint
    class MainActivity : YYBaseVDBActivity() {
    
        companion object {
            fun startInstance() {
                val intent = Intent(commContext, MainActivity::class.java)
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                commContext.startActivity(intent)
            }
        }
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    至于为什么使用的是IProvide的方式来定义,是因为便于管理,每一个组件自己需要提供的服务或跳转由组件自己定义。没有完全的通过Activity的跳转来搭建路由,有可能你的应用不是基于Activity构建的呢?

    基于单Activity+Fragment的构架的话,使用IProvide的方式也不会有影响。比如我们的项目就是把UI也组件化了,每一个组件都是Activity+多Fragment,总共8个组件就只有8个主要的Activity。


    针对这块知识点我在学习过程中,进行了详细的整理梳理了一些学习笔记,有需要参考学习的小伙伴可以 点击这里查看获取方式 传送门直达 !!!



  • 相关阅读:
    用googletest写cpp单测
    184.Flume(一):flume概念,flume安装,实时监控单个文件追加内容,监控目录下新增多个新文件,监控目录下多个文件追加内容
    秒杀项目——多级缓存
    使用vue-cli搭建SPA项目
    BasicDAO
    Python股票量化投资课学习—单均线双均线策略
    C++与C的区别终于说清楚了!
    Java - 缓冲输入输出流 (BufferedInputStream、BufferedOutputStream)
    Kotlin高仿微信-项目实践58篇
    ISO质量认证范围和审核范围的简要解说
  • 原文地址:https://blog.csdn.net/m0_71263309/article/details/126091304