Slide 1

Slide 1 text

Configuring Android Project Dmytro Danylyk Atlassian

Slide 2

Slide 2 text

gitignore To quickly generate .gitignore files I recommend you to use gitignore.io site

Slide 3

Slide 3 text

gitignore 1. Type in necessary keywords like — Android, Intellij 2. Click Generate button

Slide 4

Slide 4 text

gitignore # Built application files *.apk *.ap_ # Files for the ART/Dalvik VM *.dex # Generated files bin/ gen/ out/ ...

Slide 5

Slide 5 text

tools folder If you have some 3rd party scripts, rulesets or other files don’t just drop them in root directory — it will create a mess.

Slide 6

Slide 6 text

tools folder Try to create a folder and put all those files inside this folder.

Slide 7

Slide 7 text

flavors Flavors are used to create builds with different set-up. In most cases two flavors are enough  —  dev and prod.

Slide 8

Slide 8 text

flavors Different set-up usually means usage of separate: - applicationId - versionCode / versionName - server endpoints - google services keys - ...

Slide 9

Slide 9 text

flavors productFlavors { dev { signingConfig signingConfigs.debug versionCode 1 versionName "1.0-dev" applicationId "com.dd.template.dev" } prod { signingConfig signingConfigs.release versionCode gitVersionCode versionName gitVersionName applicationId "com.dd.template" } }

Slide 10

Slide 10 text

keystore A keystore is a binary file that contains one or more private keys used to sign your application.

Slide 11

Slide 11 text

keystore When running or debugging your project from the IDE, Android Studio automatically signs your APK with a debug certificate generated by the Android SDK tools.

Slide 12

Slide 12 text

keystore Issues when using local debug keystore: - expiration date of 365 days

Slide 13

Slide 13 text

keystore Issues when using local debug keystore: - expiration date of 365 days - installing application from multiple computers require uninstalling first

Slide 14

Slide 14 text

keystore Issues when using local debug keystore: - expiration date of 365 days - installing application from multiple computers require uninstalling first - google services require keystore SHA-1 fingerprint

Slide 15

Slide 15 text

keystore Generate debug keystore and commit it to version control system. signingConfigs { debug { keyAlias 'androiddebugkey' keyPassword 'android' storePassword 'android' storeFile file('../keystore/debug.keystore') } release { ... } }

Slide 16

Slide 16 text

proguard On Android proguard is used for three things: - shrink unused code  —  helps you to survive the 64k limit

Slide 17

Slide 17 text

proguard On Android proguard is used for three things: - shrink unused code  —  helps you to survive the 64k limit - optimize code & APK

Slide 18

Slide 18 text

proguard On Android proguard is used for three things: - shrink unused code  —  helps you to survive the 64k limit - optimize code & APK - obfuscate code  —  makes your APK difficult to reverse engineer

Slide 19

Slide 19 text

proguard Obfuscation and code optimization significantly increase compilation time and makes debugging harder.

Slide 20

Slide 20 text

proguard Use different proguard rules for release and debug build: - rules-proguard.pro - rules-proguard-debug.pro

Slide 21

Slide 21 text

proguard buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), "$project.rootDir/tools/rules-proguard.pro" } debug { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), "$project.rootDir/tools/rules-proguard-debug.pro" } }

Slide 22

Slide 22 text

proguard buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), "$project.rootDir/tools/rules-proguard.pro" } debug { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), "$project.rootDir/tools/rules-proguard-debug.pro" } }

Slide 23

Slide 23 text

proguard buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), "$project.rootDir/tools/rules-proguard.pro" } debug { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), "$project.rootDir/tools/rules-proguard-debug.pro" } }

Slide 24

Slide 24 text

proguard buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), "$project.rootDir/tools/rules-proguard.pro" } debug { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), "$project.rootDir/tools/rules-proguard-debug.pro" } }

Slide 25

Slide 25 text

proguard # rules-proguard-debug.pro -ignorewarnings -dontobfuscate -dontoptimize

Slide 26

Slide 26 text

proguard # rules-proguard.pro # Remove logs -assumenosideeffects class android.util.Log { public static boolean isLoggable(java.lang.String, int); public static int v(...); public static int i(...); public static int w(...); public static int d(...); public static int e(...); } # Proguard configurations for common Android libraries: # https://github.com/krschultz/android-proguard-snippets

Slide 27

Slide 27 text

Configure proguard on early stages, it will be a lot easier. android dev tip

Slide 28

Slide 28 text

strict mode Android StrictMode helps you to detect different kinds of problems: - closable object is not closed - file reading / network requests performed on main thread - uri exposed - ...

