组件化开发

2017-10-20  本文已影响0人  笔墨Android

不怕跌倒,所以飞翔

组件化开发

参考资源

  1. Android组件化方案

为什么要组件化开发

解决问题

好处

具体实现方案

1.组件模式和集成模式的转换

android studio 中的Module主要又两种属性

组件化和集成模式的转换

说到这里我不想去讲解gradle的语法,因为我也不会,但是可以告诉你怎么去弄!!!

这里是引用张华洋的说明:博客地址

Module的属性是在每个组件的 build.gradle 文件中配置的,当我们在组件模式开发时,业务组件应处于application属性,这时的业务组件就是一个 Android App,可以独立开发和调试;而当我们转换到集成模式开发时,业务组件应该处于 library 属性,这样才能被我们的“app壳工程”所依赖,组成一个具有完整功能的APP;
但是我们如何让组件在这两种模式之间自动转换呢?总不能每次需要转换模式的时候去每个业务组件的 Gralde 文件中去手动把 Application 改成 library 吧?如果我们的项目只有两三个组件那么这个办法肯定是可行的,手动去改一遍也用不了多久,但是在大型项目中我们可能会有十几个业务组件,再去手动改一遍必定费时费力,这时候就需要程序员发挥下懒的本质了。
试想,我们经常在写代码的时候定义静态常量,那么定义静态常量的目的什么呢?当一个常量需要被好几处代码引用的时候,把这个常量定义为静态常量的好处是当这个常量的值需要改变时我们只需要改变静态常量的值,其他引用了这个静态常量的地方都会被改变,做到了一次改变,到处生效;根据这个思想,那么我们就可以在我们的代码中的某处定义一个决定业务组件属性的常量,然后让所有业务组件的build.gradle都引用这个常量,这样当我们改变了常量值的时候,所有引用了这个常量值的业务组件就会根据值的变化改变自己的属性;可是问题来了?静态常量是用Java代码定义的,而改变组件属性是需要在Gradle中定义的,Gradle能做到吗?
Gradle自动构建工具有一个重要属性,可以帮助我们完成这个事情。每当我们用AndroidStudio创建一个Android项目后,就会在项目的根目录中生成一个文件 gradle.properties,我们将使用这个文件的一个重要属性:在Android项目中的任何一个build.gradle文件中都可以把gradle.properties中的常量读取出来;那么我们在上面提到解决办法就有了实际行动的方法,首先我们在gradle.properties中定义一个常量值 isModule(是否是组件开发模式,true为是,false为否):

在gradle.properties中添加是否组件化的判断

PS:首先gradle.properties这个文件有一个重要的功能:在Android项目中的任何一个build.gradle文件中都可以把gradle.properties中的常量读取出来(但是读取出来的时候是String格式的字符串,这一点要切记)

在gradle.properties中添加

# 是否组件化的标识,注意啊这里要用"#"号注释
isModule = false 

然后在相应的Module中的build.gradle去添加相应的逻辑

