Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Level Up Your Android Build

Level Up Your Android Build

With the release of the new Gradle-based Build System, Android finally has a flexible and highly customizable build toolchain that allows for tweaks at all stages of development, from code generation to deployment on the Google Play Store.

We assume that by now the community is familiar with the basic capabilities of the Android build tool. This talk aims to provide some more advanced tricks and techniques of how to extend and customize your build employing the wide and growing range of Gradle plugins.

Talk given at Droidcon Paris 2015 & DevFest Berlin 2015
Novoda Berlin - Volker Leck & Florian Mierzejewski

Recording: https://www.youtube.com/watch?v=513e57e7jlo

Florian Mierzejewski

November 13, 2015
Tweet

More Decks by Florian Mierzejewski

Other Decks in Programming

Transcript

  1. Level Up Your
    Android Build
    Florian Mierzejewski, Volker Leck
    Novoda - Berlin

    View full-size slide

  2. Getting started

    View full-size slide

  3. app/build.gradle
    apply plugin: 'com.android.application'
    android {
    compileSdkVersion 23
    buildToolsVersion '23.0.1'
    defaultConfig {
    ...
    }
    buildTypes {
    release {
    ...
    }
    }
    }
    dependencies {
    ...
    }

    View full-size slide

  4. Relevant Build Files
    ├── app
    │ └── build.gradle
    ├── build.gradle
    ├── gradle
    │ └── wrapper
    │ ├── gradle-wrapper.jar
    │ └── gradle-wrapper.properties
    ├── gradle.properties
    ├── gradlew
    ├── gradlew.bat
    ├── local.properties
    └── settings.gradle

    View full-size slide

  5. Gradle wrapper
    $ ./gradlew tasks

    View full-size slide

  6. Gradle wrapper
    $ gw clean aD
    https://github.com/dougborg/gdub

    View full-size slide

  7. Using a Gradle plugin
    /build.gradle
    buildscript {
    repositories {
    jcenter()
    }
    dependencies {
    classpath 'com.android.tools.build:gradle:1.3.1'
    classpath 'com.novoda:gradle-android-command-plugin:1.5.0'
    }
    }
    app/build.gradle
    apply plugin: 'com.novoda.android-command'
    $ gw runDebug
    https://github.com/novoda/gradle-android-command-plugin

    View full-size slide

  8. Dependencies
    dependencies {
    compile 'com.google.guava:guava:18.0'
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:1.9.5'
    }

    View full-size slide

  9. Dependencies
    apply plugin: 'com.neenbedankt.android-apt'
    dependencies {
    apt 'com.google.dagger:dagger-compiler:2.0'
    compile 'com.google.dagger:dagger:2.0'
    compile 'com.google.guava:guava:18.0'
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:1.9.5'
    }
    https://bitbucket.org/hvisser/android-apt

    View full-size slide

  10. Dependencies
    apply plugin: 'com.neenbedankt.android-apt'
    dependencies {
    apt 'com.google.dagger:dagger-compiler:2.0'
    compile 'com.google.dagger:dagger:2.0'
    compile 'com.google.guava:guava:18.0'
    paidCompile project(':lvl')
    testCompile 'junit:junit:4.12'
    testCompile 'org.mockito:mockito-core:1.9.5'
    }

    View full-size slide

  11. Proguard
    buildTypes {
    release {
    minifyEnabled true
    shrinkResources true
    proguardFiles getDefaultProguardFile('proguard-android.txt'),
    'proguard.cfg'
    }
    }
    proguard.cfg
    #Dagger2
    -dontwarn dagger.shaded.auto.common.**
    -dontwarn dagger.internal.codegen.**
    -keepattributes Signature

    View full-size slide

  12. Archive Release
    ext.VERSION_NAME=1.0.0
    ...
    task archiveRelease(type: Copy) {
    from 'build/outputs/apk', 'build/outputs/'
    into "releases/${VERSION_CODE}"
    include('android-release.apk', 'mapping/**')
    rename ('android-release.apk',
    "${APP_NAME}_${BUILD_DATE}_${VERSION_NAME}_${VERSION_CODE}.apk")
    }

    View full-size slide

  13. First Success

    View full-size slide

  14. Scaling up
    ● re-use
    ● testing
    ● quality assurance
    ● continuous integration
    ● push-button release

    View full-size slide

  15. Re-use - Core project
    dependencies {
    compile project(':core')
    }
    app/build.gradle
    core/
    gradle init --type java-library
    include ':core'
    settings.gradle

    View full-size slide

  16. Re-use - Annotation processing
    apply plugin: 'net.ltgt.apt'
    dependencies {
    apt 'com.google.auto.value:auto-value:1.1'
    compile 'com.squareup.okhttp:okhttp:2.5.0'
    ...
    }
    core/build.gradle
    https://github.com/tbroyer/gradle-apt-plugin

    View full-size slide

  17. gradle init --type groovy-library
    Re-use - Script client
    scripts/
    dependencies {
    compile project(':core')
    }
    scripts/build.gradle
    include ':scripts'
    settings.gradle

    View full-size slide

  18. Re-use - Script client
    def operations = createUserOperations()
    (1..100).each {
    try {
    def profile = operations.readProfile(it)
    println profile
    } catch (e) {
    println e.message
    }
    }
    scripts/src/main/groovy/UserExplorer.groovy

    View full-size slide

  19. Re-use - Reducing duplication
    rootproject.children.each { project ->
    project.buildFileName = "${project.name}.gradle"
    }
    settings.gradle

    View full-size slide

  20. Testing - Integration Tests in Core
    sourceSets {
    integrationTest {
    java { srcDir 'src/integrationtest/java' }
    resources { srcDir 'src/integrationtest/resources' }
    compileClasspath += sourceSets.main.runtimeClasspath
    }
    }
    task integrationTest(type: Test) {
    testClassesDir =
    sourceSets.integrationTest.output.classesDir
    classpath += sourceSets.integrationTest.runtimeClasspath
    }

    View full-size slide

  21. https://github.com/ben-manes/gradle-versions-plugin
    Keeping Up To Date
    apply plugin: 'com.github.ben-manes.versions'
    $ gw dependencyUpdates
    The following dependencies have later milestone versions:
    - com.fasterxml.jackson.core:jackson-annotations [2.1.2 -> 2.6.0-
    rc1]
    - com.fasterxml.jackson.core:jackson-core [2.1.1 -> 2.6.0-rc1]
    - com.fasterxml.jackson.core:jackson-databind [2.1.2 -> 2.6.0-rc1]
    - com.github.kevinsawicki:http-request [4.0 -> 9.9]
    - joda-time:joda-time [2.3 -> 2.8]
    - org.mockito:mockito-core [1.9.0 -> 2.0.8-beta]
    Generated report file build/dependencyUpdates/report.txt

    View full-size slide

  22. Code Quality Tools
    apply plugin: 'checkstyle'
    task checkstyleMain( type: Checkstyle) {
    description 'Checks whether code complies with coding rules.'
    ignoreFailures !project.failFastOnError
    ...
    doLast {
    if (project.hasProperty('checkstyle.html')) {
    ant.xslt(in: reports.xml.destination,
    style: file("$rulesDir/checkstyle/noframes-sorted.
    xsl"),
    out: new File(reports.xml.destination.parent,
    name - 'checkstyle' + '.html'))
    }
    }
    }

    View full-size slide

  23. Code Quality Tools
    apply plugin: 'checkstyle'
    task checkstyleMain(type: Checkstyle) {
    description 'Checks whether code complies with coding rules.'
    ignoreFailures !project. failFastOnError
    ...
    doLast {
    if (project.hasProperty(' checkstyle.html')) {
    ant.xslt(in: reports.xml.destination,
    style: file("$rulesDir/checkstyle/noframes-sorted.
    xsl"),
    out: new File(reports.xml.destination.parent,
    name - 'checkstyle' + '.html'))
    }
    }
    }
    $ gw checkstyleMain -PfailFastOnError -Pcheckstyle.html

    View full-size slide

  24. Code Quality Tools
    apply plugin: 'checkstyle'
    task checkstyleMain(type: Checkstyle) {
    description 'Checks whether code complies with coding rules.'
    ignoreFailures !project.failFastOnError
    ...
    doLast {
    if (project.hasProperty('checkstyle.html')) {
    ant.xslt(in: reports.xml.destination,
    style: file("$rulesDir/checkstyle/noframes-sorted.
    xsl"),
    out: new File(reports.xml.destination.parent,
    name - 'checkstyle' + '.html'))
    }
    }
    }

    View full-size slide

  25. Continuous Integration
    System.getenv("...")
    Jenkins
    BUILD_NUMBER

    View full-size slide

  26. Updating the Android SDK
    apply plugin: 'android-sdk-manager'
    $ gw assembleDebug
    Android SDK not found. Downloading...
    > Configuring > 1/5 projects > :android
    SDK extracted at '/Users/leck/.android-sdk'. Writing to local.
    properties.
    Build tools 21.1.2 missing. Downloading...
    > Configuring > 2/5 projects
    Platform tools missing. Downloading...
    Compilation API android-21 missing. Downloading…
    ...
    https://github.com/JakeWharton/sdk-manager-plugin

    View full-size slide

  27. Releasing from CI
    ● update Version
    ● commit changes
    ● upload to Stores

    View full-size slide

  28. Release from CI - Versioning
    def versionFile = file('version.properties')
    Version version = Version.read(versionFile)
    task incrementVersion << {
    Version.increment(versionFile)
    }

    View full-size slide

  29. Release from CI - Versioning
    def versionFile = file('version.properties')
    Version version = Version.read(versionFile)
    task incrementVersion << {
    Version.increment(versionFile)
    }
    buildSrc
    └── src
    └── main
    └── groovy
    └── Version.groovy

    View full-size slide

  30. Release from CI - git
    buildscript {
    dependencies {
    classpath 'org.ajoberstar:grgit:1.1.0'
    }
    }
    ext {
    git = org.ajoberstar.grgit.Grgit.open file('.')
    }
    git.with {
    add 'version.properties'
    commit(message: "Bumping version to $VERSION_CODE")
    push()
    }
    https://github.com/ajoberstar/grgit

    View full-size slide

  31. Publishing - Github
    def release = repository.createRelease(releaseTag)
    .name(releaseName)
    .body(releaseBody)
    .commitish(commitish)
    .draft(draft)
    .create()
    assets.each { asset ->
    release.uploadAsset(asset, 'application/octet-stream')
    }
    buildSrc/src/main/groovy/ReleaseToGithub.groovy
    extends org.gradle.api.DefaultTask
    https://github.com/kohsuke/github-api

    View full-size slide

  32. Publishing - Github
    applicationVariants.all { variant ->
    project.task("releaseGithub$variant.name", type: ReleaseToGithub) {
    releaseTag = "${new Date().format('YY.MM.dd')}"
    releaseName = buildReleaseName(variant)
    releaseBody = buildReleaseBody(variant)
    assets = files(apkFile, mappingFile)
    commitish = project.latestCommitHash
    ...
    }
    }
    app/build.gradle

    View full-size slide

  33. Publishing - Google Play
    app/build.gradle
    apply plugin: 'com.github.triplet.play'
    adds Tasks:
    bootstrapReleasePlayResources
    publishApkRelease
    publishListingRelease
    publishRelease
    https://github.com/Triple-T/gradle-play-publisher

    View full-size slide

  34. Tips & Tricks

    View full-size slide

  35. Shared methods
    build.gradle
    File rules(String relativeFilePath) {
    file("shared/rules/$relativeFilePath")
    }
    app/build.gradle
    findbugs {
    excludeFilter rules('findbugs/exclude.xml')
    ...
    }

    View full-size slide

  36. Shared dependencies
    dependencies.gradle
    ext {
    guava_lib = 'com.google.guava:guava:18.0'
    ...
    testing_libs = [junit_lib, assertJ_lib]
    }
    build.gradle
    apply from: 'dependencies.gradle'
    dependencies {
    compile guava_lib
    ...
    testCompile testing_libs
    }

    View full-size slide

  37. Shared buildConfig - before
    android {
    ...
    buildTypes {
    debug {
    buildConfigField 'String', 'TAG', '"DEBUG_TAG"'
    ...
    }
    release {
    buildConfigField 'String', 'TAG', '"RELEASE_TAG"'
    ...
    }
    }
    }
    app/build.gradle

    View full-size slide

  38. Shared buildConfig - after
    android {
    ...
    buildTypes {
    debug {
    tag 'DEBUG_TAG'
    ...
    }
    release {
    tag 'RELEASE_TAG'
    ...
    }
    }
    }
    app/build.gradle

    View full-size slide

  39. Shared buildConfig - how
    ext.tag = { tag ->
    buildConfigField 'String', 'TAG', '"'+ tag +'"'
    }
    android {
    buildTypes.all {
    apply from: 'buildconfig.gradle', to: delegate
    }
    ...
    }
    buildconfig.gradle
    build.gradle

    View full-size slide

  40. Auto Login
    task autoLogin(type: com.novoda.gradle.command.Input) {
    script {
    text '[email protected]'
    down()
    text 'awesomeSuperStrongPwd92'
    enter()
    }
    }

    View full-size slide

  41. Sync images
    task syncPhotos(type: com.novoda.gradle.command.Files) {
    deviceId {
    def moto = android.command.devices().find {
    it.brand() == 'motorola'
    }
    moto.id
    }
    script {
    def backupDir = mkdir 'motoPhoto'
    list('/sdcard/DCIM/Camera/').findAll { image ->
    !new File(backupDir.path, image.name).exists()
    }.each { image ->
    pull image.path + image.name, backupDir.path
    }
    }
    }

    View full-size slide

  42. Library Shrinking
    dependencies {
    classpath 'net.sf.proguard:proguard-gradle:4.11'
    }
    ...
    task shrinkGuava(type: proguard.gradle.ProGuardTask) {
    injars configurations.injar.files
    libraryjars configurations.libjar.files
    outjars file("build/libs/guava_${guavaVersion}_base.jar")
    configuration 'guava.proguard'
    }
    guava.proguard:
    -dontoptimize
    -dontobfuscate
    -keep public class com.google.common.base.** { public *; }

    View full-size slide

  43. War story - Facebook
    keytool -exportcert -alias -keystore \
    | openssl sha1 -binary \
    | openssl base64
    $ ga0RGNYHvNM5d0SLGQfpQWAPGJ8=
    Facebook docs on generating key hashes:

    View full-size slide

  44. War story - Facebook
    def message =
    'keytool error: java.io.IOException: Keystore was tampered with, or
    password was incorrect\\n'
    def digest = MessageDigest.getInstance('SHA-1')
    def digestBytes = digest.digest(message.bytes)
    println digestBytes.encodeBase64()
    $ ga0RGNYHvNM5d0SLGQfpQWAPGJ8=

    View full-size slide

  45. War story - Facebook
    def signingMeta = com.android...tasks.SigningReportTask.metaClass
    MetaMethod metaFinger = signingMeta
    .getStaticMetaMethod('getFingerprint',
    java.security.cert.Certificate, String)
    signingMeta.static.getFingerprint = { Certificate cert, String alg ->
    def base64 = MessageDigest.getInstance(alg)
    .digest(cert.encoded).encodeBase64()
    metaFinger.invoke(delegate, cert, hashAlgorithm) +
    "\n$hashAlgorithm (base64): $base64"
    }
    $ gw app:signingReport

    View full-size slide

  46. Groovy on Android
    buildscript {
    repositories {
    jcenter()
    }
    dependencies {
    classpath 'com.android.tools.build:gradle:1.1.0'
    classpath 'org.codehaus.groovy:gradle-groovy-android-plugin:0.3.6'
    }
    }
    apply plugin: 'groovyx.grooid.groovy-android'
    dependencies {
    compile 'org.codehaus.groovy:groovy:2.4.3:grooid'
    }

    View full-size slide

  47. Udacity course

    View full-size slide

  48. Volker Leck
    devisnik / florianmski
    +VolkerLeck / +FlorianMierzejewski
    devisnik / florianmski
    Merci !
    Florian Mierzejewski

    View full-size slide

  49. TDTD - Tester-Driven Talk Development
    Thanks to the folks from
    Android Stammtisch Berlin
    for serving as Testers for a lot of the
    presented material.
    Upcoming book:
    Growing Subject-Oriented Talks,
    Guided by Testers

    View full-size slide

  50. Resources (in order of appearance)
    Gradle
    https://github.com/gradle/gradle
    Android plugin for Gradle
    https://developer.android.com/tools/building/plugin-for-gradle.html
    gdub Tool
    https://github.com/dougborg/gdub
    Command Plugin
    https://github.com/novoda/gradle-android-command-plugin
    Gradle Plugin Portal
    http://plugins.gradle.org

    View full-size slide

  51. Resources (in order of appearance)
    apt Plugin
    https://bitbucket.org/hvisser/android-apt
    Proguard Annotation Squadleader
    https://bitbucket.org/littlerobots/squadleader
    Proguard Snippets
    https://github.com/krschultz/android-proguard-snippets
    Gradle Udacity Course
    https://www.udacity.com/course/gradle-for-android-and-java--ud867

    View full-size slide

  52. Resources (in order of appearance)
    Versions Plugin
    https://github.com/ben-manes/gradle-versions-plugin
    Code Analysis Plugin
    https://github.com/dlabs/soter
    Groovy library for git
    https://github.com/ajoberstar/grgit
    SDK Manager Plugin
    https://github.com/JakeWharton/sdk-manager-plugin

    View full-size slide

  53. Resources (in order of appearance)
    Shrink Guava Script Snippet
    https://gist.github.com/devisnik/e54ea0a629adc82bcfa0
    DexCount
    https://github.com/KeepSafe/dexcount-gradle-plugin
    Sqlite Analyzer Plugin
    https://github.com/novoda/sqlite-analyzer
    Groovy Android Plugin
    https://github.com/groovy/groovy-android-gradle-plugin

    View full-size slide

  54. Images
    https://upload.wikimedia.org/wikipedia/commons/d/d9/Florida_Box_Turtle_Digon3a.jpg
    http://commons.wikimedia.org/wiki/File:Perspective-Button-Stop-icon.png
    http://commons.wikimedia.org/wiki/File:Up-1.svg
    http://commons.wikimedia.org/wiki/File:Emblem-money.svg
    http://commons.wikimedia.org/wiki/File:Hipsterale.jpg
    https://commons.wikimedia.org/wiki/File:Seal_performing_tricks.jpg

    View full-size slide