Slide 29

Slide 29 text

strict mode Turn on StrictMode only for debug build: if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectAll() .penaltyLog() .build()); }

Slide 30

Slide 30 text

strict mode Turn on StrictMode only for debug build: if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectAll() .penaltyLog() .build()); }

Slide 31

Slide 31 text

strict mode Turn on StrictMode only for debug build: if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectAll() .penaltyLog() .build()); }

Slide 32

Slide 32 text

StrictMode: A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks. java.lang.Throwable: Explicit termination method 'close' not called at dalvik.system.CloseGuard.open(CloseGuard.java:184) at android.database.CursorWindow.(CursorWindow.java:111) at android.database.clearOrCreateWindow(AbstractCursor.java:198) at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:139) at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133) at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:197) at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:237) at com.dd.template.MainActivity.onCreate(MainActivity.java:124) strict mode Example of log when you forgot to close SQLiteCursor:

Slide 33

Slide 33 text

StrictMode: A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks. java.lang.Throwable: Explicit termination method 'close' not called at dalvik.system.CloseGuard.open(CloseGuard.java:184) at android.database.CursorWindow.(CursorWindow.java:111) at android.database.clearOrCreateWindow(AbstractCursor.java:198) at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:139) at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133) at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:197) at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:237) at com.dd.template.MainActivity.onCreate(MainActivity.java:124) strict mode Example of log when you forgot to close SQLiteCursor:

Slide 34

Slide 34 text

defaultConfig { versionCode 1 versionName "1.0" } Version Name & Code Developers usually hard code values for android versionName & versionCode.

Slide 35

Slide 35 text

Version Name & Code Issues with usage of hard coded values for android versionName & versionCode. - you never know which commit represent specific version

Slide 36

Slide 36 text

Version Name & Code Issues with usage of hard coded values for android versionName & versionCode. - you never know which commit represent specific version - whenever you increment versionCode and change versionName you have to modify build.gradle file

Slide 37

Slide 37 text

Version Name & Code v0.1 v0.2 v0.3 It’s a common practice to use git tags to indicate release of new version.

Slide 38

Slide 38 text

Version Name For versionName we can use git describe command. a. The command finds the most recent tag that is reachable from a commit. b. If the tag points to the commit, then only the tag is shown. c. Otherwise, it suffixes the tag name with the number of additional commits on top of the tagged object and the abbreviated object name of the most recent commit.

Slide 39

Slide 39 text

Version Name For versionName we can use git describe command. a. The command finds the most recent tag that is reachable from a commit. b. If the tag points to the commit, then only the tag is shown. c. Otherwise, it suffixes the tag name with the number of additional commits on top of the tagged object and the abbreviated object name of the most recent commit.

Slide 40

Slide 40 text

Version Name For versionName we can use git describe command. a. The command finds the most recent tag that is reachable from a commit. b. If the tag points to the commit, then only the tag is shown. c. Otherwise, it suffixes the tag name with the number of additional commits on top of the tagged object and the abbreviated object name of the most recent commit.

Slide 41

Slide 41 text

Version Name For versionName we can use git describe command. a. The command finds the most recent tag that is reachable from a commit. b. If the tag points to the commit, then only the tag is shown. c. Otherwise, it suffixes the tag name with the number of additional commits on top of the tagged object and the abbreviated object name of the most recent commit.

Slide 42

Slide 42 text

Version Name 1.0 65a9e77 dca226a tag commit 1 commit 2 git describe

Slide 43

Slide 43 text

Version Name > 1.0-2-gdca226a 1.0 65a9e77 tag commit 1 commit 2 git describe dca226a

Slide 44

Slide 44 text

Version Name 1.0 65a9e77 dca226a tag commit 1 commit 2 1.0-2-gdca226a

Slide 45

Slide 45 text

Version Name 1.0-2-gdca226a Tag name. 1.0 65a9e77 dca226a tag commit 1 commit 2

Slide 46

Slide 46 text

Version Name 1.0-2-gdca226a Number of commits made after tagged commit. tag commit 1 commit 2 1.0 65a9e77 dca226a

Slide 47

Slide 47 text

Version Name 1.0-2-gdca226a Short form of commit hash with “g” prefix (stands for git). tag commit 1 commit 2 1.0 65a9e77 dca226a

Slide 48

Slide 48 text

Version Name Using git commit hash “1.0-2-gdca226a” you can easily find out from which specific commit build was made.

Slide 49

Slide 49 text

Version Name 65a9e77 dca226a 1.0 commit 1 commit 2 tag git describe

Slide 50

Slide 50 text

