Android Gradle 模塊化構(gòu)建打包

原文鏈接Android -Gradle

前言

本文將簡述Android-Gradle在實際項目開發(fā)過程中-打包編譯過程中-所涉及到的部分知識點逻锐。
對大部分Android開發(fā)者而言,接觸Gradle應(yīng)該是從AndroidStudio開始雕薪,而AndroidStudio是google官方推薦的開發(fā)IDE,可見深入了解Gradle會對我們的Android軟件開發(fā)產(chǎn)生更深遠的意義昧诱。
Gradle其實是一種構(gòu)建工具,能自動化的進行構(gòu)建所袁,編譯盏档,打包,簽名等一系列流程燥爷。

android-gradle

構(gòu)建

構(gòu)建工具用于實現(xiàn)項目自動化蜈亩,是一種可編程的工具,你可以用代碼來控制構(gòu)建流程最終生成可交付的軟件前翎。構(gòu)建工具可以幫助你創(chuàng)建一個重復(fù)的稚配、可靠的、無需手動介入的港华、不依賴于特定操作系統(tǒng)和IDE的構(gòu)建道川。Android開發(fā)中常見構(gòu)建工具有ant,maven,make,gradle等等。

ant

Ant 是由 Java 編寫的構(gòu)建工具,具有平臺無關(guān)性冒萄,構(gòu)建腳本是XML格式的(默認為bulid.xml)臊岸。Ant的構(gòu)建腳本由三個基本元素組成:一個project(工程)、多個target(目標)和可用的task(任務(wù))尊流。
Apache Ant有以下缺點:

  1. Ant無法獲取運行時的信息帅戒。
  2. XML作為構(gòu)建腳本的語言,如果構(gòu)建邏輯復(fù)雜崖技,那么構(gòu)建腳本就會又長又難以維護蜘澜。
  3. Ant管理依賴需要配合Ivy。
  4. Ant腳本編寫雖然具有靈活性响疚,但不易于結(jié)構(gòu)化理解。

maven

Maven于2004年發(fā)布瞪醋,它的目標是改進開發(fā)人員在使用Ant時面臨的一些問題忿晕。 繼承了Ant的項目構(gòu)建功能, 同樣采用了XML作為構(gòu)建腳本的格式(默認為pom.xml)银受。Maven具有依賴管理和項目管理的功能践盼,提供了中央倉庫,能幫助我們自動下載庫文件宾巍。

Maven相比Ant的優(yōu)點:

  1. Ant是過程式的咕幻,開發(fā)者需要顯示的指定每個目標,以及完成該目標鎖需要執(zhí)行的任務(wù)顶霞。每一個項目肄程,開發(fā)著都需要重新編寫這一過程,這樣會產(chǎn)生大量的重復(fù)选浑。Maven是聲明式的蓝厌,項目的構(gòu)建過程和過程中的各個階段都由插件實現(xiàn),開發(fā)者只需要聲明項目的基本元素就可以了古徒,這很大程度消除了重復(fù)拓提。
  2. Ant需要配合Ivy來管理依賴,而Maven本身就提供了依賴管理隧膘。
  3. Maven 使用約定而不是配置代态,它為工程提供了合理的默認行為,項目會知道去哪個目錄尋找源代碼以及構(gòu)建運行時有那些任務(wù)去執(zhí)行疹吃。而Ant是使用配置且沒有默認行為的蹦疑。

Maven的缺點:

  1. Maven的提供了一套默認的結(jié)構(gòu)和生命周期,對具體的項目工程有些可能會有不適應(yīng)萨驶。
  2. Maven的定制擴展過于繁瑣不易于理解必尼。
  3. 國內(nèi)連接Maven的中央倉庫比較慢,一般需要連接國內(nèi)的Maven鏡像倉庫,目前有阿里云倉儲等可做備選判莉。

make

Make編譯構(gòu)建豆挽,在Android的源碼編譯中被大量使用,其采用Makefile作為構(gòu)建腳本的格式語言(默認為Android.mk)券盅,執(zhí)行對應(yīng)的命令帮哈,然后得到目標產(chǎn)物。除了make命令外锰镀,Android源碼編譯中還有mm,mmm等娘侍。

  1. make:不帶任何參數(shù),用于編譯整個系統(tǒng)泳炉,編譯時間比較長憾筏,除非是進行初次編譯否則不建議此種做法
  2. mmm 該命令編譯指定目錄下的目標模塊,而不編譯它所依賴其他模塊花鹅。非首次編譯可能會依賴報錯
  3. mm 同mmm 命令一樣也是不編譯依賴氧腰,只是該命令需先cd到編譯目錄,編譯當前目錄。
  4. mma 也是編譯當前目錄下的模塊刨肃,但會編譯其依賴項

默認編譯一般都是增量變化式編譯古拴,若需重新編譯 以上命令都可以用-B選項來實現(xiàn)。

在這順便提下源碼編譯的流程

  1. source build/envsetup.sh #這個腳本用來設(shè)置android的編譯環(huán)境;
  2. lunch #選擇編譯目標
  3. make #編譯android整個系統(tǒng)

make編譯優(yōu)缺點:

  1. 方便管理編譯依賴大型項目真友。
  2. make編譯需依賴linux等一系列編譯工具黄痪,跨平臺搭建會比較復(fù)雜。
  3. 對于單個Android應(yīng)用項目獨立編譯盔然,需要有源碼編譯環(huán)境桅打,不易于調(diào)試。

Gradle

gradle結(jié)合Ant和Maven等構(gòu)建工具的最佳特性愈案。它有著約定優(yōu)于配置的方法油额、強大的依賴管理,它的構(gòu)建腳本使用Groovy或Kotlin 編寫刻帚,是Android的官方構(gòu)建工具潦嘶。gradle腳本為build.gradle格式。

Groovy

Groovy基于DSL(動態(tài)語言)崇众。和Java一樣掂僵,也運行于Java虛擬機中。這一特性也使得Groovy可以引用Java顷歌,但除此之外Groovy又具有腳本語言的特定锰蓬。當執(zhí)行Groovy腳本時,Groovy會先將其編譯成Java類字節(jié)碼眯漩,然后通過Jvm來執(zhí)行這個Java類芹扭。相關(guān)關(guān)系模型如下圖

