Android模块化方案分享

为什么模块化

随着业务模块不断增加,业务线的增多,功能也越来越多。虽然项目按业务或者功能分包,但是随着代码的增多,代码结构不清晰;每个模块之间的代码耦合变得越来越严重,解耦问题急需解决;同时编译时间也会越来越长,打包速度变慢。如果再加上App各个业务模块比较独立和清晰,这就促使了项目走上模块化开发。这也是我们团队当初要模块化的原因,经过改造一个版本,到最后的稳定上线。模块化的过程中,遇到的问题比较多,细节也比较多,在此只是简单分享下模块化的大致步骤和流程。

如何模块化

1:整体项目大致结构
722143-f6b5cf42367bf514.png
Android模块化架构图.png
注解 解释
Shell App App壳工程,负责管理各个业务模块,以及主工程的打包。
moudlea,moduleb,modulec…… 不同代表不同的业务模块,也可以是热更新业务,可以单独运行,调试,也可以在外壳App中集成模式运行。
basecommonbusiness 不同业务模块抽出公共的业务逻辑,以及定义的各种基类,例如basemoduleactivity,baseapplication,arouter路由配置。
commontoollib 基础模块,各个业务实现需要的基础工具类
basecorelib 功能模块,第三方组件rn,library,sdk,aar组件例如networklib,downloadlib,burypointlib,paylib等
baseuilib theme,shape,color,一些公用的控件,例如加载刷新控件,自定义的dialog等
CI 持续集成工具(jenkins)
2:如何设置模块模式,集成模式调试和运行
gradle.properties
壳App的build.gradle和module中build.gradle配置

首先通过gradle.properties文件,定义isModule布尔变量,来控制是否开启模块单独运行和集成运行的开关。如果是一个独立运行的项目,build.gradle中apply plugin需要是application并且applicationId,以及AndroidManifest会有相关的配置,所以在壳app和各个业务module中根据isModule来控制这些。

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

defaultConfig {
    if(isModule.toBoolean()) {
        applicationId "com.zzti.gank.modulea"
        multiDexEnabled true
    }
    minSdkVersion build_versions.min_sdk
    targetSdkVersion build_versions.target_sdk
    versionCode 1
    versionName "1.0"
    resourcePrefix "modulea_"

    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    flavorDimensions "1"

    javaCompileOptions {
        annotationProcessorOptions {
            arguments = [moduleName: project.getName()]
        }
    }

}

buildTypes {

    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}

sourceSets {
    main {
        if (isModule.toBoolean()) {
            manifest.srcFile 'src/main/module/AndroidManifest.xml'
        } else {
            manifest.srcFile 'src/main/AndroidManifest.xml'
            java {
                exclude 'debug/**'
            }
        }
    }
}
}

dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation deps.support.app_compat
implementation deps.constraint_layout
testImplementation 'junit:junit:4.12'
androidTestImplementation deps.runner
androidTestImplementation deps.espresso.core
api deps.arouter.runtime
annotationProcessor deps.arouter.compiler
api project(':basecommonbusiness')
}
3:如何解决模块之间依赖冲突,资源冲突
依赖冲突

在模块化的过程中,最常见的应该是依赖冲突。由于团队成员之间sdk更新版本的不同,gradle插件版本不同,这样成员在提交代码后,其他成员拉取后,可能需要不断的更新sdk或者gradle插件,甚至提交的壳app的gradle插件和持续开发工具(例如jenkins)配置的不一样,这样会导致打包失败。除此之外还有一些jcenter引入的三方库冲突,以及第三方库中,同样存在相同的依赖,但是版本不同,这时候需要用exclude group排除下相同依赖。如何控制版本管理?这时候需要新建一个version.gradle文件,统一处理jcenter引入的三方库,分组或者单一的统一版本信息,配置compileSdkVersion、buildToolsVersion、minSdkVersion、targetSdkVersion版本管理,以及sdk版本和gradle的插件版本等。如果你熟悉Groovy语言,对在gradle里面的配置就可以轻车熟路。

def versions = [:]
versions.android_gradle_plugin = "3.1.0"
versions.support = "27.1.1"
versions.constraint_layout = "1.1.3"
versions.junit = "4.12"
versions.runner = "1.0.2"
versions.espresso = "3.0.2"
versions.arouter_version = "1.2.4"
versions.arouter_processor_version = "1.1.4"


