Gradle系列第(三)篇---Android Studio與Gradle那些事兒

版權(quán)聲明:本文為LooperJing原創(chuàng)文章,轉(zhuǎn)載請注明出處!

·

Android中的gradle.jpg

本來這篇要寫Android性能優(yōu)化的,個人時間比較少,每天加班到很晚,寫博客的時間就很少了,但是Gradle系列的文章還沒有寫完,所以補(bǔ)一篇,在Gradle系列第(二)篇---Gradle編程主要對象主要寫了Gradle中的幾個對象(Project,Settings,Gradle,Task、Action),現(xiàn)在聊一聊Android Studio中的gradle常見的功能需求。如果你還沒有閱讀過我的前兩篇博客Gradle系列第(一)篇---Groovy語法初探Gradle系列2---Gradle編程主要對象,可以先看一下,有助于本文的理解,好啦,各位看官準(zhǔn)備好瓜子花生,接下來一大篇文章嘩啦啦的來了。不過不用擔(dān)心,這篇博客仍然是面向基礎(chǔ)。

讀完這篇博客,你會了解到這些內(nèi)容

  • 1、Android的構(gòu)建文件
  • 2、全局參數(shù)配置
  • 3、用腳本更改項目結(jié)構(gòu)
  • 4、多種apk的生成
  • 5、簽名的配置與使用
  • 6、項目混淆(Proguard)
  • 7、gradle多渠道打包
  • 8、APK需求定制的案例
  • 9、動態(tài)參數(shù)配置
  • 10、gradle依賴管理
  • 11、gradle.properties文件配置
  • 12、jar文件輸出
一、AS項目構(gòu)建文件的簡單解釋

一個AS項目結(jié)構(gòu)大概像下面這樣子

項目構(gòu)建文件.jpg

如藍(lán)色條所示,項目中總共包含了6個構(gòu)建文件(不算Library中的gradle),我們先從宏觀的方面了解一下,每個構(gòu)建文件的作用是啥?

  • 1、這個文件是app文件夾下這個Module的gradle配置文件,也可以算是整個項目最主要的gradle配置文件,比如自動打包debug,release,beta等環(huán)境,簽名,多渠道打包,混淆等操作都可以在這里面寫。每一個Module都需要有一個gradle配置文件。
  • 2、我們主要看下gradle-wrapper.properties這個文件的內(nèi)容
#Mon Dec 28 10:00:20 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

可以看到里面聲明了gradle的目錄與下載路徑以及當(dāng)前項目使用的gradle版本,這些默認(rèn)的路徑我們一般不會更改的,有時候?qū)胍粋€新項目,gradle版本不對,可以在這里修改。

  • 3、這個文件是整個項目的gradle基礎(chǔ)(全局)配置文件,內(nèi)容主要包含了兩個方面:一個是聲明倉庫的源,這里可以看到是指明的jcenter(), 之前版本則是mavenCentral(), jcenter可以理解成是一個新的中央遠(yuǎn)程倉庫,兼容maven中心倉庫,而且性能更優(yōu)。另一個是聲明了android gradle plugin的版本。allprojects:中定義的屬性會被應(yīng)用到所有 moudle 中,但是為了保證每個項目的獨立性,我們一般不會在這里面操作太多共有的東西。

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.0'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

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

所有通過gradle導(dǎo)入的jar包都是從http://bintray.com/bintray/jcenter這個中央倉庫上扒下來的。如果你需要的jar包在這個網(wǎng)站上沒有,那就無法通過gradle的方式來導(dǎo)入哦。

  • 4、這個里面可以配置參數(shù),然后在其他build.gradle中引用,后面會講例子,如何動態(tài)配置參數(shù)。
  • 5、這里主要指定了ndk和SDK的路徑
ndk.dir=G\:\\Users\\wangjing\\AppData\\Local\\Android\\sdk\\ndk-bundle
sdk.dir=G\:\\Users\\wangjing\\AppData\\Local\\Android\\sdk
  • 6、setting.gradle最關(guān)鍵的內(nèi)容就是告訴Gradle這個multiprojects包含哪些子projects,當(dāng)你的app只有一個模塊的時候,你的setting.gradle將會是這樣子的:
include ':app'

當(dāng)你的app有多個模塊的時候,你的setting.gradle將會是這樣子的