groovy-jvm

Groovy-基本語言

作為動態(tài)語言麻顶,Groovy世界中的所有事物都是對象。所以舱卡,int辅肾,boolean這些Java中的基本數(shù)據(jù)類型,在Groovy代碼中其實對應(yīng)的是它們的包裝數(shù)據(jù)類型轮锥。比如int對應(yīng)為Integer矫钓,boolean對應(yīng)為Boolean等。

Groovy-集合

  • List:鏈表舍杜,其底層對應(yīng)Java中的List接口新娜,一般用ArrayList作為真正的實現(xiàn)類。

    //List由[]定義既绩,其元素可以是任何對象
    //變量存雀帕洹:可以直接通過索引存取,而且不用擔心索引越界饲握。
    //如果索引超過當前鏈表長度私杜,List會自動往該索引添加元素
    def arryList = [2,'string',true] 
    assert arryList[1] == 'string'
    assert arryList[5] == null //第6個元素為空
    aList[100] = 100 //設(shè)置第101個元素的值為10
    assert arryList[100] == 100
    println arryList.size  ===>結(jié)果是101
    
  • Map:鍵-值表,其底層對應(yīng)Java中的LinkedHashMap互拾。

    //容器變量定義
    //變量定義:Map變量由[:]定義,比如
    def aMap = ['key1':'value1','key2':true]
    //Map由[:]定義嚎幸,注意其中的冒號颜矿。冒號左邊是key,右邊是Value嫉晶。key必須是字符串骑疆,value可以是任何對象。另外替废,key可以用''或""包起來箍铭,也可以不用引號包起來。比如
    def aNewMap = [key1:"value",key2:true]
    
  • Range:范圍椎镣,它其實是List的一種拓展诈火。

    //Range類型的變量 由begin值+兩個點+end值表示
    //左邊這個aRange包含1,2,3,4,5這5個值
    def aRange = 1..5 
    //如果不想包含最后一個元素,包含1,2,3,4這4個元素
    def aRangeWithoutEnd = 1..<5  
    println aRange.from
    println aRange.to
    

Groovy-閉包

閉包状答,英文叫Closure冷守,是Groovy中非常重要的一個數(shù)據(jù)類型或者說一種概念。

def closure = {//閉包是一段代碼惊科,所以需要用花括號括起來..
    Stringparam1, int param2 ->  //這個箭頭很關(guān)鍵拍摇。箭頭前面是參數(shù)定義,箭頭后面是代碼
    println"this is code" //這是代碼馆截,最后一句是返回值充活,
   //也可以使用return蜂莉,和Groovy中普通函數(shù)一樣
}

簡而言之,Closure的定義格式是:

def xxx = {paramters -> code} //或者 def xxx = {無參數(shù)混卵,純code} 這種case不需要->符號

Closure使用中的注意點

  1. 省略圓括號
  2. 確定Closure的參數(shù)

更詳細的接受可以參考文末的參考文檔鏈接映穗。

Android-Gradle

好了有了以上相關(guān)構(gòu)建的基礎(chǔ)知識,現(xiàn)在讓我們走進今天的主角AS-Android-Gradle
在學(xué)習Android-Gradle編譯流程前淮菠,有必要先梳理下APP編譯打包的具體流程男公。

app-build編譯打包流程

android-compile

APK構(gòu)建過程如上圖總結(jié)如下:

  1. 通過AAPT(Android Asset Packaging Tool)打包res資源文件,比如AndroidManifest.xml合陵、xml布局文件等枢赔,并將這些xml文件編譯為二進制,其中assets和raw文件夾的文件不會被編譯為二進制拥知,最終會生成R.java和resources.arsc文件踏拜。
  2. AIDL工具會將所有的aidl接口轉(zhuǎn)化為對應(yīng)的Java接口。
  3. 所有的Java代碼低剔,包括R.java和Java接口都會被Java編譯器編譯成.class文件速梗。
  4. Dex工具會將上一步生成的.class文件、第三庫和其他.class文件編譯成.dex文件襟齿。
  5. 上一步編譯生成的.dex文件姻锁、編譯過的資源、無需編譯的資源(如圖片等)會被ApkBuilder工具打包成APK文件猜欺。
  6. 使用Debug Keystore或者Release Keystore對上一步生成的APK文件進行簽名位隶。
  7. 如果是對APK正式簽名,還需要使用zipalign工具對APK進行對齊操作开皿,這樣應(yīng)用運行時會減少內(nèi)存的開銷涧黄。

gradle打包流程

[圖片上傳失敗...(image-e20fea-1577691225411)]

  1. 首先是初始化階段。執(zhí)行as項目根目錄下的settings.gradle
  2. Initiliazation phase的下一個階段是Configration階段赋荆。
  3. Configration階段的目標是解析每個project中的build.gradle笋妥。解析每個子 模塊中的build.gradle。在這兩個階段之間窄潭,我們可以加一些定制化的Hook春宣。這當然是通過API來添加的。
  4. Configuration階段完了后嫉你,整個build的project以及內(nèi)部的Task關(guān)系就確定了信认。當然,我們也可以添加一個HOOK均抽,即當Task關(guān)系圖建立好后嫁赏,執(zhí)行一些操作。
  5. 最后一個階段就是執(zhí)行任務(wù)了油挥。當然潦蝇,任務(wù)執(zhí)行完后款熬,我們還是可以加Hook。

project-setting.gradle 項目配置

gradle項目初始化工作攘乒,一切從這里開始贤牛。創(chuàng)建modlue時,as會默認在setting.gradle中include相關(guān)模塊

include ':testlibrary',  ':testapplication', ':testapplication1', ':testlibrary1'
rootProject.name='AndroidGradle'

project-build.gradle 項目初始化

項目下的build.gradle,是一個整體配置则酝,各模塊編譯前后的一些公共Task可在此定義

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

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

再這一模塊殉簸,可以自定義一些task,或修改task

