美文网首页无名之辈的Android之路
Android日志:模块化和组件化

Android日志:模块化和组件化

作者: 搬码人 | 来源:发表于2021-10-01 20:30 被阅读0次

    为什么会出现模块化和组件化?

    单工程项目的缺陷

    随着应用的迭代更新,业务代码持续增加,随之而来的问题逐步呈现出来:
    1、各种业务代码混杂在同一个模块里,开发人员在开发、调试过程的效率越来越低,比如定位某个业务问题,需要在多个业务代码混合的模块寻找和跳转。
    2、工程师需要了解各个业务的功能,避免代码的改动影响其他业务的功能,导致开发和维护成本不断增加。
    3、由于项目工程越来越大,编译完整代码所花费的时间越来越长。
    4、多人协作开发时,开发风格不一,又很难将业务完整分开,大家互相影响,导致开发效率低下。
    5、代码复用性差,写过的代码很难抽离出来再次利用。

    模块化

    如何实现模块化

    这里先梳理模块化的实现步骤,后面再具体讲解什么是模块化以及模块化与组件化的区别。

    如果只是一个依赖库 在gradle配置里面 -> id 'com.android.library' -> 不可运行的库 apk包
    id 'com.android.application' ->可运行的程序 arr包

    添加模块依赖步骤


    image.png image.png

    配置了模块依赖之后,“壳”工程的gradle中就会出现下方的依赖代码

    implementation project(path ':模块名')

    最外层的壳App module配置的是Application插件,业务组件module配置的是Library插件。想要实现业务组件的独立调试,这就需要把配置改为Application插件;而独立开发调试完成后,有需要变回Library插件进行集成调试。

    如何让组件在这两种调试模式之间自动转换

    1、手动配置更改
    2、设置变量
    AndroidStudio创建一个Android项目后,会在根目录中生成一个gradle.properties文件。在这个文件定义的常量,可以被任何一个build.gradle读取。所以我们可以在gradle.properties中定义一个常量值isModule,true为独立调试;false为集成调试。然后在业务组件的build.gradle中读取isModule,设置成对应的插件即可。

    组件化(Component)

    什么是组件化

    组件(Component),是对数据和方法的简单封装,功能单一,高内聚,并且是业务能划分的最小粒度。
    组件化是基于可重用的目的,将大型的软件系统按照分离关注点的形式,拆分成多个独立的组件,使得整个软件系统也可以做到像电路板一样,是单个或多个组件元件组装起来,哪个组件坏了,整个系统可以继续运行,而不出现崩溃或不正常现象,做到更少的耦合和更高的内聚。

    为什么使用组件化

    组件化基于可重用的目的,将应用拆分成多个独立组件,以减少耦合:

    • 通过关注点分离的形式,将App分离成多个模块,每个模块都是一个组件。解决了各种业务代码耦合在一起导致的问题。
    • 开发的过程中,扔这些组件被其他组件依赖,但是在调试时也可以单独成为独立的工程并且运行,这样就解决了因为编译耗时过多导致开发效率的问题。
    • 多人开发中,每个组件由担任负责,降低了开发之间沟通成本,减少因代码风格不一而产生的相互影响。

    区分模块化与组件化

    模块化就是将一个程序按照其功能做拆分,分成相互独立的模块,以便于每个模块值包含与其功能相关的内容,比如登录功能可以是一个模块,搜索功能可以是一个模块。

    组件化是模块化思想的演进,它们两者本质都是一致的。

    • 组件化更注重关注点分离,所谓的关注点分离,就是把复杂的问题做合理分解,再分别仔细研究这些问题的不同关注点,最后综合得到整体解决方案。
    • 如果从集合角度来看的话,可以说往往一个模块包含了一个或多个组件,或者说模块式一个容器,由组件组装成。简单来说,组件化相比模块化粒度更小。

    区分组件化与插件化

    插件化也是基于模块化的思想,将应用拆分成多个模块,而这些模块都是一个APK,最终打包时将宿主APK和插件APK。

    插件化与组件化存在很多相似之处,但是它们根本的区别在于:

    • 组件化的模块虽然在调试的时候可以切换到application独立运行,但是最终在打包时,每个模块始终只是一个library,整个应用只有单独的一个APK。
    • 插件化是拆分出了多个APK,并且在运行时通过动态加载的技术方案,来加载这些APK。

    组件化结构

    不同的人有不同的组件化方式,这里只是常见组件化的一种


    组件化架构

    主工程(app)
    1、只依赖个业务组件,通过配置指定依赖哪些业务组件;
    2、除了一些全局的配置和Activity之外,不包含任何业务代码,是应用的入口;
    业务组件层
    1、各组件之间无直接关联,通过路由进行通信;
    2、可直接依赖基础组件层,同时也能依赖公用的一些功能组件;
    3、业务组件可以在library和application之间切换,但是最后打包时必须是library;
    功能组件层
    1、依赖基础组件层
    2、对一些公用的功能业务尽心封装与实现;
    基础组件层
    1、封装公用的基础组件(并且实现了路由);

    2、网络访问框架、图片加载框架等主流的第三方库;
    3、各种第三方的SDK。

    组件化实现步骤

    1、创建各个组件层

    创建业务组件
    moduleCore就是业务组件层

    image.png

    这里进行模拟,创建两个业务组件main和login

    image.png

    创建基础组件层


    image.png

    创建功能组件


    image.png

    创建依赖层


    image.png

    2、如何建立组件之间的业务管理

    为什么要建立组件之间的业务管理?
    因为各个模块可能会依赖多种第三方的库,以及SDK版本。为了避免因版本不一致的问题而产生依赖冲突。那么实现版本的统一管理就需要抽离公共组件和开源项目。
    抽取公共组件、开源项目
    在整个项目gradle中添加ext(自己的命名)
    isDebug变量的作用:使模块更加灵活地在调试与编译打包之间转换。手动的转换过程已经在上方模块化实现过程中说明。(下方的代码中有涉及到ARouter的依赖代码,将在下一部分讲解)

    ext{
    
        isDebug = false //定义变量,当为true的时候模块可以单独运行,否则模块不能单独运行
    
        android = [
                compileSdkVersion: 30,
                buildToolsVersion:"30.0.3",
                targetSdkVersion:30,
                minSdkVersion: 19,
                versionCode: 1,
                versionName: "1.0"
    
        ]
    
    
    
        applicationId = [
                "app" : "com.example.component",
                "main": "com.example.module.main",
                "login":"com.example.module.login"
        ]
    
        library = [
                "appcompat": 'androidx.appcompat:appcompat:1.2.0',
                "materail": 'com.google.android.material:material:1.2.1',
                "constraintlayout": 'androidx.constraintlayout:constraintlayout:2.0.4'
        ]
    
        libARouter = "com.alibaba:arouter-api:1.5.1"
        libARouterCompiler = "com.alibaba:arouter-compiler:1.5.1"
        libGson = "com.google.code.gson:gson:2.8.6"
    }
    

    在libBase中(依赖层),更换一切公共版本组件以及第三方依赖的代码。
    api与implementation的区别:api会向上层传递,所以在以来层中需要使用api依赖第三方库传递给其它层的组件。

    plugins {
            id 'com.android.library'
            id 'kotlin-android'
            id 'kotlin-kapt'
    }
    def cfg = rootProject.ext
    
    
    android {
        compileSdkVersion cfg.android.compileSdkVersion
        buildToolsVersion cfg.android.buildToolsVersion
    
        defaultConfig {
            minSdkVersion cfg.android.minSdkVersion
            targetSdkVersion cfg.android.targetSdkVersion
            versionCode cfg.android.versionCode
            versionName cfg.android.versionName
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
        kotlinOptions {
            jvmTarget = '1.8'
        }
    }
    
    
    dependencies {
        implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
        implementation 'androidx.core:core-ktx:1.3.2'
        api cfg.library.appcompat
        api cfg.library.materail
        api cfg.library.constraintlayout
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    
        api cfg.libARouter
    }
    

    applicationId的值没有必要和文件名一样,只是身份的一个象征,当我们在安装的时候发现已经存在相同的applicationId那么新的应用将对原来的应用进行覆盖。

    在modulesCore中(服务组件层)
    这里与依赖层有点小区别,首先是apply plugin与plugins:当使用plugins时,其必须置顶,也就是说其头部不能有任何其他的代码。
    其次是服务层涉及到调试与编译打包的切换,只有应用程序才有applicationId,所以依赖层不需要切换。


    image.png image.png

    main组件的gradle

    def cfg = rootProject.ext
    
    if (cfg.isDebug){
        apply plugin:'com.android.library'
        apply plugin:'kotlin-android'
        apply plugin: 'kotlin-kapt'
    }else {
        apply plugin:'com.android.application'
        apply plugin:'kotlin-android'
        apply plugin: 'kotlin-kapt'
    }
    
    
    
    
    android {
        compileSdkVersion cfg.android.compileSdkVersion
        buildToolsVersion cfg.android.buildToolsVersion
    
        defaultConfig {
            if (!cfg.isDebug){
                applicationId cfg.applicationId.main
            }
            minSdkVersion cfg.android.minSdkVersion
            targetSdkVersion cfg.android.targetSdkVersion
            versionCode cfg.android.versionCode
            versionName cfg.android.versionName
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            kapt {
                arguments {
                    arg("AROUTER_MODULE_NAME", project.getName())
                }
            }
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    
        sourceSets {
            main{
                if (cfg.isDebug){
                    mainfest.srcFile 'src/main/AndroidManifest.xml'
                }else {
                    manifest.srcFile 'src/main/debug/AndroidManifest.xml'
                }
            }
        }
        kotlinOptions {
            jvmTarget = '1.8'
        }
    }
    
    dependencies {
    
        implementation project(':modulesBase:libBase')
        implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
        implementation 'androidx.core:core-ktx:1.3.2'
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    
        annotationProcessor cfg.libARouterCompiler
    }
    

    login组件的gradle

    def cfg = rootProject.ext
    
    if (cfg.isDebug){
        apply plugin:'com.android.library'
        apply plugin:'kotlin-android'
        apply plugin: 'kotlin-kapt'
    }else {
        apply plugin:'com.android.application'
        apply plugin:'kotlin-android'
        apply plugin: 'kotlin-kapt'
    }
    
    
    
    android {
        compileSdkVersion cfg.android.compileSdkVersion
        buildToolsVersion cfg.android.buildToolsVersion
    
        defaultConfig {
            if (!cfg.isDebug){
                applicationId cfg.applicationId.login
            }
            minSdkVersion cfg.android.minSdkVersion
            targetSdkVersion cfg.android.targetSdkVersion
            versionCode cfg.android.versionCode
            versionName cfg.android.versionName
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            kapt {
                arguments {
                    arg("AROUTER_MODULE_NAME", project.getName())
                }
            }
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    
        sourceSets {
            main{
                if (cfg.isDebug){
                    mainfest.srcFile 'src/main/AndroidManifest.xml'
                }else {
                    manifest.srcFile 'src/main/debug/AndroidManifest.xml'
                }
            }
        }
        kotlinOptions {
            jvmTarget = '1.8'
        }
    }
    
    dependencies {
    
        implementation project(':modulesBase:libBase')
        implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
        implementation 'androidx.core:core-ktx:1.3.2'
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    
        annotationProcessor cfg.libARouterCompiler
    }
    

    功能组件层

    plugins {
        id 'com.android.library'
        id 'kotlin-android'
        id 'kotlin-kapt'
    }
    def cfg = rootProject.ext
    
    
    android {
        compileSdkVersion cfg.android.compileSdkVersion
        buildToolsVersion cfg.android.buildToolsVersion
    
        defaultConfig {
            minSdkVersion cfg.android.minSdkVersion
            targetSdkVersion cfg.android.targetSdkVersion
            versionCode cfg.android.versionCode
            versionName cfg.android.versionName
    
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        }
    
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
        kotlinOptions {
            jvmTarget = '1.8'
        }
    }
    
    
    dependencies {
        implementation project(':modulesBase:libBase')
        implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
        implementation 'androidx.core:core-ktx:1.3.2'
        testImplementation 'junit:junit:4.+'
        androidTestImplementation 'androidx.test.ext:junit:1.1.2'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    }
    

    app“壳”中:如果不是调试模式,那么就对业务组件进行依赖

    image.png

    模块间通讯

    ARouter实现模块间通讯

    相关文章

      网友评论

        本文标题:Android日志:模块化和组件化

        本文链接:https://www.haomeiwen.com/subject/udlrgltx.html