//这里说明一下就是如果是组件化的
if (isModule.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

这样写完了之后就会在每次改变*isModule赋值的化就可以更改相应的模块是否能单独运行了(为了保证单独组件的单独运行,会创建一个公用的Module,存放网络请求或者图片处理等一些公共的资源,从而确保module的单独运行)

组件之间AndroidManifest合并问题

这里是引用张华洋的说明:博客地址

但是大家应该注意到这个问题是在组件开发模式和集成开发模式之间转换引起的问题,而在上一节中我们已经解决了组件模式和集成模式转换的问题,另外大家应该都经历过将 Android 项目从 Eclipse 切换到 AndroidStudio 的过程,由于 Android 项目在 Eclipse 和 AndroidStudio开发时 AndroidManifest.xml 文件的位置是不一样的,我们需要在build.gradle 中指定下 AndroidManifest.xml 的位置,AndroidStudio 才能读取到 AndroidManifest.xml,这样解决办法也就有了,我们可以为组件开发模式下的业务组件再创建一个 AndroidManifest.xml,然后根据isModule指定AndroidManifest.xml的文件路径,让业务组件在集成模式和组件模式下使用不同的AndroidManifest.xml,这样表单冲突的问题就可以规避了。

创建一个新的AndroidManifest.xml

具体的解决方案就是在相应的Module中在根路径添加一个Module文件夹,直接创建一份AndroidManifest.xml,这里面的这个文件,直接像你平时写项目一样去写就可以了

修改相应的build.gradle
在android标签内创建
 sourceSets {
        main {
            if (isModule.toBoolean()) {/*是组件化*/
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java {/*集成开发模式下排除debug文件加中的所有java文件*/
                    exclude 'debug/**'//这个相应的debug文件是在java文件目录下的
                }
            } else {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            }
        }
    }

全局Context的获取及组件数据初始化

其实我理解的全局Context的理解就是->先创建一个所有module都引用的一个module(common这个module是每个module都会引用的一个module,这里要处理重复的问题,后面补充),这个module就存放一些都能用到的东西,然后引入到module或者主项目中,这样所有的module都会包含一些公共的内容:如请求网络,图片处理等一些相应的内容,这样既保证了每个module的单独运行,也保证了项目的耦合程度

这里有一个存在一个技巧,在前面没有去理解,就是在组件之间AndroidManifest合并问题处有这样一段代码

 java {/*集成开发模式下排除debug文件加中的所有java文件*/
                    exclude 'debug/**'
                }

这段代码的主要问题就是解决全局Context的问题,为什么呢???

我们想一下场景啊!当你某一个模块单独运行的时候,存在登陆获取token的问题,但是你要是单独运行的时候呢,可能这个模块没有token你怎么办呢?其实解决办法就是创建一个debug文件夹,这里呢可以自己创建一个相应的Application去请求登陆的接口这样你的module单独运行的时候就会有token了具体值
(这里我看张华洋的博客时我觉的处理挺好的,他在公共的类中去创建了一个BaseApplication所有module中的Application(在debug文件夹下创建的,但是要在清单文件中去配置啊!!!)都去继承这个Application,这样既解决了没有Application的情况,又解决了集成打包时候多次创建Application的情况,觉得比较不错所以借鉴一下)
上面那行代码会在集成开发模式的情况下把debug文件夹下的内容删除,这里就成功的解决了单独运行的token问题,也解决了集成打包的时候会出现多个Application的问题

library依赖问题

解决library的重复引用问题又两种方式:

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {
            exclude module: 'support-v4'//根据组件名排除
            exclude group: 'android.support.v4'//根据包名排除
        }
    }

这里说明一点:基础的控件一般都在common中去引用,然后住项目和其他的项目都导入common就可以了,但是这里应该存在一个问题就是Common存在导入多次的问题
其实在构建APP的过程中Gradle会自动将重复的arr包排除,APP中也就不会存在相同的代码了;

组件之间的调用和通信

这里是引用张华洋的说明:博客地址

在组件化开发的时候,组件之间是没有依赖关系,我们不能在使用显示调用来跳转页面了,因为我们组件化的目的之一就是解决模块间的强依赖问题,
假如现在要从A业务组件跳转到业务B组件,并且要携带参数跳转,这时候怎么办呢?
而且组件这么多怎么管理也是个问题,这时候就需要引入“路由”的概念了,由本文开始的组件化模型下的业务关系图可知路由就是起到一个转发的作用。
这里我将介绍开源库的“ActivityRouter” ,有兴趣的同学情直接去ActivityRouter的Github主页学习:ActivityRouter,ActivityRouter支持给Activity定义 URL,
这样就可以通过 URL 跳转到Activity,并且支持从浏览器以及 APP 中跳入我们的Activity,而且还支持通过 url 调用方法。
下面将介绍如何将ActivityRouter集成到组件化项目中以实现组件之间的调用;

