gradle实战总结

作者: 砺雪凝霜 | 来源:发表于2018-10-13 18:29 被阅读18次

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参数的方式执行脚本
task  getGitVersion << {
  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的值分别为http://www.google.comhttp://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')

相关文章

  • gradle实战总结

    1、获取properties配置文件的值 执行./gradlew hello 命令,运行结果如下: Task :h...

  • Gradle 打包

    Gradle实战:Android多渠道打包方案汇总

  • Gradle学习笔记

    本文总结内容来自DoctorQ整理Gradle学习文章,传送门。这个是实战文,先敲代码,再学理论。详细介绍系列电子...

  • Gradle实战

    通过下面几个案例来演示下Gradle的强大之处,因为在一般的开发中我们只是简单的配置下build.gradle文件...

  • Gradle 安装 & 命令行选项

    系统学习 Gradle ,不局限于Android ,学习《实战Gradle》并简要记录 1 安装 直接参考 Gra...

  • Spring Boot 日志

    Spring Boot 日志 《Spring Boot 开发实战》—— 基于 Gradle + Kotlin的企业...

  • android 书籍

    深入理解 Android 内核设计思想 实战Gradle kotlin实战 编程珠玑 编程之美 java核心技术卷...

  • 新书上架:《Spring Boot 开发实战》(基于 Kotli

    新书上架:《Spring Boot 开发实战》 — 基于 Kotlin + Gradle + Spring Boo...

  • gradle+maven+springboot实战

    gradle+maven+springboot实战 http://www.itjoin.org/course/de...

  • Spring Boot 缓存

    Spring Boot缓存 《Spring Boot 实战开发》—— 基于 Gradle + Kotlin的企业级...

网友评论

    本文标题:gradle实战总结

    本文链接:https://www.haomeiwen.com/subject/aqtaaftx.html