Version Name 65a9e77 dca226a 1.0 commit 1 commit 2 tag git describe > 1.0

Slide 51

Slide 51 text

Version Code For versionCode we can use total number of tags. Because every git tag indicate some version, versionCode for next version will be always greater than previous.

Slide 52

Slide 52 text

Version Code 1.0 2.0 3.0 tag tag tag versionCode = 3

Slide 53

Slide 53 text

Version Code Are we going to create git tag for every intermediate version? - No, for intermediate builds we can use timestamp of HEAD commit

Slide 54

Slide 54 text

Version Code Are we going to create git tag for every intermediate version? - No, for intermediate builds we can use timestamp of HEAD commit

Slide 55

Slide 55 text

Version Code 65a9e77 dca226a HEAD commit 1 commit 2 commit 3 versionCode = 1484407970

Slide 56

Slide 56 text

Version Code 65a9e77 dca226a HEAD commit 1 commit 2 commit 3 versionCode = 1484407970 > Sat Jan 14 2017 15:32:50 UTC

Slide 57

Slide 57 text

Groovy way to use Git To work with git we are going to use great library called grgit.

Slide 58

Slide 58 text

Groovy way to use Git # script-git-version.gradle buildscript { repositories { jcenter() } dependencies { classpath 'org.ajoberstar:grgit:1.5.0' } } import org.ajoberstar.grgit.Grgit ...

Slide 59

Slide 59 text

Groovy way to use Git # script-git-version.gradle ext { git = Grgit.open(currentDir: projectDir) gitVersionName = git.describe() gitVersionCode = git.tag.list().size() gitVersionCodeTime = git.head().time } task printVersion() { println("Version Name: $gitVersionName") println("Version Code: $gitVersionCode") println("Version Code Time: $gitVersionCodeTime") }

Slide 60

Slide 60 text

Groovy way to use Git # script-git-version.gradle ext { git = Grgit.open(currentDir: projectDir) gitVersionName = git.describe() gitVersionCode = git.tag.list().size() gitVersionCodeTime = git.head().time } task printVersion() { println("Version Name: $gitVersionName") println("Version Code: $gitVersionCode") println("Version Code Time: $gitVersionCodeTime") }

Slide 61

Slide 61 text

Groovy way to use Git >./gradlew printVersion Version Name: 1.0-6-gd2bef6b Version Code: 1 Version Code Time: 1486290177

Slide 62

Slide 62 text

Version Name & Code # build.gradle apply plugin: 'com.android.application' apply from: "$project.rootDir/tools/script-git-version.gradle" ...

Slide 63

Slide 63 text

Version Name & Code # build.gradle productFlavors { dev { versionCode gitVersionCodeTime versionName gitVersionName } prod { versionCode gitVersionCode versionName gitVersionName } }

Slide 64

Slide 64 text

Version Name & Code Run your project and verify app version.

Slide 65

Slide 65 text

Static Code Analysis Tools Static code analysis tool - is source code analyzer. It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and so forth.

Slide 66

Slide 66 text

Static Code Analysis Tools Static code analysis tool - is source code analyzer. It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, and so forth. - Lint - Findbugs - PMD

Slide 67

Slide 67 text

Lint # script-lint.gradle android { lintOptions { lintConfig file("$project.rootDir/tools/rules-lint.xml") htmlOutput file("$project.buildDir/outputs/lint/lint.html") warningsAsErrors true xmlReport false } }

Slide 68

Slide 68 text

Lint # build.gradle apply plugin: 'com.android.application' apply from: "$project.rootDir/tools/script-lint.gradle" ...

Slide 69

Slide 69 text

Lint >./gradlew lint Execution failed for task ':app:lint. Lint found errors in the project; aborting build. Wrote HTML report to: template/app/build/outputs/lint/lint.html

Slide 70

Slide 70 text

Lint # template/app/build/outputs/lint/lint.html ... Priority: 3 / 10 Category: Security Severity: Error Explanation: AllowBackup/FullBackupContent Problems. The allowBackup attribute determines if an application's data can be backed up and restored. It is documented at http://developer.android.com/reference/android/R.attr.html#allowBackup ...

Slide 71

Slide 71 text

Lint # tools/rules-lint.xml

Slide 72

Slide 72 text

Lint >./gradlew lint BUILD SUCCESSFUL Ran lint on variant release: 0 issues found Wrote HTML report to: template/app/build/outputs/lint/lint.html

Slide 73

Slide 73 text

