无名之辈的Android之路

Android日志:模块化和组件化

2021-10-01  本文已影响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实现模块间通讯

上一篇下一篇

猜你喜欢

热点阅读