AndriodStudio实用小技巧

AndroidStudio实用小技巧

  • 1、优化编译速度
  • 2、分析编译过程
  • 3、manifest中使用编译脚本中定义的变量
  • 4、编译脚本中定义多模块使用的属性
  • 5、配置lint选项
  • 6、改变测试apk的manifest的设置
  • 7、改变测试app的默认编译类型
  • 8、gradle配置测试app的测试选项
  • 9、从工程代码中删除明码的私有签名信息
  • 10、简化app开发

1、优化编译速度

  • 实时更新编译工具:SDK tools、gradle plugin到最新
  • 创建不同的编译变体,代码同步使用单个变体同步
    单个变体同步代码方法:
    File > Settings > Experimental > Gradle (Android Studio > Preferences > Experimental > Gradle on a Mac) ,勾选“Only sync the active variant ”。
  • 避免编译不需要的资源文件
    例如只编译一种语言和一种屏密度的资源
android {
  ...
  productFlavors {
    dev {
      ...
      // The following configuration limits the "dev" flavor to using
      // English stringresources and xxhdpi screen-density resources.
      resConfigs "en", "xxhdpi"
    }
    ...
  }
}
  • debug版本尽量使用静态参数配置
    例如:
int MILLIS_IN_MINUTE = 1000 * 60
int minutesSinceEpoch = System.currentTimeMillis() / MILLIS_IN_MINUTE

android {
    ...
    defaultConfig {
        // Making either of these two values dynamic in the defaultConfig will
        // require a full APK build and reinstallation because the AndroidManifest.xml
        // must be updated.
        versionCode 1
        versionName "1.0"
        ...
    }

    // The defaultConfig values above are fixed, so your incremental builds don't
    // need to rebuild the manifest (and therefore the whole APK, slowing build times).
    // But for release builds, it's okay. So the following script iterates through
    // all the known variants, finds those that are "release" build types, and
    // changes those properties to something dynamic.
    applicationVariants.all { variant ->
        if (variant.buildType.name == "release") {
            variant.mergedFlavor.versionCode = minutesSinceEpoch;
            variant.mergedFlavor.versionName = minutesSinceEpoch + "-" + variant.flavorName;
        }
    }
}

若动态修改versionCode 1和versionName "1.0",会导致整个apk编译,延长编译时间。可以仅在release版本时,动态改变需要改变的值。

  • 使用静态依赖
    在添加依赖时,避免使用version和‘+’的组合出现,比如com.android.tools.build:gradle:2.+。使用version和‘+’方式的动态依赖,会导致不必要的更新和延长编译时间。
  • 使用离线模式编译(最新版本(4.0)工具未找到设置位置);命令行编译加上–offline
  • 拆分主程序部分作为libray,组织代码模块化,仅编译修改部分。打开多线程编译,详见“parallel project execution”
  • 使用AndroidStudio工具,收集分析(profile)编译耗时部分,如果是编译脚本耗时,可以在编译脚本中增加task且需要时执行。详见“official Gradle documentation”
  • 转换图片为webP。webP类似JPEG、PNG,都是有损的压缩图像,压缩比比JPEG、PNG高,AndroidStudio提供了转换方式 。详见convert your images to WebP
  • 如果不想把PNG转为webP,可以通过关闭每次编译时自动压缩图像。在 Android plugin 3.0.0版本及以上,debug版本默认关闭该功能。其他编译类型,若要关闭,使用以下代码
android {
    buildTypes {
        release {
            // Disables PNG crunching for the release build type.
            crunchPngs false
        }
    }

// If you're using an older version of the plugin, use the
// following:
//  aaptOptions {
//      cruncherEnabled false
//  }
}

由于编译类型和风味版本中没有该属性,因此编译release版本时,需要手动设置为true。

  • 打开编译缓存。Android plugin2.3.0及之上已经默认打开。
  • Use incremental annotation processors(这个有点复杂,不做说明)

2、分析编译过程

当一个大型工程,或者对编译脚本做了很多客制化,我们需要对编译过程做一个深入分析,分析出编译瓶颈在哪里。比如,如果编译过程花费大量时间在配置工程,我们应该把编译脚本中的自定义逻辑部分移出配置脚本;如果合并资源文件时间过久,需要转图像为webP或者关闭PNG压缩功能。

  • 使用AndroidStudio图像化分析编译过程,详见using the Build Analyzer
  • 使用命令统计分析编译过程,步骤如下
  1. 打开AndroidStudio中的终端
  2. 执行gradlew clean
  3. gradlew --profile --offline --rerun-tasks assembleFlavorDebug
    • –profile: 使用编译统计分析功能
    • –offline:关闭编译时gradle联网动态更新功能
    • –rerun-tasks:强制gradle返回除了最优的task之外的task信息
  4. 代码使用project方式查看,在project-root/build/reports/profile/目录下有prifile文件
  5. 右击文件使用浏览器打开
  6. 可选操作。在修改任何代码前,重复执行3步骤中的命令,命令去掉--rerun-tasks。再次编译,不需要更改的不会再编译,编译的log会标记为UP-TO-DATE,因此可以通过多次编译,对比哪个没有被标记为UP-TO-DATE。没有被标记的,可能存在优化的地方。例如,:app:processDevUniversalDebugManifest没有被标记为UP-TO-DATE,表明每次编译都存在动态更新manifest的情况。注意:有一些task,是需要每次编译执行的,如:app:checkDevDebugManifest。