比如新增maven倉儲,或編譯task

...
allprojects {
    repositories {
        //新增阿里云倉儲
        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
        google()
        jcenter()
        
    }
    //增加一些編譯選項
    gradle.projectsEvaluated {
        tasks.withType(JavaCompile) {
            options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
        }
    }
}
...

比如重新修改clean Task,默認的clean的之后刪除根目錄下的buildDir,但對于模塊下的build未刪除

對于正在使用svn進行代碼管理時沽讹,上傳代碼時般卑,如果不刪除build,svn掃描文件會很卡爽雄。

因而重新編寫task后也會遞歸刪除相關(guān)子模塊下的build蝠检。

task clean(type: Delete) {
    println "\n======================================================"
    println "**********  Delete All Compile ********** "
    println "======================================================\n"

    println("**********  start delete ")
    println("delete project dir:" + rootProject.buildDir)
    rootProject.buildDir.deleteDir()

    def file = new File("")
    def dir = new File(file.getAbsolutePath())
    println(" root dir:" +dir.getAbsolutePath())

    dir.eachDirRecurse {
        dir2 ->
            dir2.eachDirMatch(~/build/) {
                directory ->
                        println("delete child dir:"+directory)
                                            directory.deleteDir()
            }
    }
    println("********** finished delete")
}

app-buid.grade 應(yīng)用編譯

應(yīng)用模塊的編譯配置腳本。對比編譯版本挚瘟,編譯工具叹谁,簽名,渠道配置等都可以在此配置乘盖。但對于多模塊配置而言焰檩,經(jīng)常會出現(xiàn)版本號不統(tǒng)一的情況,為了解決這個問題订框,我們可以把版本號定義在project-gradle-ext全局變量中析苫。

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    buildToolsVersion "29.0.2"


    defaultConfig {
        applicationId "top.lairdli.study.testapplication"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

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

}

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

    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

lib-build.gradle 庫模塊編譯

apply plugin: 'com.android.library'

