Android组件化架构(三)

0.412字数 1929阅读 1663

在讲到组件化优化之前,先从一些gradle知识点讲起。

通常一个Application Module的gradle.build如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.sun.test"
        minSdkVersion 19
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    testImplementation 'org.mockito:mockito-core:2.18.3'
    testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
}

第一行apply plugin: 'com.android.application'引入了构建需要用到的Gradle插件工具库。相当于Java中的import。每一个gradle文件都相当于一个Project对象,project.apply()会加载对应的工具库。
android{}和dependencies{}是函数式方程,是闭包函数。和apply一样,可以写成project.android{}和project.dependencies{}。
相对于build.gradle 还有一个settings.gradle,里面内容如下:
include ':app','login','setting'
每当新添加一个Module时,Gradle都会自动添加相应的文件路径到include函数中。声明文件夹,并以一个模块的形式存在。

1.版本参数优化

每个Module的build.gradle都有一些必要的文件,同一个Android工程在不同的模块中会要求这些属性保持一致,例如compileSDK等等。如果引用不一致,除了可能导致潜在的问题外,还会增加APP体积,降低编译效率。如何做到统一的配置的,在每个项目中改是不现实的。
第一种方法是提供一个统一的gradle配置config.gradle。
一、在根目录创建一个gradle文件,内容如下:

ext {
            compileSdkVersion            : 27,
            minSdkVersion                : 19,
            targetSdkVersion             : 27,
            supportVersion               : '27.1.1',
}

二、在Module的首行添加该gradle文件
apply from:"${rootProject.rootDir}/config.gradle"
并在Module的gradle配置如下:

android {
    compileSdkVersion ext.compileSdkVersion

    defaultConfig {
        minSdkVersion ext.minSdkVersion
        targetSdkVersion ext.targetSdkVersion
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

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

}

这样只要修改根目录下的config.gradle文件就能保证各个Module的版本一致。当然这个方法也可以用于一些第三方库。

第二种方法是使用Android对象配置
在config.gradle中添加如下代码:

setDefaultConfig = {
        extension -> //闭包参数extension相当于android对象
        extension.compileSdkVersion compileSdkVersion
        extension.buildToolsVersion buildToolsVersion
        extension.defaultConfig{
                minSdkVersion minSdkVersion
                targetSdkVersion targetSdkVersion
        }
}

然后在build.gradle中就可以使用setDefaultConfig了,在每个Module的gradle中:

android {
        project ext.setDefaultConfig android //调动配置
}

就能使用统一的配置。

第三种方法是使用project对象配置
这种方法和第二种很像,不过进一步的把代码抽离到config.gradle中。代码如下:

//设置APP配置
setAppDefaultConfig = {
        extension->
        //引用Application插件库
        extension.apply plugin:`com.andorid.application`
        extension.description "app"
        setAndroidConfig extension.android
        setDependencies extension.dependencies
}

//设置Lib配置
setLibDefaultConfig = {
        extension -> 
        //引用lib配置
        extension.apply plugin: ’com.andorid.library‘
        extension.description "lib"
         setAndroidConfig extension.android
        setDependencies extension.dependencies
}

//设置Android配置
setAndroidConfig = {
        extension ->
                extension.compileSdkVersion 25
               extension.buildToolsVersion "25.0.2"
               extension.defaultConfig{
                        minSdkVersion 14
                        targetSdkVersion 25
                        ...
                }
}

//设置依赖
setDependencies = {
        extension -> 
              extension.compile fileTree(dir:'libs',include:['*.jar'])
              extension.annotationProcessor 'com.alibaba:arouter-compiler:1.1.1'
}

在每个build.gradle中只需添加project对象到闭包函数即可。

apply from:"${rootProject.rootDir}/config.gradle"
project.ext.setAppDefaultConfig project //如果是lib则是setLibDefaultConfig

这样就可以进一步抽离代码,改动config.gradle 即可。

2.调试优化

组件化一大利器就是可以将一个模块做成APP启动,然后单独调试。这样可以保证模块之间分离,提高开发效率。步骤如下:
1)独立模块调试需要将Library Module改成Application Module。即:
com.android.library -> com.android.application

2)每个Application都要配置ApplicationId,最好不相同,这样就可以不冲突的安装多个模块。在config.gradle中添加函数配置:

setAppDefaultConfig = {
        extension->
        //引用Application插件库
        extension.apply plugin:`com.andorid.application`
        extension.description "app"
        setAndroidConfig extension.android
        setDependencies extension.dependencies
        //为每个Module配置不同的Application ID
        extension.android.defaultConfig{
                applicationId applicationId+"."+extension.getName()
        }
}

这里的extension.getName()相当于project.getName(),在默认的ApplicationId后面添加Module名字,用于区分不同的Module产生的APP。

3)在src中建立app文件夹


image.png