def build_versions = [:]
build_versions.compileSdkVersion = 27
build_versions.build_tools= "27.0.3"
build_versions.min_sdk = 15
build_versions.target_sdk = 27
build_versions.versionCode = 100
build_versions.versionName = "1.0.0"
build_versions.versionsupport = "27.1.0"
ext.build_versions = build_versions

def deps = [:]
deps.android_gradle_plugin =     "com.android.tools.build:gradle:$versions.android_gradle_plugin"
deps.junit = "junit:junit:$versions.junit"
deps.constraint_layout = "com.android.support.constraint:constraint-  layout:$versions.constraint_layout"
deps.runner = "com.android.support.test:runner:$versions.runner"


def support = [:]
support.app_compat = "com.android.support:appcompat-v7:$versions.support"
support.annotations = "com.android.support:support-        annotations:$versions.support"
 deps.support=support

def espresso = [:]
espresso.core = "com.android.support.test.espresso:espresso-  core:$versions.espresso"
espresso.contrib = "com.android.support.test.espresso:espresso-contrib:$versions.espresso"
espresso.intents = "com.android.support.test.espresso:espresso-intents:$versions.espresso"
deps.espresso = espresso

def arouter = [:]
arouter.runtime = "com.alibaba:arouter-api:$versions.arouter_version"
arouter.compiler = "com.alibaba:arouter-    compiler:$versions.arouter_processor_version"
deps.arouter = arouter

ext.deps = deps

def addRepos(RepositoryHandler handler) {
handler.maven {
    url 'https://maven.google.com/'
    name 'Google'
}
handler.jcenter()
handler.mavenCentral()
}
ext.addRepos = this.&addRepos
资源冲突

不同模块对于资源的命名可能会有冲突,为了防止不同模块的资源应为命名冲突而被错误的覆盖,就需要一种机制能够检查、提示、修改冲突的资源,因此需要在每一个业务module的build.gradle中添加resourcePrefix。如果这个模块里面存在自定义view或者自定义属性,它们都需要添加resourcePrefix的字段。

4:模块如何通信

模块间通信引入阿里ARouter,用来处理配置所有module的跳转路由,各个业务module之前的跳转,参数传递,解析参数,Interceptor 拦截器等,具体参考github地址ARouter ,至于模块之间的数据更新通知可以使用复杂的广播形式,或者EventBus

5:相关问题处理

模块化中遇到各种类型问题,在此抽几个典型的问题分享下。

静态常量问题

正常app项目在编译过程中,会生成R.java文件。R文件中包含有各个资源文件对应的id,这些变量是final类型的,但是在Library Module中,这些id不是final类型的。由于switch、case和第三方ButterKnife都需要静态常量,所有就会出现问题。因此子moudle中onClick的回调不可以使用switch、case,必须要用if else来的代替。至于ButterKnife,在ButterKnifeV8.4.0 之后修复了这个问题,在Library Module中,生成一个R2.java文件,在R2中各个id都是静态常量。所以在来回切项目时候,需要将R更换R2,这时候可以通过全局映射替换。

buildTypes设置

项目中会有多种buildTypes的设置,这时候壳App的buildTypes有多少,在每个子module的配置中也得保持一致。子module中不一定需要具体的配置,但是得保证每个都得有,不然会导致运行失败。

依赖管理

在没有模块化之前,编译整个项目或者调试自己负责的业务模块,编译速度会特别慢,导致效率低下,如果在模块化之后,可以让团队成员每人去拉取自己负责的业务模块,各个模块单独打包,调试,子模块开发完成以后,确定后版本后,发布到公司的私有maven仓库中,然后在主项目中使用版本进行依赖,提升开发效率。

模块化带来优势

a项目编译运行速度提高,提高效率
b代码规范,结构清晰,规范化管理,沉淀团队技术
c各模块业务独立清晰,新人较短时间熟悉项目很快入手
d部分模块可以尝试引用新技术,方便技术更新

写在最后

demo没有具体功能实现,但依旧按江湖规矩给出连接component

猜你喜欢

转载自blog.csdn.net/weixin_33805992/article/details/86981728