通过以上操作,分析编译过程报告,找出可优化地方。有一些配置需求需要根据实际情况确定,比如一个有很多代码量的工程,使用code shrinking(代码压缩)去除不使用的代码,压缩apk大小;但是当代码量很少时,使用code shrinking可能会得不偿失。在低内存的编译设备上,增大org.gradle.jvmargs配置,会改善编译性能。
修改影响编译性能的地方后,再次测试,通过对比编译过程报告,确定修改有效。

3、manifest中使用编译脚本中定义的变量

如果想要通过build.gradle插入一个变量到AndroidManifest.xml中,我可以使用脚本的manifestPlaceholders属性。该属性携带一个键值对,如下示例

android {
    defaultConfig {
        manifestPlaceholders = [hostName:"www.example.com"]
    }
    ...
}

然后,在Manifest中使用属性


    {hostName}" ... />
    ...

默认情况下,编译工具提供了applicationId属性。这个属性的值为当前编译的编译变体的application ID,该值可以唯一的标识一个编译变体app。例如,一个编译变体想要一个唯一的标识用户intent action。可以如下方式实现

android {
    defaultConfig {
        applicationId "com.example.myapp"
    }
    productFlavors {
        free {
            applicationIdSuffix ".free"
        }
        pro {
            applicationIdSuffix ".pro"
        }
    }
}

在manifest中


    {applicationId}.TRANSMOGRIFY" />
    ...

编译free变体后,manifest中最终为


   android:name="com.example.myapp.free.TRANSMOGRIFY" />
    ...

4、编译脚本中定义多模块使用的属性

在一个包含多个模块的项目工程中,可能存在需要一个全局属性,所有模块都可以使用。可以在project的build.gradle中的ext块中添加额外属性(extra properties)

buildscript {...}
allprojects {...}

// This block encapsulates custom properties and makes them available to all
// modules in the project.
ext {
    // The following are only a few examples of the types of properties you can define.
    compileSdkVersion = 28
    // You can also use this to specify versions for dependencies. Having consistent
    // versions between modules can avoid behavior conflicts.
    supportLibVersion = "28.0.0"
    ...
}
...

在项目中的模块的build.gradle中使用

android {
  // Use the following syntax to access properties you define at the project level:
  // rootProject.ext.property_name
  compileSdkVersion rootProject.ext.compileSdkVersion
  ...
}
...
dependencies {
    implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
    ...
}

5、配置lint选项

在module中的build.gradle中通过lintOptions块配置lint选项。更多关于使用lint改善代码,详见Improve Your Code with Lint

android {
  ...
  lintOptions {
    // Turns off checks for the issue IDs you specify.
    disable 'TypographyFractions','TypographyQuotes'
    // Turns on checks for the issue IDs you specify. These checks are in
    // addition to the default lint checks.
    enable 'RtlHardcoded', 'RtlCompat', 'RtlEnabled'
    // To enable checks for only a subset of issue IDs and ignore all others,
    // list the issue IDs with the 'check' property instead. This property overrides
    // any issue IDs you enable or disable using the properties above.
    check 'NewApi', 'InlinedApi'
    // If set to true, turns off analysis progress reporting by lint.
    quiet true
    // if set to true (default), stops the build if errors are found.
    abortOnError false
    // if true, only report errors.
    ignoreWarnings true
  }
}
...

6、改变测试apk的manifest的设置

当编译一个测试apk时,编译脚本会自动生成AndroidManifest.xml,使用节点配置。我们可以自创建一个AndroidManifest.xml,通过资源集(sourceSet)配置指定,也可以在模块中的build.gradle中改变默认 的配置,示例如下

