gradle實戰總結
1、獲取properties配置檔案的值
def getLocalProperties(String key, Object defValue) { try { Properties properties = new Properties() properties.load(new File(rootDir.absolutePath + "/local.properties").newDataInputStream()) def value = properties.getProperty(key, defValue) return value } catch (Exception e) { return defValue } } task hello << { def sdkPath = getLocalProperties('sdk.dir','~~') println "sdk 的路徑為 ${sdkPath}" }
執行./gradlew hello 命令,執行結果如下:
Task :hello
sdk 的路徑為 /Users/lianjia/Library/Android/sdk
2、gradle執行指令碼,並獲取指令碼的返回值
- 通過設定CommondLine引數的方式執行指令碼
taskgetGitVersion << { def stdout = new ByteArrayOutputStream() exec { commandLine 'git','--version' standardOutput = stdout } def version = stdout.toString().substring(stdout.toString().indexOf('n')+1) println "git版本號為:${version}" }
執行./gradlew getGitVersion,結果如下:
Task :getGitVersion
git版本號為: 2.18.0
-
通過設定args引數的方式執行指令碼
外掛化專案中用到這個task,用adb命令把外掛拷貝到plugins目錄下,以便debug的時候,直接這條命令便可debug我們外掛開發的程式碼
task pushPlugin() { doFirst { def apkExistPath = transferPath( new File(project.getBuildDir().absolutePath + "/outputs/apk", pluginExistName).absolutePath) def command = "adb shell mkdir sdcard/plugins; adb push -p $apkExistPath sdcard/plugins/$pluginPushName; adb shell am force-stop $packageName" exec { try { executable 'bash' args "-c", "$command" } catch (Exception e) { println e.message println("=====================push plugin failed with exception.=========================") } } println("=====================push plugin successfull.=========================") } doLast { restart.execute() } }
這是gradle特別牛的地方,我們不僅可以執行各種bash命令,例如git、adb、python等
3、自定義你的BuildConfig
Android打包完之後,在build資料夾下會生成一個BuildConfig類,欄位包含了專案構建的一些基本資訊,比如:ApplicationId,buildType,versionName和versionCode等
public final class BuildConfig { public static final boolean DEBUG = Boolean.parseBoolean("true"); public static final String APPLICATION_ID = "com.lianjia.link.linkplugin"; public static final String BUILD_TYPE = "debug"; public static final String FLAVOR = ""; public static final int VERSION_CODE = 1; public static final String VERSION_NAME = "1.0"; }
我們可以通過buildConfigField(String type,String name,String value)讓我們可以新增自己的常量到BuildConfig
如果我們需要在不同的渠道中新增不同的一些引數,那麼我們便可以使用buildConfigField這個方法實現,如下所示專案中配置了兩個渠道,google和baidu,如果是google渠道,那麼我們配置了WEB_URL的值分別為 ofollow,noindex">http://www.google.com 和 http://www.baidu.com
android { compileSdkVersion 28 defaultConfig { applicationId "com.lianjia.link.linkplugin" minSdkVersion 15 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" buildConfigField 'String', 'WEB_URL', '"http://www.baidu.com"' } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } productFlavors{ google { buildConfigField 'String','WEB_URL','"http://www.google.com"' } baidu { buildConfigField 'String','WEB_URL','"http://www.baidu.com"' } } }
build專案之後檢視一下BuildConfig這個類已經添加了WEB_URL的常量
public final class BuildConfig { public static final boolean DEBUG = Boolean.parseBoolean("true"); public static final String APPLICATION_ID = "com.lianjia.link.linkplugin"; public static final String BUILD_TYPE = "debug"; public static final String FLAVOR = ""; public static final int VERSION_CODE = 1; public static final String VERSION_NAME = "1.0"; // Fields from default config. public static final String ALL_MODEUL = "app,router"; public static final String WEB_URL = "http://www.baidu.com"; }
4、動態配置AndroidManifest檔案
動態配置AndroidManifest檔案,顧名思義是可以在構建的過程中,動態修改AndroidManifest檔案中的一些內容。這樣的例子非常多,比如使用友盟等第三方分析統計的時候,會要求我們在AndroidManifest檔案中指定渠道名稱:
<meta-data android:name="UMENG_CHANNEL" android:value="${UMENG_CHANNEL}"/>
對於這種情況我們不可能定義很多個AndroidManifest檔案,因為這種工作太繁瑣,而且維護麻煩。所以就需要在構建的時候,根據正在生成的不同渠道包來為其制定不同的渠道名稱。
對於這種情況,AndroidGradle提供了非常便捷的方法讓我們來替換AndroidManifest檔案中的內容,它就是ManifestPlaceHolder、Manifest佔位符。
ManifestPlaceholders是ProductFlavor的一個屬性,是一個Map型別,所以我們同時可以配置多個佔位符。下面我們在google和baidu兩個渠道中配置了google和baidu的渠道名,如下:
productFlavors{ google { manifestPlaceholders.put("UMENG_CHANNEL","google") } baidu { manifestPlaceholders.put("UMENG_CHANNEL","baidu") } }
build專案,開啟反編譯apk,我們發現baidu渠道中的渠道名稱已經修改成baidu了,如下所示:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.lianjia.link.linkplugin"> <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="28" /> <application android:theme="@ref/0x7f0c0005" android:label="@ref/0x7f0b0027" android:icon="@ref/0x7f0a0000" android:debuggable="true" android:allowBackup="true" android:supportsRtl="true" android:roundIcon="@ref/0x7f0a0001" android:appComponentFactory="android.support.v4.app.CoreComponentFactory"> <activity android:name="com.lianjia.link.linkplugin.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <meta-data android:name="UMENG_CHANNEL" android:value="baidu" /> </application> </manifest>
5、使用resValue 動態新增自定義資源
在我們開發Android的過程中,我們會使用很多資源,有圖片、動畫、字串等,這些資源我們可以在res資料夾裡定義,然後在工程裡引用即可使用。這裡我們講的自定義資源是專門針對res/values型別資源的,它們不光可以在res/values資料夾裡使用xml的方式定義,還可以在我們的Android Gradle中定義,這大大增加了構建的靈活性。
實現這一功能的正式resValue方法,它在BuildType和ProductFlavor這兩個物件都存在,也就是我們可以分別針對不同的渠道,或者不同構建型別來自定義其特有的資源。我們可以先看看ProductFlavor中的resValue方法為例,可以先看看它的原始碼實現:
/** * Adds a new generated resource. * * <p>This is equivalent to specifying a resource in res/values. * * <p>See <a * href="http://developer.android.com/guide/topics/resources/available-resources.html">Resource * Types</a>. * * @param type the type of the resource * @param name the name of the resource * @param value the value of the resource */ public void resValue(@NonNull String type, @NonNull String name, @NonNull String value) { ClassField alreadyPresent = getResValues().get(name); if (alreadyPresent != null) { String flavorName = getName(); if (BuilderConstants.MAIN.equals(flavorName)) { logger.info( "DefaultConfig: resValue '{}' value is being replaced: {} -> {}", name, alreadyPresent.getValue(), value); } else { logger.info( "ProductFlavor({}): resValue '{}' value is being replaced: {} -> {}", flavorName, name, alreadyPresent.getValue(), value); } } addResValue(new ClassFieldImpl(type, name, value)); }
從其文件註釋中可以看到,它會新增生成一個資源,其效果和在res/values資料夾中定義一個資源是等價的。
resValue方法有3個引數:
type:要定義的資源型別,比如string、id、boolean等
name:自定義資源的名稱,以便我們在工程中引用它。
value:定義的資源值
下面開始使用了,分別在google和baidu兩個渠道中定義了一個channel_tips的string型別,它們的值各不相同。如下所示:
productFlavors{ google { resValue 'string','channel_tips','google渠道歡迎你' } baidu { resValue ' ','channel_tips','baidu渠道歡迎你' } }
寫好了配置之後,reuild一下專案,然後在/build/generated/res/resValues/baidu/debug/values下面會生成一個generated.xml檔案,如下所示:
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- Automatically generated file. DO NOT MODIFY --> <!-- Values from product flavor: baidu --> <string name="channel_tips" translatable="false">baidu渠道歡迎你</string> </resources>
上面演示的事string型別,也可以使用id、boolean、integer、color等這些型別來自定義values資源。總之這個reValue方法和BuildConfigField方法非常類似。
6、批量修改生成的apk檔名
android { compileSdkVersion 28 defaultConfig { applicationId "com.lianjia.link.linkplugin" minSdkVersion 15 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" flavorDimensions "versionCode" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } productFlavors{ google { manifestPlaceholders.put("UMENG_CHANNEL","google") buildConfigField 'String','WEB_URL','"http://www.google.com"' resValue 'string','channel_tips','google渠道歡迎你' } baidu { manifestPlaceholders.put("UMENG_CHANNEL","baidu") buildConfigField 'String','WEB_URL','"http://www.google.com"' resValue 'string','channel_tips','baidu渠道歡迎你' } } applicationVariants.all { variant -> variant.outputs.all { output -> if(output.outputFile != null && output.outputFile.name.endsWith('.apk')){ output.outputileName = "link_${variant.flavorName}_${variant.buildType.name}_${buildTime()}.apk" } } } }
applicationVariants 是一個DomainObjectCollection集合,我們可以通過all方法進行遍歷,遍歷的每一個variant都是一個生成的產物。
application Variants中的variant都是Application Variant,通過檢視原始碼,可以看到它有一個outputs作為它的輸出。每一個Application Variant至少有一個輸出,也可以有多個,所以這裡的outputs屬性是一個List集合、我們再遍歷它,如果它的名字是以.apk結尾的話,我們再去修改apk的名字就好了。我們發現output的實際上是ApkVariantOutputImpl類,它實現了ApkVariantOutput介面,我們在ApkVariantOutput介面中找到了一個setOutputFileName方法,那就好辦了,直接通過呼叫該方法便可修改生成的apk名稱。
7、grale檔案模組化
我們都知道專案中如果有很多依賴庫的話,gradle檔案會非常臃腫,不易於維護,那麼此時我們可以把那些依賴庫放在單獨的一個gradle檔案中,用ext來放這些依賴,最後通過apply from引用該檔案。
config.gradle ext { isPlugin = true //全域性環境配置 android = [compileSdkVersion: 25, minSdkVersion: 19, targetSdkVersion: 22, // 不要寫成23,寫成23會在6.0手機上進行許可權隱私校驗,如果沒有判斷就會crash renderscriptTargetApi: 19, versionName: "3.26.0", lianjiasticker: "LianjiaSticker",] //依賴配置 dependencies = [ /*Google Library*/ libFlexbox: "com.google.android:flexbox:0.2.5", ... ] debugDependencies = [ /*Square Library*/ libLeakCanary: "com.squareup.leakcanary:leakcanary-android:1.5.1", /*Facebook Library*/ libStethoOkhttp3: "com.facebook.stetho:stetho-okhttp3:1.4.1", libStetho: "com.facebook.stetho:stetho:1.3.0",] releaseDependencies = [ /*Square Library*/ libLeakCanary: "com.squareup.leakcanary:leakcanary-android-no-op:1.5.1"] }
[project] build.gradle
apply from: rootProject.file('lianjia.gradle')