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实战总结

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