include ':app', ':library',。。。。

setting.gradle文件將會在gradle初始化時期執(zhí)行,關(guān)于初始化時期,可以查看上一篇博客,并且定義了哪一個模塊將會被構(gòu)建。舉個例子,上述setting.gradle包含了app模塊,setting.gradle是針對多模塊操作的,所以單獨的模塊工程完全可以刪除掉該文件。在這之后,Gradle會為我們創(chuàng)建一個Setting對象,每一個settings.gradle都會轉(zhuǎn)換成一個Settings對象,并為其包含必要的方法,你不必知道Settings類的詳細(xì)細(xì)節(jié),但是你最好能夠知道這個概念。另外可以在settings做一些初始化的工作,后面介紹。

讀到這里做個總結(jié)

  • build.gradle:控制每個Module的構(gòu)建過程
  • gradle.properties:設(shè)置gradle腳本中的參數(shù)
  • local.properties:gradle的SDK和NDK環(huán)境變量配置
  • gradle.properties:用于配置參數(shù)信息
  • setting.gradle :配置gradle的多項目管理
二、實用技能精講
1、全局參數(shù)配置

通常我們的項目都有很多的Module,像我現(xiàn)在公司的項目就有十幾個,那么每個Module里面的gradle文件通常都有類似這樣的配置。

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"
    defaultConfig {
        applicationId "com.zhangwan.www.gradle"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

這些配置對于每個Module來說,最好統(tǒng)一,我把它定義在項目根目錄的gradle文件中,如下。

//全局配置
ext {
    minSdkVersion =15
    targetSdkVersion =24
    compileSdkVersion =24
    buildToolsVersion ="24.0.0"
    versionCode =1
    versionName="1.0"
}

定義好了,我們可以在各個Module的gradle文件文件中引用,如下:

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
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

利用Gradle全局變量,對于多Module有很大的好處,方便統(tǒng)一,除了上面的列子,在舉個例子。上面全部按照單個的屬性配置的,對于相關(guān)的屬性,可以將他們寫到一個列表中,下面定義了一個dependencies_config的列表。

ext{
    dependencies_config=[supportv7:"com.android.support:appcompat-v7:25.0.0"]
}

在Module中,這樣引用

dependencies {
    compile rootProject.ext.dependencies_config.supportv7
     .....
}
2、項目結(jié)構(gòu)更改

sourceSets 的作用是重新定義資源文件位置,比如

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"
 
    sourceSets{
        main{
            res.srcDirs=['src/main/res','src/main/res/layout/activity','src/main/res/layout/fragment']
        }
    }
}

在你Sync Now之后,會出現(xiàn)activity和fragment兩個文件夾


更改項目目錄.png

最常見的是下面這塊代碼,當(dāng)Eclipse項目轉(zhuǎn)到Studio的時候,需要重新指定一些文件的位置。

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

sourceSets的用法就是這樣,可以重新指定文件目錄,但是讀到這,可能有些人心中有個問題,為什么sourceSets ,defaultConfig這樣的東東要寫在android的大括號中。換言之,android這個大括號里面還能寫什么東西,我來列舉一下。

android {
    defaultConfig {
        //默認(rèn)配置項,defaultConfig就是程序的默認(rèn)配置,注意,如果在   AndroidMainfest.xml里面定義了與這里相同的屬性,會以這里的為主。
    }

    buildTypes {
      // 編譯配置,release或debug版本的內(nèi)容
    }

    compileOptions {
      // Java 的版本配置
    }

    sourceSets {
        //源碼設(shè)置(項目目錄結(jié)構(gòu)的設(shè)置)
    }

    packagingOptions {
       //打包時的相關(guān)配置  
    }

    lintOptions {
        //編譯的 lint 開關(guān),程序在buid的時候,會執(zhí)行l(wèi)int檢查,有任何的錯誤或者警告提示,都會終止構(gòu)建,我們可以將其關(guān)掉。
        //abortOnError false  
    }

    productFlavors {
        //產(chǎn)品發(fā)布的一些東西,比如渠道、包名等
        flavor1 {
        }

        flavor2 {
        }
    }

    signingConfigs {
        //簽名的配置
        release {
        }
    }

    testOptions{
        //測試配置,TestOptions類型
    } 
    aaptOptions{
      //aapt配置,AaptOptions類型 
    } 
     lintOptions{
       //lint配置,LintOptions類型
    } 
    dexOptions{
       //dex配置,DexOptions類型
    } 
    compileOptions{
     // 編譯配置,CompileOptions類型
    } 
    packagingOptions{
       // PackagingOptions類型
    } 
    jacoco{ 
       //JacocoExtension類型。 用于設(shè)定 jacoco版本
    } 
    splits{
       //Splits類型。
    } 
}