android {
    compileSdkVersion 28
    buildToolsVersion "29.0.2"


    defaultConfig {
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        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'])

    implementation 'androidx.appcompat:appcompat:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

auto-package 自動打包

更改編譯輸出路徑,以及編譯輸出app名稱布蔗,不同的gradle版本藤违,api會有些許差異

Gradle4.10以前配置如下


  //gradle4.10以前
    android.applicationVariants.all { variant ->
        variant.outputs.all { output ->
            if (variant.buildType.name == 'release') {
                outputFileName = "${variant.applicationId}_${variant.buildType.name}_${variant.versionName}.apk"
                if (outputFileName != null && outputFileName.endsWith('.apk')) {
                    variant.getPackageApplication().outputDirectory = new File("$rootProject.projectDir/out/${project.name}/"+
                            "${variant.flavorName}")
                    variant.getPackageApplication().outputScope.apkDatas.forEach {
                        apkData -> apkData.outputFileName = outputFileName
                    }
                }
            }
        }
    }
  

Gradle4.10以后配置如下

    //gradle4.10以后
    android.applicationVariants.all { variant ->
        variant.outputs.each { output ->
            if (variant.buildType.name == 'release') {
                def outputFileName = "${variant.applicationId}." +
                        "${variant.flavorName}.${variant.buildType.name}.${variant.versionName}.apk"
                output.outputFileName = outputFileName
                def outputDir = new File("$rootProject.projectDir/out/${project.name}/" +
                        "${variant.flavorName}")
                variant.packageApplicationProvider.get().outputDirectory = new File("$outputDir")
            }
        }
    }

如果多應(yīng)用工程浪腐,需要統(tǒng)一輸出目錄纵揍,可以編譯完成后copy到統(tǒng)一的目錄下,并記錄版本信息

   
android.applicationVariants.all { variant ->
      //重命名+重定義輸出目錄
    variant.outputs.each { output ->
        if (variant.buildType.name == 'release') {
            def outputFileName = "${variant.applicationId}." +
                    "${variant.buildType.name}.${variant.versionName}.apk"
            output.outputFileName = outputFileName
            def outputDir = new File("$rootProject.projectDir/out/${project.name}/" +
                    "${variant.flavorName}")
            variant.packageApplicationProvider.get().outputDirectory = new File("$outputDir")
        }
    }
        //編譯完成后议街,重新copy
    variant.assemble.doLast {
        variant.outputs.each { output ->
            def outputFile = output.outputFile;

            if (outputFile != null && outputFile.name.endsWith('.apk') && variant.buildType.name == 'release') {
                packageAppRelease(outputFile,variant)
            }
        }
    }
}

//重新copy 并寫入文件
def packageAppRelease(outputFile, variant) {

    def releaseDir = "$rootProject.projectDir/out/release/app/$getDateYYMMDD"
    def newName = variant.applicationId + '.apk'

    copyFile("$outputFile", releaseDir
            , "$outputFile.name", newName)

    StringBuilder stringBuild = new StringBuilder()
    stringBuild.
            append("****************************************************").append('\n')
            .append("*************** https://lairdli.top ***************").append('\n')
            .append("****************************************************").append('\n')
            .append("**:Name:" + outputFile.name).append('\n')
            .append("**:ApplicationId:" + variant.applicationId).append('\n')
            .append("**:VersionCode:" + variant.versionCode).append('\n')
            .append("**:VeresionName:" + variant.versionName).append('\n')
            .append("**:LastModify:" + formatDateYYMMDDHMS(outputFile.lastModified())).append('\n')
            .append("**:Size:" + outputFile.length()).append('\n')
            .append("**:Md5:" + getFileMd5(outputFile)).append('\n')
            .append("**:Sha1:" + getFileSha1(outputFile)).append('\n')
            .append("**:Des:").append('\n')
            .append("****************************************************")
            .append('\n').append('\n').append('\n')

    println stringBuild.toString()

    def releaseModuleFileFullPath =  outputFile.getParent()+ File.separator + variant.applicationId + '.txt';
    def releasePackFileFullPath =releaseDir  +  File.separator + variant.applicationId + '.txt';

    writeFile(releaseModuleFileFullPath, stringBuild.toString(), true)
    writeFile(releasePackFileFullPath, stringBuild.toString(), false)

    return newName;
}

def writeFile(String fileName,String content,boolean appendMode){

    File file = new File(fileName)

    if(!file.exists()){
        file.createNewFile();
    }

    FileOutputStream fos = new FileOutputStream(fileName, appendMode);
    fos.write(content.getBytes("UTF-8"));
    fos.close();
}

寫入配置文件內(nèi)容如下

****************************************************
*************** https://lairdli.top ***************
****************************************************
**:Name:top.lairdli.study.testapplication.release.201912291716.apk
**:ApplicationId:top.lairdli.study.testapplication
**:VersionCode:2019122916
**:VeresionName:201912291716
**:LastModify:2019-12-29 05:16:54
**:Size:1439615
**:Md5:f7ec01d7b6dced6b01fd11bf4c61aae4
**:Sha1:dfd178102f4b8c2501bba6efae7ae11f58861f3a
**:Des:
****************************************************

編譯生成的out目錄結(jié)構(gòu)如下

Laird-MacBook-Pro:AndroidGradle laird$ cd out/
Laird-MacBook-Pro:out laird$ tree
.
├── app_test
│   ├── output.json
│   ├── top.lairdli.study.testapplication1.release.201912291724.apk
│   └── top.lairdli.study.testapplication1.txt
├── app_test1
│   ├── output.json
│   ├── top.lairdli.study.testapplication1.release.201912291728.apk
│   └── top.lairdli.study.testapplication1.txt
└── release
    └── app
        └── 20191229
            ├── top.lairdli.study.testapplication1.apk
            └── top.lairdli.study.testapplication1.txt

5 directories, 8 files
Laird-MacBook-Pro:out laird$

key-store 秘鑰配置

普通簽名keystore生成可以通過keytool生成秘鑰。

系統(tǒng)簽名keystore生成可以通過keytool-importkeypair 生成秘鑰。

由于篇幅有限供嚎,本文只展示普通秘鑰生成嘹黔,更多使用命令行可參考往期博文

https://lairdli.top/2019/08/13/android-command/

To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
Laird-MacBook-Pro:~ laird$ keytool -genkey -alias test -keypass 123456 -keyalg RSA -keysize 2048 -validity 36500 -keystore test.keystore -storepass 123456
您的名字與姓氏是什么?
  [Unknown]:  lairdli.top
您的組織單位名稱是什么?
  [Unknown]:  lairdli.top
您的組織名稱是什么?
  [Unknown]:  lairdli.top
您所在的城市或區(qū)域名稱是什么?
  [Unknown]:  wuhan
您所在的省/市/自治區(qū)名稱是什么?
  [Unknown]:  hubei
該單位的雙字母國家/地區(qū)代碼是什么?
  [Unknown]:  china
CN=lairdli.top, OU=lairdli.top, O=lairdli.top, L=wuhan, ST=hubei, C=china是否正確?
  [否]:  y


Warning:
JKS 密鑰庫使用專用格式。建議使用 "keytool -importkeystore -srckeystore test.keystore -destkeystore test.keystore -deststoretype pkcs12" 遷移到行業(yè)標準格式 PKCS12涂身。
Laird-MacBook-Pro:~ laird$

除此之外AS-Build --->> Generate Signed APK-->Create New也可以創(chuàng)建秘鑰雄卷。

As-app-build.gradle配置

    signingConfigs {
        release {
            keyAlias 'test'
            keyPassword '123456'
            storeFile file("${rootProject.ext.defaultKeyStoreDir}" + '/test.keystore')
            storePassword '123456'
        }

        debug {
            keyAlias 'test'
            keyPassword '123456'
            storeFile file("${rootProject.ext.defaultKeyStoreDir}" + '/test.keystore')
            storePassword '123456'
        }
    }

    buildTypes {

        debug {
            signingConfig signingConfigs.debug
        }

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

當然,如果不希望秘鑰明文被看到蛤售,也可以將秘鑰密匙配置在local.properties本地鍵值對中丁鹉。

#defined kesotre
SIGNINGCONFIGS_KEYALIAS=test
SIGNINGCONFIGS_KEYPASSWORD=123456
SIGNINGCONFIGS_STOREFILE=/config/keystore/test.keystore
SIGNINGCONFIGS_STOREPASSWORD=123456

然后在app-build.gradle中配置

    signingConfigs {
        ...
        test{
            keyAlias SIGNINGCONFIGS_KEYALIAS
            keyPassword SIGNINGCONFIGS_KEYPASSWORD
            storeFile file(SIGNINGCONFIGS_STOREFILE)
            storePassword SIGNINGCONFIGS_STOREPASSWORD
        }
    }

module-config 模塊化配置

之所以提出模塊化編譯妒潭,是為了按我們跟方便,更解耦的進行項目的編譯開發(fā)工作揣钦。一切為了更好的Dev雳灾。

AndroidStudio模塊化配置,可以從以下幾個方面進行配置

  • setting.gradle 模塊化配置冯凹,主要配置
  • build.gradle 模塊化配置
  • utils.gradle 工具類配置

為更方便的管理谎亩,建議將utils.gradle工具類相關(guān)的腳步整理為單獨的文件夾

config
├── app.gradle
├── config.gradle
├── cvs.gradle
├── keystore
│   └── test.keystore
├── lib.gradle
├── libdebug.gradle
├── sh
│   ├── buildAll.sh
│   └── cleanAll.sh
└── util.gradle

setting.gradle 模塊化配置

對于多模塊的Android項目,或者模塊路徑不在同級目錄下的模塊宇姚,自定義setting.gradle匈庭,非常有用。

rootProject.name = 'AndroidGradleTest'

/**
 * u can disable module by adding excludexxx properties in local.properties
 * the full excludexxx like below example:
 *
 exclude_app_test=true
 exclude_app_test1=true
 exclude_lib_test=true
 exclude_lib_test1=true
 * u can copy the example ,and modify in u local.properties
 * focus!!!! local.properties should not be pushed to svn or git server.
 */

println "\n======================================================"
println "**********  Init All Module ********** "
println "**** compile gradle verison:" + gradle.gradleVersion + "  ***** "
println "======================================================\n"
def enableModuleMap = [
        app_test : true,
        app_test1: true,
        lib_test : true,
        lib_test1: true
]
println "**** read enableModuleMap from local.properties"
Properties properties = new Properties()
File propertyFile = new File(rootDir.getAbsolutePath() + "/local.properties")
if (propertyFile.exists()) {
    properties.load(propertyFile.newDataInputStream())
    enableModuleMap.each {
        entry ->
            entry.value = !Boolean.parseBoolean(properties.getProperty('exclude_' + entry.key))
            println "enableModuleMap->module  " + entry.key + " is included : " + entry.value
    }
} else {
    println "**** ${propertyFile.getAbsolutePath()} is not exists! "
}
println "**** finish enableModuleMap from local.properties "


/**
 * -----------------------application modules--------------------------
 */

if (enableModuleMap.app_test) {
    include 'app_test'
    project(':app_test').projectDir = new File('testapplication')
}

if (enableModuleMap.app_test1) {
    include 'app_test'
    project(':app_test').projectDir = new File('testapplication1')
}

/**
 * -----------------------library modules--------------------------
 */
if (enableModuleMap.lib_test) {
    include 'lib_test'
    project(':lib_test').projectDir = new File('testlibrary')
}

if (enableModuleMap.lib_test1) {
    include 'lib_test1'
    project(':lib_test1').projectDir = new File('testlibrary1')
}


當暫時不需要此模塊加入工程編譯時空凸,只需在local.properties中配置要剔除的模塊即可

## This file is automatically generated by Android Studio.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file should *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=/Users/laird/Soft/Android/sdk

#exclude module from project
exclude_app_test=true
#exclude_app_test1=true
exclude_lib_test=true
#exclude_lib_test1=true

build.gradle模塊化配置

Project-build.gradle

項目根目錄下build.gradle配置嚎花,主要配置一些腳本依賴,

// Top-level build file where you can add configuration options common to all sub-projects/modules.

apply from: rootProject.file("config/util.gradle")
apply from: rootProject.file("config/cvs.gradle")
apply from: rootProject.file("config/config.gradle")

  • util.gradle

主要配置一些工具方法類

import java.security.MessageDigest
import java.text.SimpleDateFormat

ext{
    getDateYYMMDD = this.&getDateYYMMDD
    formatDateYYMMDDHMS = this.&formatDateYYMMDDHMS
    getVersionCode = this.&getVersionCode
    getVersionName = this.&getVersionName
    copyFile = this.&copyFile
    getFileSha1 = this.&getFileSha1
    getFileMd5 = this.&getFileMd5
    writeFile = this.&writeFile
}

def getDateYYMMDD(){
    Integer.parseInt(new Date().format("yyyyMMdd"))
}

def formatDateYYMMDDHMS(time){
    Calendar calendar = Calendar.getInstance()
    calendar.setTimeInMillis(time)
    Date date = calendar.getTime()
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    sdf.format(date)
}


def getVersionCode() {
    Integer.parseInt(new Date().format("yyyyMMddmm"))

}

def getVersionName() {

    String today = new Date().format("yyyyMMdd")
    String time =  new Date().format("HHmm")

    if(rootProject.ext.isNeedSvnVersion){
        "$today" + "$time"+".$rootProject.ext.buildSvnNum"
    }else{
        "$today" + "$time"
    }
}

def copyFile(String fromFile, String dstDir,String oldName, String newName){
    copy {
        from fromFile
        into dstDir

        if(oldName!=null && newName!=null){
            rename(oldName, newName)
        }
    }
}

def getFileSha1(file)
{
    MessageDigest md = MessageDigest.getInstance("SHA-1");
    file.eachByte 4096, {bytes, size ->
        md.update(bytes, 0, size);
    }
    return md.digest().collect {String.format "%02x", it}.join();
}

def getFileMd5(file)
{
    MessageDigest md = MessageDigest.getInstance("MD5");
    file.eachByte 4096, {bytes, size ->
        md.update(bytes, 0, size);
    }
    return md.digest().collect {String.format "%02x", it}.join();
}

def writeFile(String fileName,String content,boolean appendMode){

    File file = new File(fileName)

    if(!file.exists()){
        file.createNewFile();
    }

    FileOutputStream fos = new FileOutputStream(fileName, appendMode);
    fos.write(content.getBytes("UTF-8"));
    fos.close();
}
  • cvs.gradle

主要配置一些版本相關(guān)工具類

import org.tmatesoft.svn.core.wc.ISVNOptions
import org.tmatesoft.svn.core.wc.SVNClientManager
import org.tmatesoft.svn.core.wc.SVNRevision
import org.tmatesoft.svn.core.wc.SVNStatus
import org.tmatesoft.svn.core.wc.SVNStatusClient
import org.tmatesoft.svn.core.wc.SVNWCUtil

buildscript {
    repositories {
        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
    }
    dependencies {
        classpath 'org.tmatesoft.svnkit:svnkit:1.10.1'
    }
}

ext{
    buildSvnNum = this.&buildSvnNo
    buildGitNum = this.&buildGitNo
}

def buildSvnNo() {
  
    ISVNOptions options = SVNWCUtil.createDefaultOptions(true);
    SVNClientManager clientManager = SVNClientManager.newInstance(options);
    SVNStatusClient statusClient = clientManager.getStatusClient();
    SVNStatus status = statusClient.doStatus(projectDir, false);
    SVNRevision revision = status.getRevision();
    def svnLog = revision.getNumber();
}


static def buildGitNo() {
    String revisionNumberCMD = 'git rev-parse --short HEAD'
    revisionNumberCMD.execute().getText().trim()
}
  • config.gradle

主要配置一些全局變量呀洲,版本號統(tǒng)一等

ext {
    //true 每個業(yè)務(wù)Module可以單獨開發(fā)
    //false 每個業(yè)務(wù)Module以lib的方式運行
    //修改之后需要Sync方可生效
    isBuildModule = false
    //是否需要代碼混淆
    isNeedMinify = false
    //是否需要打開git版本
    isNeedSvnVersion = false
    //是否需要打開git版本
    isNeedGitVersion = false

    defaultKeyStoreDir = rootProject.file("config/keystore")
        //模塊相關(guān)的屬性
    modules = [
            utilcommon_dir : rootProject.file("../xxx")   ,
            uiservice_dir : rootProject.file("../xxxx")
    ]
        //android編譯相關(guān)的版本號
    androids = [
            applicationId           : "top.lairdli.app",     //應(yīng)用ID
            versionCode             : getVersionCode(),      //版本號
            versionName             : getVersionName(),      //版本名稱
            versionCodeDebug        : 8888888888,      //版本號
            versionNameDebug        : "DebugVersion",      //版本名稱

            compileSdkVersion       : 28,
            minSdkVersion           : 15,
            buildToolsVersion       : "29.0.2",
            targetSdkVersion        : 22,
            androidSupportSdkVersion: "28.0.0",
    ]

    //第三方庫版本號
    versions = [

            xmaterialVersion        : "1.0.0",
            xrunnerVersion          : "1.2.0",
            xrolesVersion           : "1.2.0",
            ...
    ]

     //依賴配置
    dependencies = [
            "x_constraint_layout"   : "androidx.constraintlayout:constraintlayout:${versions["xconstraintLayoutVersion"]}",
            "x_runner"              : "androidx.test:runner:${versions["xrunnerVersion"]}",
            "x_rules"               : "androidx.test:rules:${versions["xrulesVersion"]}",
                ...
}                       

modlue-app-build.gradle

應(yīng)用模塊模塊化紊选,可以將統(tǒng)一的配置抽離整理成app.gradle

  • app.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion rootProject.ext.androids.compileSdkVersion
    buildToolsVersion rootProject.ext.androids.buildToolsVersion
    flavorDimensions "versionCode"

    defaultConfig {
        minSdkVersion rootProject.ext.androids.minSdkVersion
        targetSdkVersion rootProject.ext.androids.targetSdkVersion
        versionCode rootProject.ext.androids.versionCode
        versionName rootProject.ext.androids.versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    
    signingConfigs {
        //篇幅有限,參考as-key-store配置
    }
    
}
    //篇幅有限道逗,參考as-auto-package配置
android.applicationVariants.all { variant ->
        
    //gradle4.10以后自定義版本命令規(guī)則以及生成目錄
    variant.outputs.each { 
        ...
    }
    //自定義版本備份路徑以及版本描述
    variant.assemble.doLast {
     ...
    }
}

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

然后在模塊下build.gradle引用app.gradle

  • build.gradle
//apply plugin: 'com.android.application'
apply from: rootProject.file("config/app.gradle")

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

    implementation rootProject.ext.dependencies["x_appcompat"]
    implementation rootProject.ext.dependencies["x_constraint_layout"]
    testImplementation rootProject.ext.dependencies["junit"]
    androidTestImplementation rootProject.ext.dependencies["runner"]
    androidTestImplementation rootProject.ext.dependencies["espresso-core"]

}

modlue-lib-build.gradle

參考modlue-app-build.gradle配置兵罢,也可以抽離lib.gradle.

  • lib.gradle
apply plugin: 'com.android.library'

android {
    compileSdkVersion rootProject.ext.androids.compileSdkVersion
    buildToolsVersion rootProject.ext.androids.buildToolsVersion

    defaultConfig {
        minSdkVersion rootProject.ext.androids.minSdkVersion
        targetSdkVersion rootProject.ext.androids.targetSdkVersion
        versionCode rootProject.ext.androids.versionCode
        versionName rootProject.ext.androids.versionName

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles 'consumer-rules.pro'
    }

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

android.libraryVariants.all { variant ->
    variant.assemble.doLast {
        variant.outputs.each { output ->
            def outputFile = output.outputFile;

            if (outputFile != null && outputFile.name.endsWith('.aar') && variant.buildType.name == 'release') {
                copyFileToApk(outputFile,variant)
            }
        }
    }
}

def copyFileToApk(outputFile, variant) {

    def newName = variant.applicationId + '.aar';
    def releaseDir = "$rootProject.projectDir/out/aar/app/$getDateYYMMDD"

    copyFile("$outputFile", releaseDir
            , "$outputFile.name", "$newName")

    return newName;
}

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

然后在library模塊下的build.gradle目錄下進行引用

  • build.gradle
apply from: rootProject.file("config/lib.gradle")

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

    implementation rootProject.ext.dependencies["x_appcompat"]
    testImplementation rootProject.ext.dependencies["junit"]
    androidTestImplementation rootProject.ext.dependencies["runner"]
    androidTestImplementation rootProject.ext.dependencies["espresso-core"]
}
Modlue-lib(debug)-build.gradle

其實這個模塊還是屬性lib模塊,只是在模塊化項目下滓窍,某些情況下我們只負責某一個庫模塊卖词,這個時候,如果要進行調(diào)試吏夯,相對于application可以直接在as上run,lib模塊可能會有些劣勢了此蜈。那有沒有辦法在最小的改動下我們也可以讓lib也能像app一樣run起來了?

答案是肯定的噪生,只需一處改動就能實現(xiàn)裆赵。還記得config.gradle-ext里面有一個熟悉嗎?

ext {
    //true 每個業(yè)務(wù)Module可以單獨開發(fā)
    //false 每個業(yè)務(wù)Module以lib的方式運行
    //修改之后需要Sync方可生效
    isBuildModule = false
    ...
    }

對就是這個isBuildModule屬性跺嗽,當我們想讓lib變成app時战授,只需將isBuildModule改為true然后同步下工程即可

so,怎么實現(xiàn)咧桨嫁?

還是想上文lib.gradle配置一樣植兰,我們也可以重新配置一個libdebug.gradle.下文主要列出一些不同點。

  • libdebug.gradle
if (Boolean.valueOf(rootProject.ext.isBuildModule)) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}

android {
    ...
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']

            //如果是lib-app isBuildModule模式璃吧,走manifest目錄下的AndroidManifest
            if (Boolean.valueOf(rootProject.ext.isBuildModule)) {
                manifest.srcFile 'src/main/manifest/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java {
                    //lib model ,exclude all file below java/debug/
                    exclude '*modlue'
                }
            }
        }
    }

}
//自定義版本命令規(guī)則楣导,生成路徑等
if (Boolean.valueOf(rootProject.ext.isBuildModule)) {
    android.applicationVariants.all { variant ->
     ...
    }

} else {
    android.libraryVariants.all { variant ->
     ...
    }
}