Findbugs # script-findbugs.gradle apply plugin: 'findbugs' task findbugs(type: FindBugs) { excludeFilter = file("../tools/rules-findbugs.xml") classes = fileTree("$project.buildDir/intermediates/classes/") source = fileTree('src/main/java/') classpath = files() reports { xml.enabled = false html.enabled = true html.destination = "$project.buildDir/outputs/findbugs/findbugs.html" } }

Slide 74

Slide 74 text

Findbugs # script-findbugs.gradle apply plugin: 'findbugs' task findbugs(type: FindBugs) { excludeFilter = file("../tools/rules-findbugs.xml") classes = fileTree("$project.buildDir/intermediates/classes/") source = fileTree('src/main/java/') classpath = files() reports { xml.enabled = false html.enabled = true html.destination = "$project.buildDir/outputs/findbugs/findbugs.html" } }

Slide 75

Slide 75 text

Findbugs # script-findbugs.gradle apply plugin: 'findbugs' task findbugs(type: FindBugs) { excludeFilter = file("../tools/rules-findbugs.xml") classes = fileTree("$project.buildDir/intermediates/classes/") source = fileTree('src/main/java/') classpath = files() reports { xml.enabled = false html.enabled = true html.destination = "$project.buildDir/outputs/findbugs/findbugs.html" } }

Slide 76

Slide 76 text

Findbugs # script-findbugs.gradle apply plugin: 'findbugs' task findbugs(type: FindBugs) { excludeFilter = file("../tools/rules-findbugs.xml") classes = fileTree("$project.buildDir/intermediates/classes/") source = fileTree('src/main/java/') classpath = files() reports { xml.enabled = false html.enabled = true html.destination = "$project.buildDir/outputs/findbugs/findbugs.html" } }

Slide 77

Slide 77 text

Findbugs # build.gradle apply plugin: 'com.android.application' apply from: "$project.rootDir/tools/script-findbugs.gradle" ...

Slide 78

Slide 78 text

Findbugs # MainActivity.class ... private void someMethod(int variable) { switch (variable) { case 1: System.out.println("1"); case 2: System.out.println("2"); } } ...

Slide 79

Slide 79 text

Findbugs >./gradlew findbugs Execution failed for task ':app:findbugs'. FindBugs rule violations were found. See the report at: template/app/build/outputs/findbugs/findbugs.html

Slide 80

Slide 80 text

Findbugs # template/app/build/outputs/findbugs/findbugs.html ... SF_SWITCH_NO_DEFAULT Switch statement found in MainActivity.someMethod(int) where one case falls through to the next case SF_SWITCH_FALLTHROUGH Switch statement found in MainActivity.someMethod(int) where default case is missing ...

Slide 81

Slide 81 text

Findbugs # rules-findbugs.xml

Slide 82

Slide 82 text

Findbugs >./gradlew findbugs BUILD SUCCESSFUL

Slide 83

Slide 83 text

PMD # script-pmd.gradle apply plugin: 'pmd' task pmd(type: Pmd) { ruleSetFiles = files("$project.rootDir/tools/rules-pmd.xml") source = fileTree('src/main/java/') reports { xml.enabled = false html.enabled = true html.destination = "$project.buildDir/outputs/pmd/pmd.html" } }

Slide 84

Slide 84 text

PMD # script-pmd.gradle apply plugin: 'pmd' task pmd(type: Pmd) { ruleSetFiles = files("$project.rootDir/tools/rules-pmd.xml") source = fileTree('src/main/java/') reports { xml.enabled = false html.enabled = true html.destination = "$project.buildDir/outputs/pmd/pmd.html" } }

Slide 85

Slide 85 text

PMD # script-pmd.gradle apply plugin: 'pmd' task pmd(type: Pmd) { ruleSetFiles = files("$project.rootDir/tools/rules-pmd.xml") source = fileTree('src/main/java/') reports { xml.enabled = false html.enabled = true html.destination = "$project.buildDir/outputs/pmd/pmd.html" } }

Slide 86

Slide 86 text

PMD # script-pmd.gradle apply plugin: 'pmd' task pmd(type: Pmd) { ruleSetFiles = files("$project.rootDir/tools/rules-pmd.xml") source = fileTree('src/main/java/') reports { xml.enabled = false html.enabled = true html.destination = "$project.buildDir/outputs/pmd/pmd.html" } }

Slide 87

Slide 87 text

PMD # build.gradle apply plugin: 'com.android.application' apply from: "$project.rootDir/tools/script-pmd.gradle" ...

Slide 88

Slide 88 text

PMD # MainActivity.class ... private void someMethod(int a, int b, int c, int d) { if (a > b) { if (b > c) { if (c > d) { if (d > a) { // some logic } } } } }

Slide 89

Slide 89 text