在DSL文檔中,以上每個類型都有它的詳細(xì)配置選項,一般常見的設(shè)置就是上面啦,如果你覺得有的不太了解,看下面之后就了解了。

3、多種apk的生成

默認(rèn)studio生成的buildTypes是像下面這樣的,但是呢,我還想要其他的變種類型。

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

我們可以這樣添加

 buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        r1{
            applicationIdSuffix ".r1"
        }
        r2{
            applicationIdSuffix ".r2"
        }
        r3{
            applicationIdSuffix ".r3"
        }
    }

通過這樣就可以得到多種變種app,執(zhí)行assemble這個task,打出所有apk。

build_types.png

總共得到系統(tǒng)默認(rèn)有的release和debug兩個apk,額外還有r1,r2,r3三個不同的apk。
那么applicationIdSuffix是什么呢,逆向r1包看看。

r1包逆向結(jié)果.png

系統(tǒng)通過包名來區(qū)分應(yīng)用,這種方式無非就是在包名后面加上了一個后綴r1。

4、簽名的配置與使用

上面打出的包都是沒有指定簽名的,我們要配置一個簽名,首先需要生成簽名文件。我生成的簽名文件是1.jks

 signingConfigs{
        signR1{
            storeFile file("build/1.jks");
            storePassword "123456"
            keyAlias "xxx"
            keyPassword "123456"
        }
        signR2{
            storeFile file("build/2.jks");
            storePassword "123456"
            keyAlias "xxx"
            keyPassword "123456"
        }
    }

簽名在signingConfigs中配置,signR1,signR2是簽名的名字,在buildTypes中使用。

 buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        r1{
            signingConfig signingConfigs.signR1
            applicationIdSuffix ".r1"
        }
        r2{
            signingConfig signingConfigs.signR2
            applicationIdSuffix ".r2"
        }

    }

加上簽名后打的包是這樣,跟未加簽名相比較,多了app-r1.apk,app-r2.apk。


簽名apk生成.png
5、項目混淆(Proguard)

面對眾多的渠道,打包也有很多不同的需求。 比如 debug版,release版,dev版等等。 有時候不同的版本中使用到的不同的服務(wù)端api域名也不相同。 比如 debug_api.com,release_api.com,dev_api.com等等。不同的版本對應(yīng)了不同的 api 域名,還可能對應(yīng)不同的 icon 等。渠道首發(fā)包通常需要要求在歡迎頁添加渠道的logo等。下面我們開始進(jìn)行打包。首先進(jìn)行混淆設(shè)置,混淆需要buildTypes中配置,在上面說過,默認(rèn)生成的buildTypes是這樣子的

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

其中,proguard-android.txt是在你的sdk\tools\proguard目錄下。minifyEnabled:表示是否開啟混淆,默認(rèn)為false;proguardFiles:混淆配置文件,一般就采用項目中默認(rèn)的proguard-rules.pro文件。在這個文件中寫我們的混淆規(guī)則,比如:

-keepclasseswithmembernames class * {                                           # 保持 native 方法不被混淆
    native <methods>;
}