然后在待調(diào)試的lib中build.gradle配置

  • build.gradle
apply from: rootProject.file("config/libdebug.gradle")

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

    if (Boolean.valueOf(rootProject.ext.isBuildModule)) {
        implementation rootProject.ext.dependencies["x_constraint_layout"]
    }
    implementation rootProject.ext.dependencies["x_appcompat"]
    testImplementation rootProject.ext.dependencies["junit"]
    androidTestImplementation rootProject.ext.dependencies["runner"]
    androidTestImplementation rootProject.ext.dependencies["espresso-core"]
}

這樣你的library模塊就可以向application一樣飛一般的run了。

but還是有些問題畜挨,如果子模塊有重寫application邏輯筒繁,或者項目中用了類似ARouter的工具結(jié)構(gòu)彬坏,那又改怎么配置咧?不急膝晾,一步一步來栓始。

  • aRouter

配置aRouter引用,模塊build.gradle進行配置

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

dependencies {
    implementation rootProject.ext.dependencies["arouter"]
    annotationProcessor rootProject.ext.dependencies["arouter-compiler"]
}

MainActivity統(tǒng)一攔截url或intent進行分發(fā)處理

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        handleIntent(intent);
    }

    private void handleIntent(Intent intent) {
        ARouterHelper.getInstance().dispatchPage(intent);
        finish();
    }
}

