不只自動化而且更敏捷的android開發工具 gradle mopcon

Post on 09-Jan-2017

3.361 Views

Category:

Mobile

4 Downloads

Preview:

Click to see full reader

TRANSCRIPT

A productive Android development environment

不只自動化而且更敏捷的Android開發工具 Gradle

Sam Chiu

Sam Chiu 邱炫儒Sam Chiu@HTC engineering build dept.

promote DevOps in HTC

iamsamchiu@gmail.com / mindcraft4life.tumblr.com

Active Gradle Using Developers

500+

Software Components

1000+

Release Builds per Day

1000+

“ ”

The challenge of Mobile App development

If your App crashes for 0.01% of your users, they’re going to post a negative review.

However, the 99.99% of people for whom it works great are NOT going to post five-star ratings for the software working as expected.

-- The Loop magazine

Automate everythingand shrink the cycle time

可重複運行 減少人為/環境錯誤 流程透明 產生更有品質的程式碼

Coding Check-in Build Test Publish

易於分享

Using Gradle...

put every new code commit through a full cycle

Automated provisioning and configuration management also part of the continuous delivery

From Command line to IDE to CI

消除溝通的嫌隙From Command line to IDE to CI

Convention 1

一句話惹毛老闆:

“我電腦沒這個問題呀!”“It works on my machine!”

“一定有鬼偷偷動過我的程式碼”

使用不一致的工具

ant

proguardshrinkresources

androidgradle plugin

build.xmlADT

eclipse

gradle

debug on/off

Source Code

module A

module B

moduleC build failure

Those tools have different build processes on your source code!

Android Studio/IntelliJ/..

gradle

減少溝通的嫌隙From command line to IDE to continuous integration

proguardshrinkresources

android gradle plugin

debug on/off

Coding with IDE

Building Automation

減少溝通的嫌隙From command line to IDE to continuous integration

Android Studio

proguardshrinkresources

android gradle plugin

gradle

debug on/off customizedconfiguration

genericconfiguration

Source Code

Source Code

Source Code

APK

ALPHA BETA RCProject

Schedule

後期出現的錯誤往往影響巨大

將商業版本客製盡量提前到開發時期

Product Flavor and Multiple APK

Convention 2

<manifest ... >

<supports-screens android:smallScreens="false"

android:normalScreens="false"

android:largeScreens="true"

android:xlargeScreens="true"

android:requiresSmallestWidthDp="600"

/>

...

</manifest>

custom AndroidManifest.xml for tablet

Example on Google dev site:Design Multi-APK for tablet and phone

Google Play

The versionName/versionCode schema for multiple APK

versionCode (04 01 310)

phone screenApp version(3.1.0)API Level(4+)

versionCode (04 02 310)

tablet screenApp version(3.1.0)API Level(4+)

release 1upload APKs

Version Name: 3.1.0-build1

Edit your versionCode in AndroidManifest.xml?<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.testing.unittesting.BasicSample" >

<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" android:versioncode="0401310" android:versionName="3.1.0-build1" > <activity android:name="com.example.android.testing.unittesting.BasicSample.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application>

</manifest>

error-prone manual efforts around deployment

把處理邏輯都編寫至gradle設定檔中

android{defaultConfig {

versionName computeVersionName()}productFlavors {

phone{ versionCode computeVersionCode(1) } tablet { versionCode computeVersionCode(2) }

}}def computeVersionCode(int flavor) { def version_code = ext.minSdkVersion * 100000 + flavor * 1000 + ext.versionMajor * 100 + ext.versionMinor*10 \

+ ext.versionIncremental return version_code}def computeVersionName(){

def buildNumber = System.getenv("BUILD_NUMBER") ?: "dev"def version_name = ext.versionMajor+"."+ext.versionMinor+”.”+ext.versionIncremental+"-"+buildNumberreturn version_name

}

custom build logic and integrate with Jenkinsbuild.gradle:

為什麼要把Jenkins buildNumber 寫進version name?

混在一起做撒尿牛丸?

為什麼要把Jenkins buildNumber 寫進version name?