这里涉及到路由的问题后续在进行补充

组件之间资源名称冲突的问题

这里是引用张华洋的说明:博客地址

因为我们拆分出了很多业务组件和功能组件,在把这些组件合并到“app壳工程”时候就有可能会出现资源名冲突问题,
例如A组件和B组件都有一张叫做“ic_back”的图标,这时候在集成模式下打包APP就会编译出错,解决这个问题最简单的办法就是在项目中约定资源文件命名规约,
比如强制使每个资源文件的名称以组件名开始,这个可以根据实际情况和开发人员制定规则。当然了万能的Gradle构建工具也提供了解决方法,
通过在在组件的build.gradle中添加如下的代码:

```
//设置了resourcePrefix值后,所有的资源名必须以指定的字符串做前缀,否则会报错。
//但是resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。
resourcePrefix "girls_"
```

但是设置了这个属性后有个问题,所有的资源名必须以指定的字符串做前缀,否则会报错,
而且resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名;
所以我并不推荐使用这种方法来解决资源名冲突。

2.组件化项目的工程类型

在组件化工程模型中主要有:app壳工程、业务组件和功能组件3种类型,而业务组件中的Main组件和功能组件中的Common组件比较特殊,下面将分别介绍。

1.app壳工程

app壳工程相当于一个空壳的项目,没有任何业务代码,也不能又Activity,但是它又必须单独划分一个组件,而且不能融合其他组件中;
因为它又几点重要的功能:

这里是引用张华洋的说明:博客地址

一份工程的build.gradle文件

apply plugin: 'com.android.application'

static def buildTime() {
    return new Date().format("yyyyMMdd");
}

android {
    signingConfigs {
        release {
            keyAlias 'guiying712'
            keyPassword 'guiying712'
            storeFile file('/mykey.jks')
            storePassword 'guiying712'
        }
    }

    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion rootProject.ext.buildToolsVersion
    defaultConfig {
        applicationId "com.guiying.androidmodulepattern"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName
        multiDexEnabled false
        //打包时间
        resValue "string", "build_time", buildTime()
    }

    buildTypes {
        release {
            //更改AndroidManifest.xml中预先定义好占位符信息
            //manifestPlaceholders = [app_icon: "@drawable/icon"]
            // 不显示Log
            buildConfigField "boolean", "LEO_DEBUG", "false"
            //是否zip对齐
            zipAlignEnabled true
            // 缩减resource文件
            shrinkResources true
            //Proguard
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            //签名
            signingConfig signingConfigs.release
        }

        debug {
            //给applicationId添加后缀“.debug”
            applicationIdSuffix ".debug"
            //manifestPlaceholders = [app_icon: "@drawable/launch_beta"]
            buildConfigField "boolean", "LOG_DEBUG", "true"
            zipAlignEnabled false
            shrinkResources false
            minifyEnabled false
            debuggable true
        }
    }


}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
    if (isModule.toBoolean()) {
        compile project(':lib_common')
    } else {
        compile project(':module_main')
        compile project(':module_girls')
        compile project(':module_news')
    }
}

2.功能组件和Common组件

功能组件是为了支撑业务组件的某些功能而独立划分出来的组件,功能组件和第三方库是一样的,
拥有如下特征:

功能组件

这里是引用张华洋的说明:博客地址

一份 普通 的功能组件的 build.gradle文件:

apply plugin: 'com.android.library'

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
    }

}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
}
Common组件

Common组件除了有功能组件的普遍属性外,还具有其他功能:

这里是引用张华洋的说明:博客地址

一份 Common 的功能组件的 build.gradle文件:

apply plugin: 'com.android.library'

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
    }

}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    //Android Support
    compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
    compile "com.android.support:design:$rootProject.supportLibraryVersion"
    compile "com.android.support:percent:$rootProject.supportLibraryVersion"
    //网络请求相关
    compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
    compile "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"
    compile "com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"
    //稳定的
    compile "com.github.bumptech.glide:glide:$rootProject.glideVersion"
    compile "com.orhanobut:logger:$rootProject.loggerVersion"
    compile "org.greenrobot:eventbus:$rootProject.eventbusVersion"
    compile "com.google.code.gson:gson:$rootProject.gsonVersion"
    compile "com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"

    compile "com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"
    compile "com.github.GrenderG:Toasty:$rootProject.toastyVersion"

    //router
    compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
}

3.业务组件和Main组件

业务组件

业务组件就是根据业务逻辑的不同拆分出来的组件,业务组件的特征如下:

这里是引用张华洋的说明:博客地址

一份普通业务组件的 build.gradle文件:

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

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
    }

    sourceSets {
        main {
            if (isModule.toBoolean()) {
                manifest.srcFile 'src/main/module/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                //集成开发模式下排除debug文件夹中的所有Java文件
                java {
                    exclude 'debug/**'
                }
            }
        }
    }

    //设置了resourcePrefix值后,所有的资源名必须以指定的字符串做前缀,否则会报错。
    //但是resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。
    //resourcePrefix "girls_"


}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
    compile project(':lib_common')
}
Main组件

Main组件集成模式下的AndroidManifest.xml是跟其他业务组件不一样的,Main组件的表单中声明了我们整个Android应用的launch Activity,这就是Main组件的独特之处;所以我建议SplashActivity、登陆Activity以及主界面都应属于Main组件,也就是说Android应用启动后要调用的页面应置于Main组件。

这里是引用张华洋的说明:博客地址

一份Main业务组件的 build.gradle文件:

        <activity
            android:name=".splash.SplashActivity"
            android:launchMode="singleTop"
            android:screenOrientation="portrait"
            android:theme="@style/SplashTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

3.组件化项目的混淆方案

组件化项目的Java代码混淆方案采用在集成模式下集中在app壳工程中混淆,各个业务组件不配置混淆文件。 集成开发模式下在app壳工程中build.gradle文件的release构建类型中开启混淆属性,其他buildTypes配置方案跟普通项目保持一致,Java混淆配置文件也放置在app壳工程中,各个业务组件的混淆配置规则都应该在app壳工程中的混淆配置文件中添加和修改。
之所以不采用在每个业务组件中开启混淆的方案,是因为 组件在集成模式下都被 Gradle 构建成了 release 类型的arr包,一旦业务组件的代码被混淆,而这时候代码中又出现了bug,将很难根据日志找出导致bug的原因;另外每个业务组件中都保留一份混淆配置文件非常不便于修改和管理,这也是不推荐在业务组件的 build.gradle 文件中配置 buildTypes (构建类型)的原因。

4.工程的build.gradle和gradle.properties文件

组件化工程的build.gradle文件

在组件化项目中因为每个组件的 build.gradle 都需要配置 compileSdkVersion、buildToolsVersion和defaultConfig 等的版本号,而且每个组件都需要用到 annotationProcessor,为了能够使组件化项目中的所有组件的 build.gradle 中的这些配置都能保持统一,并且也是为了方便修改版本号,我们统一在Android工程根目录下的build.gradle中定义这些版本号,当然为了方便管理Common组件中的第三方开源库的版本号,最好也在这里定义这些开源库的版本号,然后在各个组件的build.gradle中引用Android工程根目录下的build.gradle定义的版本号,组件化工程的 build.gradle 文件代码如下:

这里是引用张华洋的说明:博客地址

buildscript {
    repositories {
        jcenter()
        mavenCentral()
    }

    dependencies {
        //classpath "com.android.tools.build:gradle:$localGradlePluginVersion"
        //$localGradlePluginVersion是gradle.properties中的数据
        classpath "com.android.tools.build:gradle:$localGradlePluginVersion"
    }
}