Arouter調(diào)用時我們也可以封裝一個工具類進行路由分發(fā)等

/**
 * @author laird
 * @date 2019-12-30 11:09
 * @desc
 */
public class ARouterHelper {
    public static final String PATH_ACTIVITY_LIB_TEST = "/ModuleLib/LibActivity";
    public static final String ACTION_ACTIVITY_LIB_TEST = "top.lairdli.action.LIB_TEST";

    public static ARouterHelper getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        private static final ARouterHelper INSTANCE = new ARouterHelper();
    }

    public void build(String path){
        ARouter.getInstance().build(path).navigation();
    }

    public void build(String path, Activity activity, int requestCode){
        ARouter.getInstance().build(path).navigation(activity,requestCode);
    }

    public void build(String path,String key,String value){
        ARouter.getInstance().build(path).withString(key,value).navigation();
    }

    public Postcard getPostCard(String path){
        return ARouter.getInstance().build(path);
    }

    //fix me
    // 1. intent can be replaced by schame-url
    // 2. withSerializable can be replaced by with object,but u should implement SerializationService First

    public void dispatchPage(Intent intent) {

        if (intent == null || intent.getAction() == null) {
            dispatchPageDefault();
            return;
        }

        switch (intent.getAction()) {
            case ACTION_ACTIVITY_LIB_TEST:
                build(PATH_ACTIVITY_LIB_TEST);
                break;

            default:
                dispatchPageDefault();
        }
    }

    private static void dispatchPageDefault() {
        // to add u default page
    }

}