android {
  ...
  // Each product flavor you configure can override properties in the
  // defaultConfig block. To learn more, go to Configure Product Flavors.
  defaultConfig {
    ...
    // Specifies the application ID for the test APK.
    testApplicationId "com.test.foo"
    // Specifies the fully-qualified class name of the test instrumentation runner.
    testInstrumentationRunner "android.test.InstrumentationTestRunner"
    // If set to 'true', enables the instrumentation class to start and stop profiling.
    // If set to false (default), profiling occurs the entire time the instrumentation
    // class is running.
    testHandleProfiling true
    // If set to 'true', indicates that the Android system should run the instrumentation
    // class as a functional test. The default value is 'false'
    testFunctionalTest true
  }
}
...

注意: 测试app非debug版本的可提供完整功能的app,是使用Android提供的测试机制实现对app功能、性能等测试的app;下同。

7、改变测试app的默认编译类型

默认情况下,测试app的编译类型都是debug,如果新增了不同的编译类型,想要测试app编译的是新增的类型,可以通过如下方式修改

android {
    ...
    testBuildType "staging"
}

8、gradle配置测试app的测试选项

通过gradle配置测试选项,告诉系统如何运行测试app。在module中的build.gradle的testOptions块中配置

android {
  ...
  // Encapsulates options for running tests.
  testOptions {
    // Changes the directory where Gradle saves test reports. By default, Gradle saves test reports
    // in the path_to_your_project/module_name/build/outputs/reports/ directory.
    // '$rootDir' sets the path relative to the root directory of the current project.
    reportDir "$rootDir/test-reports"
    // Changes the directory where Gradle saves test results. By default, Gradle saves test results
    // in the path_to_your_project/module_name/build/outputs/test-results/ directory.
    // '$rootDir' sets the path relative to the root directory of the current project.
    resultsDir "$rootDir/test-results"
  }
}

设置单元测试app的测试选项,在testOptions.unitTests块中配置

android {
  ...
  testOptions {
    ...
    // Encapsulates options for local unit tests.
    unitTests {
      // By default, local unit tests throw an exception any time the code you are testing tries to access
      // Android platform APIs (unless you mock Android dependencies yourself or with a testing
      // framework like Mockito). However, you can enable the following property so that the test
      // returns either null or zero when accessing platform APIs, rather than throwing an exception.
      returnDefaultValues true

      // Encapsulates options for controlling how Gradle executes local unit tests. For a list
      // of all the options you can specify, read Gradle's reference documentation.
      all {
        // Sets JVM argument(s) for the test JVM(s).
        jvmArgs '-XX:MaxPermSize=256m'

        // You can also check the task name to apply options to only the tests you specify.
        if (it.name == 'testDebugUnitTest') {
          systemProperty 'debug', 'true'
        }
      }
    }
  }
}

9、从工程代码中删除明码的私有签名信息

  1. 在项目根目录下创建keystore.properties,文件中内容如
storePassword=myStorePassword
keyPassword=myKeyPassword
keyAlias=myKeyAlias
storeFile=myStoreFileLocation
  1. 在build.gradle中加载keystore.properties文件
// Creates a variable called keystorePropertiesFile, and initializes it to the
// keystore.properties file.
def keystorePropertiesFile = rootProject.file("keystore.properties")

// Initializes a new Properties() object called keystoreProperties.
def keystoreProperties = new Properties()

// Loads the keystore.properties file into the keystoreProperties object.
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

android {
  ...
}
...
  1. 签名信息赋值给签名属性
android {
  signingConfigs {
    config {
      keyAlias keystoreProperties['keyAlias']
      keyPassword keystoreProperties['keyPassword']
      storeFile file(keystoreProperties['storeFile'])
      storePassword keystoreProperties['storePassword']
    }
  }
  ...
}
...
  1. Sync Now工程

10、简化app开发

  1. 代码中共享自定字段和资源
    在编译阶段,gradle会自动生成BuildConfig文件,说明当前编译版本的一些信息。我们可以在脚本中使用buildConfigField在BuildConfig文件中添加自定义字段,在运行时app可以从BuildConfig中获取配置的值。同理,在编译脚本中可以使用resValue添加资源。示例如下
android {
  ...
  buildTypes {
    release {
      // These values are defined only for the release build, which
      // is typically used for full builds and continuous builds.
      buildConfigField("String", "BUILD_TIME", "\"${minutesSinceEpoch}\"")
      resValue("string", "build_time", "${minutesSinceEpoch}")
      ...
    }
    debug {
      // Use static values for incremental builds to ensure that
      // resource files and BuildConfig aren't rebuilt with each run.
      // If these rebuild dynamically, they can interfere with
      // Apply Changes as well as Gradle UP-TO-DATE checks.
      buildConfigField("String", "BUILD_TIME", "\"0\"")
      resValue("string", "build_time", "0")
    }
  }
}
...

resValue通过编译脚本修改strings.xml中的内容
2. manifest中使用编译脚本中定义的变量
详见前面章节

你可能感兴趣的:(AndroidStudio)