1.概述

早期用 Eclipse 进行 Android 开发,创建一个工程,对于引入开源框架时候,采用的是用库的形式进行引入;到后来有 Android Studio 的出现,在 Project 下可以存在多个 module,除了要运行的 module 是 application 外,其他 module 都是 library。在每个 module 的 build.gradle 文件中区分:

应用:

apply plugin: 'com.android.application'

库:

apply plugin: 'com.android.library'

2. 模块化和组件化

2.1 模块化

把常用的功能、控件、基础类、第三方库、权限等公共部分抽离封装,把业务拆分成N个模块进行独立(module)的管理;而所有的业务组件都依赖于封装的基础库,业务组件之间不做依赖,这样的目的是为了让每个业务模块能单独运行。模块化的特点是:模块之间解耦,可以独立管理。

如下项目采用模块化,只有一个模块是 application,其他的都是 libary:

优缺点:

如上图所示,将自定义的软键盘、自定义控件等分为不同的模块,彼此之间降低耦合;而且模块可以复用,甚至不同的项目都可以复用。随着业务越来越多,模块也越来越多,当模块原来越多,每次进行编译都要长时间的等待。

2.2 组件化

将一个app的代码拆分成几份独立的组件,组件之间是低耦合的,可以独立编译打包;也可以将组件打包到一个apk中。组件化的核心是角色的转换。 在打包时, 是library; 在调试时, 是application。组件化后的优点:每个模块可独立编译,提高了编译速度;开发只负责自己的模块,还可以再做的隔离一些,每个业务线只可见自己业务模块的代码,避免了误修改和版本管理问题;公共的Lib依然是个独立模块。

组件化与模块化的区别:是每个模块的角色的转换,一个组件可以独立编译打包,也可以作为lib集成到整个apk中。

3.组件化的实践

最近瑞幸咖啡正处于风口浪尖,就以瑞幸咖啡的APP为模板:

分为五个模块:首页、菜单、潮品、购物车、我的。

3.1 创建模块,并实现组件模式和集成模式的切换

创建五个module,但是五个 module 都是application,可以独立运行,这里对 application 模块名进行统一格式进行命名。对于application 模块以 module_X 的格式进行命名,而对于 library 模块以 lib_X 的格式进行命名。

创建了三个不同的module,如何做到在调试阶段,每个module都能独立运行,在发布时候集成编译成一个APP呢?Android Studio中的Module主要有两种属性,分别为:application属性和library属性;在项目的根目录下的文件 gradle.properties 中定义相关常量,所有的 module 都能读取该常量。用这种办法来切换module为应用还是库。 

gradle.properties 文件中定义五个参数用来控制五个不同的模块是否是独立编译:

# isBuildModuleXX 用于控制是否单独编译,true:单独编译,false:library形式#首页模块isBuildModuleHome=true#菜单模块isBuildModuleMenu=true#潮品模块isBuildModuleFashions=true#购物车模块isBuildModuleShopping=true#我的模块isBuildModuleUser=true

在五个 module 的 build.gradle 中读取 gradle.properties中的常量,决定 module 为应用还是库:

if (isBuildModuleHome.toBoolean()) {    apply plugin: 'com.android.application'} else {    apply plugin: 'com.android.library'}

3.2 不同的模块,确保 SDK 和引入库版本号一致

不同模块的依赖 SDK 版本不一致或者引入的库版本不一致会导致编译问题和兼容性问题。解决办法是在主项目最外层用一个文件来统一管理引入库和 SDK 的版本。

在工程的根目录创建文件 dependency.gradle:

ext {    minSdkVersion = 16    targetSdkVersion = 29    compileSdkVersion = 29    buildToolsVersion = "29.0.3"    versionCode = 1    versionName = "1.0"    versions = [            ...            ...    ]    dependencies = [        ...        ...    ]}

在工程的 build.gradle 中引入文件 dependency.gradle:

apply from: "dependency.gradle"

在模块中的 build.gradle 实现对文件 dependency.gradle 的使用:

android {    compileSdkVersion rootProject.ext.compileSdkVersion    buildToolsVersion rootProject.ext.buildToolsVersion    defaultConfig {        minSdkVersion rootProject.ext.minSdkVersion        targetSdkVersion rootProject.ext.targetSdkVersion        versionCode rootProject.ext.versionCode        versionName rootProject.ext.versionName        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"        consumerProguardFiles 'consumer-rules.pro'    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'        }    }}

3.3 AndroidManifest 管理问题

在创建模块的时候,是以 application 的形式进行创建的,所以每个模块都有程序的启动入口。但是一旦整合起来后,如果继续保持多个启动入口,明显是有问题的。

在 main 目录下新增文件夹 compound,用于模块以 library 形式存在时候提供 AndroidManifest.xml ;而Android studio 产生的原路径下的 AndroidManifest.xml 文件保留,用于存储单独编译时候的 AndroidManifest.xml 文件。

 

在 library 状态下的 AndroidManifest.xml 文件:

<?xml version="1.0" encoding="utf-8"?>                        

单独编译状态下的 AndroidManifest.xml 文件:

<?xml version="1.0" encoding="utf-8"?>                                                                                

不同状态下定义好了不同的 AndroidManifest.xml 文件了,而 AndroidStudio 该如何识别呢?

在组件的 build.gradle 文件的 android 中进行 manifest 的管理:

android {        ...    ...    sourceSets {        main {            if (isBuildModuleHome.toBoolean()) {                manifest.srcFile 'src/main/AndroidManifest.xml'            }else{                manifest.srcFile 'src/main/compound/AndroidManifest.xml'            }        }    }}

3.4 基础公共组件

以 library 形式创建模块 lib_base 和 lib_common,其中 lib_base用于第三方库的统一依赖,lib_common 用于封装公共部分,如对于网络请求等的二次封装等。

lib_base 的 build.gradle文件如下所示:

apply plugin: 'com.android.library'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'def root_dependencies = rootProject.ext.dependenciesandroid {    compileSdkVersion rootProject.ext.compileSdkVersion    buildToolsVersion rootProject.ext.buildToolsVersion    defaultConfig {        minSdkVersion rootProject.ext.minSdkVersion        targetSdkVersion rootProject.ext.targetSdkVersion        versionCode rootProject.ext.versionCode        versionName rootProject.ext.versionName        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"        consumerProguardFiles 'consumer-rules.pro'    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'        }    }}dependencies {    implementation fileTree(dir: 'libs', include: ['*.jar'])    api root_dependencies.kotlin    api root_dependencies.appcompat    api root_dependencies.androidx_core    api root_dependencies.glide    annotationProcessor root_dependencies.glide_compiler    api root_dependencies.retrofit    api root_dependencies.converter_gson    api root_dependencies.retrofit_adapter_rxjava    api root_dependencies.rxjava    api root_dependencies.constraintlayout    api root_dependencies.okhttp    api root_dependencies.eventbus    api root_dependencies.easypermissions    api root_dependencies.coroutines_core}

其中对于依赖的第三方库版本的定义在工程根目录下的文件 dependency.gradle 中:

ext {    minSdkVersion = 16    targetSdkVersion = 29    compileSdkVersion = 29    buildToolsVersion = "29.0.3"    versionCode = 1    versionName = "1.0"    versions = [            appcompat              : "1.1.0",            androidx_core          : "1.2.0",            kotlin                 : "1.3.70",            glide                  : "4.11.0",            glide_compiler         : "4.11.0",            retrofit               : "2.7.2",            converter_gson         : "2.7.2",            retrofit_adapter_rxjava: "2.7.2",            rxjava                 : "3.0.0",            constraintlayout       : "1.1.3",            okhttp                 : "4.0.0",            eventbus               : "3.2.0",            easypermissions        : "3.0.0",            coroutines_core        : "1.3.5"    ]    dependencies = [            kotlin                 : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$versions.kotlin",            androidx_core          : "androidx.core:core-ktx:$versions.androidx_core",            appcompat              : "androidx.appcompat:appcompat:$versions.appcompat",            glide                  : "com.github.bumptech.glide:glide:$versions.glide",            glide_compiler         : "com.github.bumptech.glide:compiler:$versions.glide_compiler",            retrofit               : "com.squareup.retrofit2:retrofit:$versions.retrofit",            converter_gson         : "com.squareup.retrofit2:converter-gson:$versions.converter_gson",            retrofit_adapter_rxjava: "com.squareup.retrofit2:adapter-rxjava2:$versions.retrofit_adapter_rxjava",            rxjava                 : "io.reactivex.rxjava3:rxjava:$versions.rxjava",            constraintlayout       : "androidx.constraintlayout:constraintlayout:$versions.constraintlayout",            okhttp                 : "com.squareup.okhttp3:okhttp:$versions.okhttp",            eventbus               : "org.greenrobot:eventbus:$versions.eventbus",            easypermissions        : "pub.devrel:easypermissions:$versions.easypermissions",            coroutines_core        : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$versions.coroutines_core"    ]}