更多使用方法可以參靠Arouter官方說明

  • Application

lib向app轉(zhuǎn)換時,另外一個問題就是Application邏輯的問題血当,當主APP包含Lib模塊時幻赚,我們也希望Lib中的邏輯也能被執(zhí)行,但manifest卻只能配置一個application-name.配置了主App的application后臊旭,lib就不能配置了落恼。

本著盡量解耦,最少改動的原則离熏,我們還是用面向接口編程的實現(xiàn)佳谦,先看如下類圖,看完你就明白了滋戳。

android-as-mul-module.png

Main-appliaction

public class MainApplication extends BaseApplication implements IAppApplication {

    private static final String[] MODULESLIST =
            {"top.lairdli.study.testlibrary.LibApplication"};

    @Override
    public List<String> getModuleAppClassList() {
        return Arrays.asList(MODULESLIST);
    }

    @Override
    public void init(Application instance) {
        Log.d(LOG_TAG, "---init");
        //to do u biz
    }
}

Libdebug-application

public class LibApplication extends BaseApplication {
    
    @Override
    public void init(Application instance) {
        Log.d(LOG_TAG,"---init");
        //to do u biz
    }
}

BaseApplication


/**
 * @Description: BaseApplication
 */
@SuppressLint("Registered")
public abstract class BaseApplication extends Application implements IComponentApplication {

    protected String LOG_TAG = "BaseApplication";
    private static BaseApplication instance;

    public  static BaseApplication getInstance() {
        return instance;
    }

    private List<Activity> mList = new LinkedList<Activity>();

    public BaseApplication() {
        super();
        LOG_TAG = this.getClass().getSimpleName();
    }

    @Override
    public void onCreate() {
        Log.v(LOG_TAG, "onCreate()");
        super.onCreate();
        //init ARouter
        if(BuildConfig.DEBUG){
            ARouter.openLog();
            ARouter.openDebug();
        }
        ARouter.init(this);
        instance= this;
        //asbs method ,implementation in sub class
        init(this);
        //init call modulesApplication
        if (IAppApplication.class.isAssignableFrom(this.getClass())) {
            IAppApplication appApplication = (IAppApplication) this;
            modulesApplicationInit(appApplication.getModuleAppClassList());
        }
    }