-keepclassmembers enum * {                                                      # 保持枚舉 enum 類不被混淆
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keep class * implements android.os.Parcelable {                                # 保持 Parcelable 不被混淆
  public static final android.os.Parcelable$Creator *;
}

有這些還不夠,還需要在gradle中開啟混淆

   buildTypes {
        release {
           // 不顯示 Log 
            buildConfigField "boolean", "LOG_DEBUG", "false"
            shrinkResources true
            signingConfig signingConfigs.release
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.cfg'
          
        }
        debug {
          // 顯示 Log 
            buildConfigField "boolean", "LOG_DEBUG", "true"
            signingConfig signingConfigs.debug
        }
    }

我們設(shè)置minifyEnabled true,就會在打包的時候進(jìn)行代碼混淆處理. 其中proguard-android.txt不用管,在sdk目錄里面,我們主要是配置了proguard.cfg文件??赡艽蠹抑苯釉赼ndroid studio創(chuàng)建項目不會有這個文件,而是proguard-rules.pro文件,其實一樣的,我這里是因為項目是從eclipse遷移過來的,之前在eclipse上混淆是proguard.cfg文件.

6、gradle多渠道打包
  • 1、第一步 在AndroidManifest.xml里配置PlaceHolder
 <meta-data
            android:name="MY_CHANNEL"
            android:value="${MY_CHANNEL}" />
  • 2、第二步 在build.gradle設(shè)置productFlavors
   productFlavors{
        xiaomi {
            //用gradle修改AndroidManifest.xml中的meta-data元素值
            manifestPlaceholders = [MY_CHANNEL: "xiaomi"]
        }

        _360 {
            manifestPlaceholders = [MY_CHANNEL: "_360"]
        }
        baidu {
            manifestPlaceholders = [MY_CHANNEL: "baidu"]
        }

        huawei{
            manifestPlaceholders = [MY_CHANNEL: "huawei"]
        }
    }

或者批量修改

 productFlavors{
        xiaomi {}
        _360 {}
        baidu {}
        huawei{}
    }

    productFlavors.all {
        flavor -> flavor.manifestPlaceholders = [MY_CHANNEL: name]
    }

最后,最好在defaultConfig中定義一個默認(rèn)的渠道

defaultConfig{
        manifestPlaceholders = [ MY_CHANNEL:"xiaomi" ]
}

到此配置完成,可以執(zhí)行命令了。

  • 3、去工程的根目錄,也就是有g(shù)radlew文件的目錄,打開命令行,輸入命令:
    ./gradlew assemble
    這時候你去app/build/outputs/apk中就能看到自動打好的渠道包了。
    ./gradlew assembleRelease
    只打Release包
    ./gradlew assembleDebug
    只打Debug包
    ./gradlew assemblebaidu
    只打360的渠道包
    ./gradlew assemblebaiduRelease

不想敲命令行的,調(diào)起下面這個面板打包

打包Task.png
7、APK需求定制

上面說了一下打包,在打包的時候,一些特殊化的操作,比如修改指定apk Logo,apk重命名,這些怎么搞?

  • 渠道包重命名
android.applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def outputFile = output.outputFile
            if (outputFile != null && outputFile.name.endsWith('.apk')) {
                File outputDirectory = new File(outputFile.parent);
                def fileName
                if (variant.buildType.name == "release") {
                    fileName = "wangjing_${variant.productFlavors[0].name}.apk"
                } else {
                    fileName = "wangjing_${variant.productFlavors[0].name}_beta.apk"
                }
                output.outputFile = new File(outputDirectory, fileName)
            }
        }
    }
  • 根據(jù)渠道修改APP名稱
buildTypes {
        debug {
            // 顯示Log
            buildConfigField "boolean", "LOG_DEBUG", "true"
            //重命名
            resValue("string","app_name","DEBUG")
            versionNameSuffix "-debug"
            minifyEnabled false
            zipAlignEnabled false
            shrinkResources false
            signingConfig signingConfigs.debug
        }
        release {
            // 不顯示Log
            buildConfigField "boolean", "LOG_DEBUG", "false"
            //重命名
            resValue("string","app_name","DEBUG")
            //混淆
            minifyEnabled true
            //加載默認(rèn)混淆配置文件
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            //簽名
            signingConfig signingConfigs.release

        }
    }

其中 resValue("string","app_name","DEBUG") 表示一個string 類型的變量app_name的值是DEBUG,做了上面的配置之后,需要將string.xml的app_name刪掉,因為gradle編譯的時候,會將腳本中的配置跟string.xml的合并。

8、動態(tài)參數(shù)配置
signingConfigs{
        release{
            storeFile file("build/mykey.jks")
            storePassword   "123456"
            keyAlias "123456"
            keyPassword   "123456"
        }
    }

上面的這段配置,有個缺點,就是值直接寫死了,我們可以動態(tài)配置參數(shù)。在哪里配置呢,一開始就說了,在gradle.properties中配置參數(shù)。如下:

 systemPro.keyAliasPassword=123456
systemPro.keyAlias=123456
systemPro.keyStorePassword=123456
systemPro.keyStore=mykey.jks