在 lib_commom 中定义对 lib_base的依赖,build.gradle 文件如下:

......dependencies {    implementation fileTree(dir: 'libs', include: ['*.jar'])    api project(path: ':lib_base')}

业务模块实现对 lib_commom 的依赖:

dependencies {    implementation fileTree(dir: 'libs', include: ['*.jar'])    implementation project(':lib_common')}

3.5 使用 ARouter 进行组件之间的调用和通信

由于业务组件间不存在依赖关系,不可以通过 Intent 进行跳转。组件化的目的是为了解决模块间的强依赖问题,这时候就需要借助于路由的,我使用的是阿里巴巴的开源框架 ARouter。

ARouter 的地址:https://github.com/alibaba/ARouter

3.5.1 ARouter 的导入

根据官方的文档,需要添加的依赖有:

dependencies {    // Replace with the latest version    compile 'com.alibaba:arouter-api:?'    annotationProcessor 'com.alibaba:arouter-compiler:?'    ...}

在这里,我将 arouter-api 添加在 lib_base 中,arouter-compiler 必须添加在每个组件模块中。由于此项目采用的语言为 kotlin,配置如下:

需要在每个组件模块中添加:

android {        ...    ...    defaultConfig {                ...        ...        kapt {            arguments {                arg("AROUTER_MODULE_NAME", project.getName())            }        }    }    ...    ...}dependencies {    ...    ...    kapt root_dependencies.arouter_compiler        ...    ...}

而且还需要添加:

apply plugin: 'kotlin-kapt'

3.5.2 页面间的简单跳转

在目标页面 Activity/Fragment 类上写上 Route Path 的注解,这里的路径需要注意的是至少需要有两级,/xx/xx。

@Route(path = "/home/HomeActivity")class HomeActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_home)    }    override fun onDestroy() {        super.onDestroy()    }}

在需要进行跳转的页面进行触发跳转:

    fun click(v: View) {        when (v.id) {            R.id.btn_home -> {                ARouter.getInstance().build("/home/HomeActivity").navigation()            }            R.id.btn_menu -> {                ARouter.getInstance().build("/menu/MenuActivity").navigation()            }            R.id.btn_fashions -> {                ARouter.getInstance().build("/fashions/FashionsActivity").navigation()            }            R.id.btn_shopping -> {                ARouter.getInstance().build("/shopping/ShoppingActivity").navigation()            }            R.id.btn_user -> {                ARouter.getInstance().build("/user/UserActivity").navigation()            }        }    }

效果如下:

这里只是列举了页面的简单跳转,如果需要在跳转的时候携带参数等,可以参照 ARouter 的介绍:https://github.com/alibaba/ARouter

4.总结

此篇文章只是针对组件化进行简单的实现,其中还有很多问题没有描述具体的解决办法,比如如何在每个模块获取登录信息、如何避免资源名字冲突等问题。

更多相关文章

  1. React Native开发指南
  2. Android对ListView控件增删改查
  3. Android(安卓)开源组件PagerBottomTabStrip 快速构建底部导航栏
  4. Android(安卓)帧动画 的实现
  5. 在Android中借助TensorFlow使用机器学习(译)
  6. android MediaCodec ACodec OMX tips
  7. Android(安卓)aar与 jar
  8. 将第三方apk变成系统apk
  9. Master OpenCV with Pratical Computer Vision Projects——如何

随机推荐

  1. windows定时执行PHP文件
  2. ThinkPHP利用.htaccess文件的Rewrite规则
  3. PHP错误处理函数
  4. php命令行下相对路径问题的解决方法
  5. php的use和require的区别
  6. php实现简单MVC
  7. PHP数组合并之array_merge和数组相加
  8. PHP操作数据库
  9. php代码连不上mysql
  10. php final关键字的应用