    private void modulesApplicationInit(List<String> modulesList){
        for (String moduleImpl : modulesList){
            try {
                Class<?> clazz = Class.forName(moduleImpl);
                Object obj = clazz.newInstance();
                if (obj instanceof IComponentApplication){
                    ((IComponentApplication) obj).init(BaseApplication.getInstance());
                }
            } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

IApplication

public interface IAppApplication {
    List<String> getModuleAppClassList();
}

IComponentApplication

public interface IComponentApplication {
    void init(Application instance);
}

MainApplication編譯時運行打印如下

2019-12-30 11:43:39.219 23539-23539/? D/MainApplication: ---init
2019-12-30 11:43:39.219 23539-23539/? D/LibApplication: ---init

Libdebug-application當模塊編譯時運行打印如下

2019-12-30 11:43:39.219 23539-23539/? D/LibApplication: ---init

以上是我總結(jié)的一種解決模塊化Application問題的方法之一钻蔑,如果還有其他更好的方法也歡迎補充。

eclipse-translate 舊工程遷移

對于以前eclipse舊工程奸鸯,如果我們不希望破壞之前原有路徑或者代碼結(jié)構(gòu)咪笑,但又想再忍受eclipse的IED,想再AS上進行調(diào)試娄涩,只需3個步驟三部曲就行

1. 腳本拷貝

新建as工程文件夾窗怒,拷貝正常as項目文件配置到步驟1所建立的文件夾

-rw-r--r--   1 laird  staff   713 12 29 16:05 build.gradle
drwxr-xr-x   3 laird  staff    96 12 29 14:42 gradle/
-rw-r--r--   1 laird  staff  1073 12 29 15:20 gradle.properties
-rwxr--r--   1 laird  staff  5296 12 29 14:42 gradlew*
-rw-r--r--   1 laird  staff  2260 12 29 14:42 gradlew.bat
-rw-r--r--   1 laird  staff  2079 12 29 17:26 settings.gradle

2. 項目配置

配置項目setting.gradle,將需要轉(zhuǎn)換的eclispe工程模塊include到setting.gradle中蓄拣。

可參考As-project-setting.gradle

rootProject.name = 'ProjectEclipse2As'

include 'app_module'
project(':app_module').projectDir = new File('../u eclispe app moudle path)

include 'lib_module'
project(':lib_vodservice').projectDir = new File('../../u eclispe lib moudle path')

3. 配置模塊build.gradle

模塊build.gradle包含library,application兩種扬虚,掌握了application的配置,library的配置對比著配就行了

建議直接copy一份完整的application-build.gradle球恤,然后我們只需要改幾個關(guān)鍵的點就行

  • 源碼路徑

    由于as默認的源碼構(gòu)建方式和eclipse有些區(qū)別辜昵,因為第一個重要的點就是配置源碼路徑

        sourceSets {
            main {
                manifest.srcFile 'manifest/AndroidManifest.xml'
                java.srcDirs = ['src']
                resources.srcDirs = ['src']
                aidl.srcDirs = ['src']
                renderscript.srcDirs = ['src']
                res.srcDirs = ['res']
                assets.srcDirs = ['assets']
            }
        }
    

    需要注意的是manifest,由于最新版本的as對manifest的一些配置有強制限制(版本號相關(guān))碎捺,因而建議copy一份AndroidManifest.xml到manifest文件夾路鹰,重新制定路徑贷洲。這樣不影響之前elcipse工程配置收厨。

  • 依賴配置

    原eclipse工程依賴配置可在project.properties文件中查看,

    target=android-17
    proguard.config=proguard.cfg
    android.library.reference.1=../../../library1
    android.library.reference.2=../../../library2
    

    根據(jù)project.properties的配置在build.gradle中的depend中相應(yīng)配置优构。

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

    需要注意的是除了模塊間的依賴外诵叁,libs下的jar包,so依賴也許要注意钦椭。

    jar包依賴分兩種拧额,一種編譯時依賴碑诉,一種運行時依賴。

    編譯時依賴只參與編譯侥锦,不打入app源碼进栽,一般是引用系統(tǒng)api會用到。

    編譯時依賴使用compileOnly

      compileOnly files('libs/compileOnlyxxx.jar')
    

    運行時依賴除了參與編譯恭垦,會打入app源碼快毛,常見的模塊鍵依賴就是這種。

    運行時依賴使用implementation

      implementation files('libs/compilexx.jar
    
  • 編譯配置

    eclispe編譯配置可在mainifest文件中查看番挺,然后在build.gradle相應(yīng)配置就行唠帝。

后記

Gralde的學(xué)習應(yīng)遠不止與此,重在實踐與理解玄柏。

本文涉及到的相關(guān)源碼已整理開源到github

示例-androidgradle

https://github.com/lairdli/AndroidGradle

在線瀏覽-androidgradle

https://lairdli.top/2019/12/30/android-gradle/

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末襟衰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子粪摘,更是在濱河造成了極大的恐慌瀑晒,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徘意,死亡現(xiàn)場離奇詭異瑰妄,居然都是意外死亡,警方通過查閱死者的電腦和手機映砖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門间坐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人邑退,你說我怎么就攤上這事竹宋。” “怎么了地技?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵蜈七,是天一觀的道長。 經(jīng)常有香客問我莫矗,道長飒硅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任作谚,我火速辦了婚禮三娩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘妹懒。我一直安慰自己雀监,他們只是感情好,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著会前,像睡著了一般好乐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瓦宜,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天蔚万,我揣著相機與錄音,去河邊找鬼临庇。 笑死笛坦,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的苔巨。 我是一名探鬼主播版扩,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼侄泽!你這毒婦竟也來了礁芦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤悼尾,失蹤者是張志新(化名)和其女友劉穎柿扣,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闺魏,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡未状,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了析桥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片司草。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖泡仗,靈堂內(nèi)的尸體忽然破棺而出埋虹,到底是詐尸還是另有隱情,我是刑警寧澤娩怎,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布搔课,位于F島的核電站,受9級特大地震影響截亦,放射性物質(zhì)發(fā)生泄漏爬泥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一崩瓤、第九天 我趴在偏房一處隱蔽的房頂上張望袍啡。 院中可真熱鬧,春花似錦谷遂、人聲如沸葬馋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽畴嘶。三九已至,卻和暖如春集晚,著一層夾襖步出監(jiān)牢的瞬間窗悯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工偷拔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蒋院,地道東北人。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓莲绰,卻偏偏與公主長得像欺旧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蛤签,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容