配置好了,就可以“到處”使用了

 signingConfigs{
        release{
            storeFile       System.properties["keyStore"]
            storePassword   System.properties["keyStorePassword"]
            keyAlias        System.properties["keyAlias"]
            keyPassword     System.properties["keyAliasPassword"]
        }
        debug{
            storeFile file("mykey.jks")
            storePassword  "123456"
            keyAlias"123456"
            keyPassword  "123456"
        }
    }

9、gradle依賴管理

比如我們想依賴個support-v4包,直接一句話:

compile 'com.android.support:support-v4:23.1.1'

一個依賴需要定義三個元素:group,name和version。group意味著創(chuàng)建該library的組織名,通常這會是包名,name是該library的唯一標(biāo)示。
上述的代碼是基于groovy語法的,所以其完整的表述應(yīng)該是這樣的:

compile group: 'com.android.support:', name: 'support-v4', version:'23.1.1'

有些時候,你可能需要和sdk協(xié)調(diào)工作。為了能順利編譯你的代碼,你需要添加SDK到你的編譯環(huán)境。你不需要將sdk包含在你的APK中,因為它早已經(jīng)存在于設(shè)備中,不需要在compile,我們總共有5個不同的配置:

  • compile是默認(rèn)的那個,其含義是包含所有的依賴包,即在APK里,compile的依賴會存在。

  • apk的意思是apk中存在,但是不會加入編譯中,這個貌似用的比較少。

  • provided的意思是提供編譯支持,但是不會寫入apk。

  • testCompileandroidTestCompile會添加額外的library支持針對測試。

通常項目的Module很多,依賴也非常多,為了方便管理,我們應(yīng)該將這些依賴寫到一個全局的地方,可以供其他module使用。這種思想也是第一小節(jié)所提的全局參數(shù)的配置。依賴管理可以參考:http://stormzhang.com/android/2016/03/13/gradle-config/

10、gradle.properties文件配置
  • gradle.properties常見配置比如有:
    開啟并行編譯:加快gradle 的編譯
    org.gradle.parallel=true

  • 開啟編譯守護(hù)進(jìn)程:該進(jìn)程在第一次啟動后回一直存在,當(dāng)你進(jìn)行二次編譯的時候,可以重用該進(jìn)程。
    org.gradle.daemon=true

  • 加大可用編譯內(nèi)存:
    org.gradle.jvmargs=-Xms256m -Xmx1024m

11、jar文件輸出

android Studio常常有輸出jar包的需求,只要下面這段代碼即可:

task makeJar(type: Copy) {
    delete 'build/libs/my.jar'
    from('build/intermediates/bundles/release/')
    into('build/libs/')
    include('classes.jar')
    rename ('classes.jar', 'my.jar')
}

OK,Gradle研究了一個多星期,這篇博客耗時兩個晚上,終于結(jié)束,另外如果時間來的急,在寫一篇Gradle系列4或5,因為感覺自己還沒有講清楚,側(cè)重多個Module中g(shù)radle的使用與Gradle常用命令的使用,下篇博客繼續(xù)性能優(yōu)化系列的更新,每一次寫博客都花費很多的時間和精力也是一次鍛煉,跟他人分享自己的學(xué)習(xí)成果,最后附上參考資料,比我寫的好。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,950評論 25 709
  • 1.介紹 如果你正在查閱build.gradle文件的所有可選項,請點擊這里進(jìn)行查閱:DSL參考 1.1新構(gòu)建系統(tǒng)...
    Chuckiefan閱讀 12,357評論 8 72
  • 這一章主要針對項目中可以用到的一些實用功能來介紹Android Gradle,比如如何隱藏我們的證書文件,降低風(fēng)險...
    acc8226閱讀 7,966評論 3 25
  • ① 朗費羅說:“不要老嘆息過去,它是不再回來的;要明智地改善現(xiàn)在。 人生是條無名的河,是深是淺...
    玉兒說閱讀 795評論 3 3
  • 這是最近打磨出來的一個小技巧,也是之前思考時間概念時,迸發(fā)出來的一個小感悟。 干貨總結(jié)語:穿越到現(xiàn)在的未來的自己,...
    果大喵喵閱讀 760評論 0 0

友情鏈接更多精彩內(nèi)容