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

Continuous integration and deployment on Android (plus some sweets)

Continuous integration and deployment on Android (plus some sweets)

The presentation from the talk Željko and I held at droidcon Zagreb

Dino Kovač

April 29, 2016
Tweet

More Decks by Dino Kovač

Other Decks in Programming

Transcript

  1. Continuous integration and deployment on Android (plus some sweets) DINO

    KOVAČ / ŽELJKO PLESAC ANDROID ENGINEERS @ INFINUM
  2. ABOUT INFINUM • independent design and development agency • 90

    employees • 15 Android Engineers • hundreds of projects
  3. ANDROID LINT • common android specific issues and best practices

    • LinearLayout orientation • using method introduced in SDK level > minSdk • missing super calls
  4. CHECKSTYLE • helps to keep a consistent code style •

    highly configurable using config file • works well in combination with AS code style if(url.equals(otherUrl)){
 System.out.print("it's equal!");
 }
 
 if(url.equals(otherUrl))
 System.out.print("it's equal!");
 
 if (url.equals(otherUrl)) {
 System.out.print("it's equal!");
 }
  5. PMD, FINDBUGS • warn you about common Java pitfalls •

    findbugs works on class files • pmd works on source files
  6. JAVA.NET.URL @Test
 public void urlEquals() throws Exception {
 URL url

    = new URL("https://infinum.co");
 URL otherUrl = new URL("https://www.infinum.co");
 
 Assert.assertEquals(url, otherUrl);
 }
  7. JAVA.NET.URL - JAVADOCS • Two hosts are considered equivalent if

    both host names can be resolved into the same IP addresses; else if either host name can't be resolved, the host names must be equal without regard to case; or both host names equal to null. • Since hosts comparison requires name resolution, this operation is a blocking operation.
  8. MATH.ABS • does not always return positive numbers! • integer

    values range from -2147483648 to 2147483647 Assert.assertTrue(Math.abs(Integer.MIN_VALUE) >= 0);
  9. TESTING APPROACHES • instrumentation tests - robotium / espresso •

    unit tests - plain JUnit • functional tests - robolectric
  10. TESTS • we use a combination of unit tests and

    functional tests • centered around the clients needs and the specification
  11. @Test
 public void correctLoginRequest() throws Exception {
 String user =

    "testUserName";
 String pass = "testPass0&";
 
 enqueueResponse("sdk-login-response.json");
 Sdk.login(user, pass, mock(SdkCallback.class));
 
 RecordedRequest request = takeLastRequest();
 
 assertThat(request.getMethod()).isEqualTo("POST");
 assertThat(request.getPath()).isEqualTo("/login");
 assertThat(request.getHeader(“Accept")) .isEqualTo("application/json");
 assertThat(request.getHeader(“Content-Type")) .isEqualTo("application/x-www-form-urlencoded");
 assertThat(request.getBody().readUtf8()) .isEqualTo("login=" + user + "&password=" + "testPass0%26");
 }
  12. CONTINUOUS INTEGRATION • each push to the git repo triggers

    a build on the CI service • CI service checks out the code, runs static analysis and tests • if the build fails, you get notified • if you break the build you fix it!
  13. CIRCLE CI • hosted CI service • nice android support

    • configurable via .yml file • support for saving and displaying build artifacts
  14. CIRCLE CI - DOWNSIDES • integrates only with github •

    no support for x86 emulators • your code is ‘in the cloud’
  15. BROKEN BUILDS • clients want their features done yesterday •

    project managers / product owners don’t care about broken builds • ‘we’ll fix the build after the release’
  16. AUTOMATIC DEPLOY FOR CLIENTS • for each push to the

    labs branch the release build is built • the CI build number is added to versionName: ‘1.0.0-b45’ • the .apk is uploaded to Infinum Labs • the last commit message is used as changelog
  17. ENFORCE RULES • don’t block the main thread - even

    regular users will notice flickering • get familiar with multithreading components • use Traceview and dmtracedump
  18. STRICT MODE • use Strict mode in debug builds -

    set penalty death • detectDiskReads() • detectDiskWrites()
  19. DETECT ANR’S • ANRWatchDog (link) • detects Android ANRs (Application

    Not Responding) problems • can either crash your application or notify you via callback
  20. CRASH YOU APPLICATIONS AS SOON AS POSSIBLE • Square’s approach

    to handling crashes (presentation and video) • organise your code in a way that it crashes as soon as possible • returning null values is evil
  21. public class Person { private String name; private String surname;

    public Person(String name, String surname) { this.name = name; this.surname = surname; } … }
  22. public static String getFullName(Person person) { if(person != null){ return

    person.getName() + person.getSurname(); } else{ return null; } }
  23. public static String getFullName(Person person) { if (person == null)

    { throw new IllegalStateException("Person cannot be null!”); } return person.getName() + person.getSurname(); }
  24. LOG AND MEASURE YOUR CRASHES • many great tools (Crashlytics,

    AppsDynamics, Crittercism) • analyse your crashes • custom ROMs causing crashes? • cheap, low quality devices? • frequency of crashes?
  25. THE TRY-CATCH BLOCK AND EXCEPTIONS • you should care about

    your handled exceptions • they have to be logged and analysed • should contain useful information
  26. public class ProfilePresenterImpl implements ProfilePresenter{ public void showPersonData() { String

    fullName = PersonUtils.getFullName(person)); if(fullName != null){ view.showFullName(PersonUtils.getFullName(person))); } view.showBirthday(PersonUtils.getFormattedBirthday(person)) view.hideLoadingDialog(); } }
  27. public class ProfilePresenterImpl implements ProfilePresenter{ public void showPersonData() { try{

    String fullName = PersonUtils.getFullName(person)); if(fullName != null){ view.showFullName(PersonUtils.getFullName(person))); } view.showBirthday(PersonUtils.getFormattedBirthday(person))) } catch(Exception e){ e.printStackTrace(); view.showErrorDialog(); } } }
  28. TIMBER • Utility on top of Android's default Log class

    • by Jake Wharton • can be configured
  29. CRASH REPORTING TREE private static class CrashReportingTree extends Timber.Tree {


    
 @Override
 protected void log(int priority, String tag, String message, Throwable t) {
 if (priority == Log.VERBOSE || priority == Log.DEBUG) {
 return;
 }
 
 // will write to the crash report but NOT to logcat
 Crashlytics.log(message);
 
 if (t != null) {
 Crashlytics.logException(t);
 }
 }
 }
  30. CRASH REPORTING TREE @Override
 public void onCreate() {
 super.onCreate(); if

    (BuildConfig.DEBUG) {
 Timber.plant(new Timber.DebugTree());
 } else { Fabric.with(this, new Crashlytics());
 Timber.plant(new CrashReportingTree());
 }
 }
  31. public class ProfilePresenterImpl implements ProfilePresenter{ public void showPersonData() { try{

    String fullName = PersonUtils.getFullName(person)); if(fullName != null){ view.showFullName(PersonUtils.getFullName(person))); } view.showBirthday(PersonUtils.getFormattedBirthday(person ))));} } catch(Exception e){ Timber.e(e, “Failure in “ + getClass().getSimpleName()); view.showErrorDialog(); } } }
  32. APP CRASH HANDLERS • define custom app crash handler in

    everyone of your production builds • avoid ugly system dialogs • watch out for app restart loops!
  33. public class AppCrashHandler implements Thread.UncaughtExceptionHandler {
 
 private Activity liveActivity;


    
 public AppCrashHandler(Application application) {
 application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { 
 @Override
 public void onActivityResumed(Activity activity) {
 liveActivity = activity;
 }
 @Override
 public void onActivityPaused(Activity activity) {
 liveActivity = null;
 }
 });
 }
 @Override
 public void uncaughtException(Thread thread, Throwable ex) {
 if(liveActivity != null){
 Intent intent = new Intent(getApplicationContext(), MainActivity.class);
 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
 liveActivity.finish();
 liveActivity.startActivity(intent);
 }
 
 System.exit(0);
 }
 } CUSTOM CRASH HANDLER
  34. Thank you! [email protected] / @DINO_BLACKSMITH [email protected] / @ZELJKOPLESAC Visit infinum.co

    or find us on social networks: infinum.co infinumco infinumco infinum YES, WE ARE HIRING!