Android组件化架构(三)

在讲到组件化优化之前,先从一些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就会覆盖远程仓库的版本,导致冲突。当然两种方法各有各的优缺点,需要结合实际项目需求,这两个方法的优缺点值得再开一遍文章来写。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • Android组件化项目地址:Android组件化项目AndroidModulePattern Android组件...
    半灬边灬天阅读 2,865评论 4 37
  • 10个细节,助你养好血管 血管老,人就老。人到中年,如果发现自己已面露老相,身体逐渐走下坡路,就有可能是血管提前老...
    fae015cd3105阅读 271评论 0 0
  • 我还记得那天阿杰叫我去酒吧 他不开心失恋了 。 我就赶了过去 笑着问他 杰哥难得啊 你不是夜店小王子吗 动肾不动心...
    Nittyi阅读 480评论 0 0
  • 《我的外国同事 》 五月份 一对小燕子搬来 住我宿舍斜对门 我们多了两位穿燕尾服的外国同事 自从他俩来后 我们单位...
    梅花知己阅读 87评论 0 0