这里用于放置调试状态需要的AndroidManifest文件和其他在作为Module和Application不同状态下不同的文件。例如在正常状态下MainActivity是这样配置的:

        <activity android:name=".MainActivity">
        </activity>

但是app下,这个Activity需要作为这个Module的MainActivity启动,那么就可以在app文件夹下新建一个AndroidManifest文件。并添加启动Activity的Intent-filter:

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

4)声明需要单独调试的模块。
如果每次需要单独调试一个某块都需要在里面改来改去,那么不但繁琐,而且容易出错。更好的方法是在config.gradle 中声明一个Module是否需要单独调试的变量。例如添加一个isLoginDebug

project.ext {
            isLoginDebug  = false
}

在模块的build.gradle 中将变量isLoginDebug作为单独应用和lib的开关:

if(project.ext.isLoginDebug){
        project.ext.setLibDefaultConfig project //当前为lib
} else{
        project.ext.setAppDefaultConfig project //当前为app
}

在sourceSets中配置不同的资源路径和Manifest路径:

sourceSets{
        main{
                if(project.ext.isLoginDebug){
                        manifest.srcFile `src/app/AndoridManifest.xml`
                        res.srcDirs =['src/app/res','src/main/res']
                } else {
                        manifest.srcFile `src/main/AndoridManifest.xml`
                         resources {
                                  //排除debug下所有文件
                                  exclude `src/app/*`
                          }
                }
        }
}

这样就可以在编译构建的时候,根据isLoginDebug来判断选择相应的配置文件和资源。

5)原APP Module需要移除已经单独调试的模块依赖。

dependencies {
        if(!project.ext.isLoginDebug){
                compile project(':login")
        }
}

为什么需要设置移除成为Application的Module呢?这是因为Application只能依赖Library Module,不能依赖 Application Module。所以如果不改,编译无法通过。

3.Gradle 4.1+ 的依赖特性

Gradle升级到4.1以后,原先的compile方式改成了implementation方式。有什么差异呢?implementation不能传递依赖。换句话说就是如果一个Module被以implementation的方式作为一个lib,那么这个lib的主Application是不能访问这个Module的依赖库的。这么说有点抽象。看下下面的架构图:


架构图

使用implementation 的方式,APP层是无法直接调用BaseModule中的类的。这样做有什么好处?当BaseModule中改动的时候,APP层不需要重新编译,只需要编译Base层即可,提高了编译效率。当然实际使用中如果确实需要访问baseModule的依赖库,就需要把implementation改成api,这样的效果就和compile相同。所以BaseModule中的第三方库建议使用api的方式依赖第三方库,虽然这样牺牲了一定的编译效率,但是这样可以减少调用封装的代码,加快开发。

4.Git组件化部署

一、submodule子模块

  • 添加子模块到项目中可以使用:
    git submodule add module的git地址
    然后在setting.gradle中配置Library Module即可。当主工程需要更新最新代码的时候,可使用如下命令:
    git submodule update
    由于存在缓存的原因,所以不一定能够成功。可以使用git submodule update -remote从远程仓库获取全部最新代码。

  • 工程同步
    如果使用了上面配置的submodule方式,首次从主项目clone代码的时候,子工程只有文件夹模块,文件夹中没有任何代码。这时需要git submodule update - init -recursive来拉取各项子工程代码。如果这时出现“Plugins Suggestion Unknown features (Run configuration [AndroidRunConfigurationType],Facet[android,android-gradle]) covered by disable plugin detected. Enable plugins ... Ignore Unknown Features”。这是因为Android Support没有被勾选导致的,点击File->Setting->Plugins,勾选Android Support,点击 Apply,然后重启。
    如果想要删除一个子模块,可以执行以下命令:

git rm -r -cached 子模块名称
rm -rf .git/modules/子模块名称

二、subtree

  • 子模块管理也可以使用subtree的方法。
    通过git subtree add -prefix=<sub项目相对路径> <sub项目git地址><分支> --squash关联主项目。一般 -prefix= 后面直接用空格代替,表示直接关联到根目录下。如果需要关联多个子工程,关联下一个子工程前先要使用git commit 命令来提交代码。

  • 更新/提交子模块代码
    通过git subtree push -prefix=<sub项目相对路径> <sub项目git地址><分支>可以提交改动到远程仓库。
    git subtree pull -prefix=<sub项目相对路径> <sub项目git地址><分支>从远程仓库更新代码到本地。

至于到底是用submodule还是subtree,目前来看主流的意见是subtree。submodule使用起来相对繁琐些,而且如果每次提交都需要update,如果没有update就直接commit就会覆盖远程仓库的版本,导致冲突。当然两种方法各有各的优缺点,需要结合实际项目需求,这两个方法的优缺点值得再开一遍文章来写。

好了,到这里我们就大致完成了组件化架构一些基本的开发和管理介绍。

推荐阅读更多精彩内容