混在一起做Dogfooding

What is Dogfooding?

Dogfooding means a good product versioningLibrarySource

ProjectSource

alpha 1 beta1Debug APK

Release 2Release APK

beta2

Release 1

alpha 1 beta2

OTA

Issue Tracking bug report

Release 2

OTAPRELOAD

Employee Device

透過Jenkins追朔程式碼

versionName 3.1.0-Build#2

Q:處理邏輯要寫在gradle script 還是寫在CI Server script?

寫在CI Server script的處理邏輯如何管理?

期待 jenkins 2.0

pipeline as code !

what is “pipeline as code”:https://wiki.jenkins-ci.org/display/JENKINS/2.0+Pipeline+as+Code

java code preprocessing

Convention 3

public final static boolean MY_DEBUG = false;

@Overridepublic void onConnectionFailed(ConnectionResult result) { if (MY_DEBUG){ Log.i(TAG, "[Security sensitive Log] GoogleClient connection failed." ); Log.i(TAG, "[Security sensitive Log] UserName: "+UserName ); Log.i(TAG, "[Security sensitive Log] InternalModelName: "+InternalModelName ); }else{ Log.i(TAG, "GoogleApiClient connection failed: " + result.toString()); }

每次專案定義可能都不同大家都需要去記Flag

增加自動化的複雜度

adjust consts to switch debug on/off

What is bad直接修改程式碼

error-prone manual efforts around deployment AGAIN!

Use BuildConfig in gradlepublic final static boolean MY_DEBUG = false;

@Overridepublic void onConnectionFailed(ConnectionResult result) { if (BuildConfig.DEBUG){ Log.i(TAG, "[Security sensitive Log] GoogleClient connection failed." ); Log.i(TAG, "[Security sensitive Log] UserName: "+UserName ); Log.i(TAG, "[Security sensitive Log] InternalModelName: "+InternalModelName ); }else{ Log.i(TAG, "GoogleApiClient connection failed: " + result.toString()); }

編譯時期無需修改程式碼

$ gradlew assembleRelease$ gradlew assembleDebug

Customize BuildConfig in gradle

buildTypes { debug { buildConfigField "boolean","NETWORK_DEBUG","true" minifyEnabled false } release { buildConfigField "boolean","NETWORK_DEBUG","false" minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' }}

從程式碼管理

到組態管理

Convention 4

“By distributing your project source based on gradle ,anyone can work with it without needing to install many annoying tools/dependencies beforehand”

“Users of the build are guaranteed to use the same process and the same dependencies version that was designed to work with”

from http://gradle.org/

SCM (git)

.gradle/local.properties/.idea/workspace.xml/.idea/libraries.DS_Store/build/captures

Use .gitignore

建立可重複且可靠的流程

Build script as code

Build Once and build anywhere

Why upload shell/batch files to version control server?

gradlew.sh

gradle wrapper

#Sun Aug 09 19:57:07 CST 2015distributionBase=GRADLE_USER_HOMEdistributionPath=wrapper/distszipStoreBase=GRADLE_USER_HOMEzipStorePath=wrapper/distsdistributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip

gradle-wrapper.properties

Even better! Install android-sdk automaticallyapply plugin: 'android-sdk-manager'apply plugin: 'com.android.application'

android { compileSdkVersion 21 buildToolsVersion '22.0.1' defaultConfig { applicationId "com.example.android.testing.unittesting.BasicSample" minSdkVersion 17 targetSdkVersion 23 }}

Reference: https://github.com/JakeWharton/sdk-manager-plugin

gradle project on git

pure Mac OS

Java

source code

build script

dependenciesconfiguration

toolchain configuration

Maven Repo

toolchain sites

google play servicefacebook sdk lib firebase sdk lib...

gradle tool

android sdk

pure Windows

Java

less administration and more scalable for the client machines

gradle 2.4 gradle 2.5

build-tool 19build-tool 20platforms 19platforms 23

pure linux

Java

pure linux

Java

pure linux

Java ...developers machines Continuous Integration servers

install tools on build time

Dependencies Management

Convention 5

root project lib-proj widget

lib-projActivity

firebase sdk

facebook sdk

evernote sdk

dropbox sdk

Google play service libgooglesupport v4

log/util module

drive

Google+

games

analytics

Location

Ads

maps

nearby

wallet

google support appcompat-v7

google support design lib

google supportmultidex

google supportmultidex instrumentation

A large scale Android App

root project lib-proj widget

lib-projActivity

firebase sdk

evernote sdk

dropbox sdk

Google play service libgooglesupport v4

log/util module

drive

Google+

games

analytics

Location

Ads

maps

nearby

wallet

google support appcompat-v7

google support design lib

google supportmultidex

google supportmultidex instrumentation

facebook sdk

The dependency nightmare

Before Gradle

Getting started with the Dropbox Android SDK:1. Include everything under lib/ in your project/build.2. You'll want to start off by creating an AndroidAuthSession with your consumer key and secret.3...

Download dropbox-android-sdk from dropbox website

1

2

到底用了哪一版library?

┌Top Android Project│├── Project 1 - (Pure Java Modules)│ ││ ├── Module A1│ │ └──libs/android-support-v4_22.jar│ ├── Module B1│ :│ └── facebook-lib-project│ └──libs/android-support-v4-v22.0.0.jar│

BUILD FAIL

After Gradle

dependencies {

compile 'com.facebook.android:facebook-android-sdk:4.1.0'

}

1. Use gradle

2.

3. That’s it.

dependencies {

compile 'com.google.android.gms:play-services-wearable:7.3.0'

}

How about the depend on a tree of dependencies?

dependencies {

compile 'com.facebook.android:facebook-android-sdk:4.1.0'

}

dependencies {

compile 'com.google.android.gms:play-services-wearable:7.3.0'

}

android-support-v4:21.0.0

android-support-v4:22.0.0

Transitive Dependencies on Gradle(可遞移性相依)

A -> B and B -> Chence A -> C

google play service aar library hierarchy on maven

Tips of transitive dependencies

dependencies {

compile 'com.google.android.gms:play-services-wearable:7.3.0'

}

dependencies {

compile 'com.google.android.gms:play-services-wearable:7.3.0

@aar'}

android-support-v4:22.0.0

android-support-v4:22.0.0

transitive=false

As a library provider

<?xml version="1.0" encoding="UTF-8"?><project> <modelVersion>4.0.0</modelVersion> <groupId>com.google.android.gms</groupId> <artifactId>play-services-location</artifactId> <version>7.8.0</version> <packaging>aar</packaging> <dependencies> <dependency> <groupId>com.google.android.gms</groupId> <artifactId>play-services-maps</artifactId> <version>7.8.0</version> <scope>compile</scope> <type>aar</type> </dependency> …...

Maven Repository

HelloWorld Library/pom.xml

remember to provide your transitive dependencies in pom.xml

Handle dependency conflict

Gradle offers the following conflict resolution strategies:

Newest: The newest version of the dependency is used. This is Gradle's default strategy, and is often an appropriate choice as long as versions are backwards-compatible.

Fail: A version conflict results in a build failure. This strategy requires all version conflicts to be resolved explicitly in the build script.

Use ‘Fail’ resolution strategy

configurations.all { resolutionStrategy { // fail eagerly on version conflict (includes transitive dependencies) // e.g. multiple different versions of the same dependency (group and name are equal) failOnVersionConflict() }}

build.gradle:

Handle dependency conflict

dependencies { compile('org.hibernate:hibernate:3.1') { //in case of versions conflict '3.1' version of hibernate wins: force = true //disabling all transitive dependencies of this dependency transitive = false }}

build.gradle:

Versioning Control dependencies { compile 'com.facebook.android:facebook-android-sdk:4.1.0'}

dependencies { compile 'com.facebook.android:facebook-android-sdk:4.1.+'}

dependencies { compile 'com.facebook.android:facebook-android-sdk:4.1.0-SNAPSHOT'}

建置系統始終應該指定專案所需外部類別庫的確切版本 :

開發時期指定專案所需外部函式庫的最新版本 :

開發時期指定專案所需外部函式庫的 SNAPSHOT版本:

about testing

Convention 6

Building Effective Testing planUnit test● mokito● Robolectric

Integration test (acceptance testing)

● Instrumentation● emulation

○ Genymotion (local with virtualBox)○ Manymo (online)

● online device testing○ appthwack (AWS Cloud)

Repeatable

- 可在任何環境下進行測試(沒有網路、db、Android Context的狀況下仍能進行)

Purpose

- 讓單元測試運行速度加快,可加速回饋,更可了解自己的修改是否破壞現有的任何功能。

How

- 單元測試依存於測試替身,不應該存取資料庫、檔案系統、與外部系統互動。- 簡單來說,單元測試不應該與系統元件之間產生互動。

A unit test should be “Repeatable”

Building Effective Unit TestsUnit test● mokito● Robolectric

put every new code commit through a full cycle!

Integration test (acceptance testing)

● Instrumentation● emulation

○ Genymotion (local with virtualBox)○ Manymo (online)

● online device testing○ appthwack (AWS Cloud)

測試策略的設計主要是個「識別和評估專案風險之優先順序及

決定採用哪些行動來緩解風險」的歷程

Test Case Strategy

--Continuous Delivery:reliable software rleases though build, test and deployment automation

測試策略的設計主要是個「識別和評估專案風險之優先順序及

決定採用哪些行動來緩解風險」的歷程

Test Case Strategy

--Continuous Delivery:reliable software rleases though build, test and deployment automation

未慮勝 先慮敗

Example: com.facebook.login.widget.LoginButtonTest.java

@Testpublic void testLoginButtonWithReadPermissions() throws Exception { LoginManager loginManager = mock(LoginManager.class); Activity activity = Robolectric.buildActivity(MainActivity.class).create().get();

LoginButton loginButton = (LoginButton) activity.findViewById(R.id.login_button); ArrayList<String> permissions = new ArrayList<>(); permissions.add("user_location"); loginButton.setReadPermissions(permissions); loginButton.setDefaultAudience(DefaultAudience.EVERYONE); loginButton.setLoginManager(loginManager); loginButton.performClick();

verify(loginManager).logInWithReadPermissions(activity, permissions); verify(loginManager, never()).logInWithPublishPermissions(isA(Activity.class), anyCollection()); // Verify default audience is channeled verify(loginManager).setDefaultAudience(DefaultAudience.EVERYONE);}

Reference: https://github.com/facebook/facebook-android-sdk

com.facebook.login.widget.LoginButton.java

private class LoginClickListener implements OnClickListener {@Overridepublic void onClick(View v) {…

if (LoginAuthorizationType.PUBLISH.equals(properties.authorizationType)) {loginManager.logInWithPublishPermissions(LoginButton.this.getActivity(),properties.permissions);

} else {

loginManager.logInWithReadPermissions(LoginButton.this.getActivity(),properties.permissions);//[sam] demo unit test//loginManager.logInWithPublishPermissions(LoginButton.this.getActivity(),properties.permissions);

} }

Command-line Optionenable continuous build with the -t or –continuous command-line option which will automatically re-execute builds when changes are detected to its inputs.ex:

reference:http://gradle.org/feature-spotlight-continuous-build/

speed up your build-edit-build feedback loop

$ gradlew test -t

From handcrafted to mindcrafted

Q&A

Reference:● Android plugin for gradle:https://developer.android.com/tools/building/plugin-for-gradle.html: ● Android tools project site, tips:http://tools.android.com/tech-docs/new-build-system/tips● Gradle dependency management:https://docs.gradle.org/current/userguide/dependency_management.html● Google dev site, multiple apk:https://developer.android.com/google/play/publishing/multiple-apks.html● Sample project on github:https://github.com/iamsamchiu/AndroidSampleForGradleUsage

The Android robot is modified from work created by Google/StockLogos and used according to the Creative Commons 3.0 Attribution License.

top related