allprojects {
    repositories {
        jcenter()
        mavenCentral()
        //Add the JitPack repository
        maven { url "https://jitpack.io" }
        //支持arr包
        flatDir {
            dirs 'libs'
        }
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

// Define versions in a single place
//时间:2017.2.13;每次修改版本号都要添加修改时间
ext {
    // Sdk and tools
    //localBuildToolsVersion是gradle.properties中的数据
    buildToolsVersion = localBuildToolsVersion
    compileSdkVersion = 25
    minSdkVersion = 16
    targetSdkVersion = 25
    versionCode = 1
    versionName = "1.0"
    javaVersion = JavaVersion.VERSION_1_8

    // App dependencies version
    supportLibraryVersion = "25.3.1"
    retrofitVersion = "2.1.0"
    glideVersion = "3.7.0"
    loggerVersion = "1.15"
    eventbusVersion = "3.0.0"
    gsonVersion = "2.8.0"
    photoViewVersion = "2.0.0"

    //需检查升级版本
    annotationProcessor = "1.1.7"
    routerVersion = "1.2.2"
    easyRecyclerVersion = "4.4.0"
    cookieVersion = "v1.0.1"
    toastyVersion = "1.1.3"
}

组件化工程的gradle.properties文件

在组件化实施流程中我们了解到gradle.properties有两个属性对我们非常有用:

利用gradle.properties的属性不仅可以解决集成开发模式和组件开发模式的转换,而且还可以解决在多人协同开发Android项目的时候,因为开发团队成员的Android开发环境(开发环境指Android SDK和AndroidStudio)不一致而导致频繁改变线上项目的build.gradle配置。

在每个Android组件的 build.gradle 中有一个属性:buildToolsVersion,表示构建工具的版本号,这个属性值对应 AndroidSDK 中的 Android SDK Build-tools,正常情况下 build.gradle 中的 buildToolsVersion 跟你电脑中 Android SDK Build-tools 的最新版本是一致的,比如现在 Android SDK Build-tools 的最新的版本是:25.0.3,那么我的Android项目中 build.gradle 中的 buildToolsVersion 版本号也是 25.0.3,但是一旦一个Android项目是由好几个人同时开发,总会出现每个人的开发环境 Android SDK Build-tools 是都是不一样的,并不是所有人都会经常升级更新 Android SDK,而且代码是保存到线上环境的(例如使用 SVN/Git 等工具),某个开发人员提交代码后线上Android项目中 build.gradle 中的 buildToolsVersion 也会被不断地改变。

另外一个原因是因为Android工程的根目录下的 build.gradle 声明了 Android Gradle 构建工具,而这个工具也是有版本号的,而且 Gradle Build Tools 的版本号跟 AndroidStudio 版本号一致的,但是有些开发人员基本很久都不会升级自己的 AndroidStudio 版本,导致团队中每个开发人员的 Gradle Build Tools 的版本号也不一致。

如果每次同步代码后这两个工具的版本号被改变了,开发人员可以自己手动改回来,并且不要把改动工具版本号的代码提交到线上环境,这样还可以勉强继续开发;但是很多公司都会使用持续集成工具(例如Jenkins)用于持续的软件版本发布,而Android出包是需要 Android SDK Build-tools 和 Gradle Build Tools 配合的,一旦提交到线上的版本跟持续集成工具所依赖的Android环境构建工具版本号不一致就会导致Android打包失败。

为了解决上面问题就必须将Android项目中 build.gradle 中的 buildToolsVersion 和 GradleBuildTools 版本号从线上代码隔离出来,保证线上代码的 buildToolsVersion 和 Gradle Build Tools 版本号不会被人为改变。

具体的实施流程大家可以查看这篇博文: AndroidStudio本地化配置gradle的buildToolsVersion和gradleBuildTools

上面这些要感谢章华洋的博客,基本是按照他的写法自己写了一遍,体会了一下,里面的只是还是很多了,仅作备份,如果楼主有意见,马上删除!!!

上一篇下一篇

猜你喜欢

热点阅读