PMD >./gradlew pmd Execution failed for task ':app:pmd. 1 PMD rule violations were found. See the report at: template/app/build/outputs/pmd/pmd.html

Slide 90

Slide 90 text

PMD # template/app/build/outputs/pmd/pmd.html ... File MainActivity.java ... Line 31 Problem Deeply nested if..then statements are hard to read

Slide 91

Slide 91 text

PMD # rules-pmd.xml

Slide 92

Slide 92 text

PMD >./gradlew pmd BUILD SUCCESSFUL

Slide 93

Slide 93 text

Code Coverage Code coverage basically tests how much of your code is covered under tests. So, if you have 90% code coverage than it means there is 10% of code that is not covered under tests.

Slide 94

Slide 94 text

Code Coverage # script-java-code-coverage.gradle apply plugin: 'jacoco' task javaCodeCoverage(type: JacocoReport, dependsOn: 'testDebugUnitTest') { reports { xml.enabled = false html.enabled = true html.destination "$project.buildDir/outputs/jacoco/" } ... }

Slide 95

Slide 95 text

Code Coverage # script-java-code-coverage.gradle def classFileTree = fileTree( dir: "${buildDir}/intermediates/classes/", excludes: ['**/R.class', '**/R$*.class'], includes: ['**/*Presenter*.class'] ) classDirectories = files([classFileTree]) sourceDirectories = files("${project.projectDir}/android/src/main/java") executionData = files("${buildDir}/jacoco/testDevDebugUnitTest.exec")

Slide 96

Slide 96 text

Code Coverage # script-java-code-coverage.gradle def classFileTree = fileTree( dir: "${buildDir}/intermediates/classes/", excludes: ['**/R.class', '**/R$*.class'], includes: ['**/*Presenter*.class'] ) classDirectories = files([classFileTree]) sourceDirectories = files("${project.projectDir}/android/src/main/java") executionData = files("${buildDir}/jacoco/testDevDebugUnitTest.exec")

Slide 97

Slide 97 text

Code Coverage # build.gradle apply plugin: 'com.android.application' apply from: "$project.rootDir/tools/script-java-code-coverage.gradle" ...

Slide 98

Slide 98 text

Code Coverage >./gradlew javaCodeCoverage BUILD SUCCESSFUL

Slide 99

Slide 99 text

Code Coverage # template/app/build/outputs/jacoco/jacoco.html ... Element MainPresenter(MainView) onExitClicked() ... Lines 3 2 Coverage 100% 100%

Slide 100

Slide 100 text

Continuous Integration Continuous Integration (CI) is the process of automating the build and testing of code every time a team member commits changes to version control.

Slide 101

Slide 101 text

Continuous Integration Continuous Integration (CI) is the process of automating the build and testing of code every time a team member commits changes to version control.

Slide 102

Slide 102 text

GitHub repository setup Webhooks allow external services to be notified when certain events happen within your repository. (push, pull-request, etc.) Protect branches to disable force pushing, prevent branches from being deleted, and optionally require status checks before merging.

Slide 103

Slide 103 text

Continuous Integration Build Triggered Workflow is Executed App Ready

Slide 104

Slide 104 text

Continuous Integration Trigger define when to build what.

Slide 105

Slide 105 text

Continuous Integration

Slide 106

Slide 106 text

Continuous Integration Workflow defines the steps of a Build process. You can customize it the way you want to suite your build process.

Slide 107

Slide 107 text

Continuous Integration

Slide 108

Slide 108 text

Continuous Integration

Slide 109

Slide 109 text

Continuous Integration

Slide 110

Slide 110 text

Continuous Integration # build logs +---+---------------------------------------------------+----------+ | ✓ | git-clone@3.4.1 | 4.84 sec | +---+---------------------------------------------------+----------+ | ✓ | assembleProdRelease | 118 sec | +---+---------------------------------------------------+----------+ | x | lintProdRelease (exit code: 1) | 35 sec | +---+---------------------------------------------------+----------+

Slide 111

Slide 111 text

Continuous Integration

Slide 112

Slide 112 text

No content

Slide 113

Slide 113 text

No content

Slide 114

Slide 114 text

Resources www.github.com/dmytrodanylyk/template www.pmd.sourceforge.net/integrations.html www.findbugs.sourceforge.net www.developer.android.com/studio/write/lint.html www.guardsquare.com/en/proguard www.bitrise.io - sample project - pmd official site - findbugs official site - lint user guide - proguard official site - continuous integration

Slide 115

Slide 115 text

@dmytrodanylyk slides.com/dmytrodanylyk www.dmytrodanylyk.com S speakerdeck.com/dmytrodanylyk S