Don’t go crashing my heart
ŽELJKO PLESAC
ANDROID NOWADAYS
• more than 1.4 billions of users
• 23 different SDK versions
• 1,294 device vendors with more than 24,093 distinct
Android devices
YOUR USERS DESERVE BEAUTIFUL APPLICATIONS, WITH BEST POSSIBLE USER
EXPERIENCE.
CRASHES HAVE ENORMOUS AFFECT ON USER
EXPERIENCE.
NOBODY LIKES CRASHES*.
* EXPECT TESTERS
PROVIDE BETTER CRASH EXPERIENCE
• you should care about your crashes and try to minimise
them
• optimise your applications in a way, that you can detect
crash even before it occurs
01USE STATIC CODE CHECKERS & WRITE TESTS
Strict rules• use at least default set of rules
• enforce zero tolerance to all static
code checker warnings and
errors
• aim for high test code coverage
• don’t test only for positive
outcomes, test also for negative
• write stress tests
• always insert at least one test
which will throw an exception or
bug
• all tests have to pass before
merging into development
TESTS STABILITY? Test frameworks have bugs. CI servers also. Android platform also.
HANDLING FAILED TESTS
• all failed tests have to be carefully examined
• if they are not caused by your code, ignore them but test
them once again when new version of testing platform is
available
• this doesn’t mean that everything is not your fault
03USE CONTINUOUS INTEGRATION
CONTINOUS INTEGRATION
• static code checkers & tests automation
• a lot of available products - Jenkins, Travis, CircleCI…
• use protected branches for master and development branch
• use Git flow (or some other pattern)
04HANDLE COMMON ANDROID PROBLEMS - MEMORY LEAKS
MEMORY LEAKS
• they will cause problems and crash your applications
• a lot of great tools
A memory leak detection library for Android and Java,
developed by Square (Pierre-Yves Ricau).
De facto standard for detecting memory leaks, use it in your
debug builds.
LEAK CANARY
• detects memory leaks in your
application, external libraries,
event Android OS itself
• it will not give you an answer
what is the cause of a leak, just
an information that the leak has
occurred
WEAK REFERENCES ARE NOT THE ANSWER TO
EVERYTHING.
05CRASH FAST
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
• use exceptions
• null values are evil
package co.infinum.crashhandler.sampleapp;
/** * Created by Željko Plesac on 13/03/16. */ public class Person {
private String name;
private String surname;
public Person(String name, String surname) { this.name = name; this.surname = surname; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getSurname() { return surname; }
public void setSurname(String surname) { this.surname = surname; } }
package co.infinum.crashhandler.sampleapp;
/** * Created by Željko Plesac on 13/03/16. */ public class PersonUtils {
private PersonUtils() {
}
public static String getFullName(Person person) { return person.getName() + person.getSurname(); } }
package co.infinum.crashhandler.sampleapp;
/** * Created by Željko Plesac on 13/03/16. */ public class PersonUtils {
private PersonUtils() {
}
public static String getFullName(Person person) { if(person != null){
return person.getName() + person.getSurname(); }
else{ return null;
} } }
package co.infinum.crashhandler.sampleapp;
/** * Created by Željko Plesac on 13/03/16. */ public class PersonUtils {
private PersonUtils() {
}
public static String getFullName(Person person) { if (person == null) { throw new IllegalStateException("Person cannot be null!”); }
return person.getName() + person.getSurname(); } }
06LOG AND MEASURE
LOG AND MEASURE YOUR CRASHES
• lot of great tools (Crashlytics, AppsDynamics, Crittercism)
• analyse your crashes • are crashes happening on custom ROMs? • are crashes occurring only on cheap phones? • are your crashes frequent?
I don’t care about warnings, only errors.
- KING HENRIK VIII.
TRY-CATCH BLOCK AND EXCEPTIONS
• you should care about your handled exceptions
• they have to be logged and analysed
• should contain useful information
package co.infinum.crashhandler.sampleapp;
/** * Created by Željko Plesac on 13/03/16. */ public class ProfilePresenterImpl implements ProfilePresenter{
private Person person;
private ProfileView view;
public ProfilePresenterImpl(Profile person){ this.person = person;
} …
public void showPersonData() { view.showFullName(PersonUtils.getFullName(person))); view.showBirthday(PersonUtils.getFormattedBirthday(person))); view.hideLoadingDialog();
} }
package co.infinum.crashhandler.sampleapp;
/** * Created by Željko Plesac on 13/03/16. */ public class ProfilePresenterImpl implements ProfilePresenter{
private Person person;
private ProfileView view;
public ProfilePresenterImpl(Profile person){ this.person = person;
} …
public void showPersonData() { String fullName = PersonUtils.getFullName(person));
if(fullName != null){ view.showFullName(PersonUtils.getFullName(person)));
}
view.showBirthday(PersonUtils.getFormattedBirthday(person))); view.hideLoadingDialog();
} }
package co.infinum.crashhandler.sampleapp;
/** * Created by Željko Plesac on 13/03/16. */ public class ProfilePresenterImpl implements ProfilePresenter{
private Person person;
private ProfileView view;
public ProfilePresenterImpl(Profile person){ this.person = person;
} …
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.prinStackTrace();
view.showErrorDialog(); }
} }
TIMBER
• Utility on top of Android's normal Log class
• by Jake Wharton
Timber.plant(new Timber.DebugTree());
Timber.d(...) Timber.i(...) Timber.v(...) Timber.e(...) Timber.w(...) Timber.wtf(...)
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); } }}
CRASH REPORTING TREE
@Overridepublic void onCreate() { super.onCreate(); CrashlyticsCore crashlyticsCore = new CrashlyticsCore.Builder()
.disabled(BuildConfig.DEBUG).build(); Fabric.with(this, new Crashlytics.Builder().core(crashlyticsCore).build());
if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); } else { Timber.plant(new CrashReportingTree()); } }
package co.infinum.crashhandler.sampleapp;
/** * Created by Željko Plesac on 13/03/16. */ public class ProfilePresenterImpl implements ProfilePresenter{
private Person person;
private ProfileView view;
public ProfilePresenterImpl(Profile person){ this.person = person;
} …
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(); }
} }
/** * Logs everything to crashlytics, then we just need to log an exception and we should see all prior logs online! */ private static class RemoteDebuggingTree extends Timber.Tree {
@Override protected void log(int priority, String tag, String message, Throwable t) { // will write to the crash report as well to logcat Crashlytics.log(priority, tag, message);
if (t != null) { Crashlytics.logException(t); } } }
08HIDE CRASHES FROM YOUR USERS
Crashes are just exceptions, which are not handled by your application*.
* IN MOST CASES
APP CRASH HANDLERS
• define custom app crash handler in all of your production
builds
• avoid ugly system dialogs
• simple configuration
• apps are restarted, so they go into stable state
• watch for cyclic bugs!
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
APPLICATION CLASS
@Overridepublic void onCreate() { super.onCreate(); Thread.setDefaultUncaughtExceptionHandler(new AppCrashHandler(this)); }
→
EXAMPLE
08USE STAGED ROLLOUTS
STAGED ROLLOUT
• can only be used for app updates, not when publishing an
app for the first time
• your update reaches only a percentage of your users, which
you can increase over time
STAGED ROLLOUT PROCESS ①
• at first, define that your update is only available to small
percentage of your users (I.E. 5%)
• closely monitor crash reports and user feedback • users receiving the staged rollout can leave public
reviews on Google Play
• if everything goes OK, increase the percentage
• if you get negative feedback or encounter some bugs, halt
the process and fix all of reported problems
10HARSH TRUTH
YOUR APPS WILL CRASH IN PRODUCTION, AND THERE IS
NOTHING YOU CAN DO TO PREVENT IT.
THERE IS NO SUCH THING AS 100% CRASH FREE ANDROID APPLICATION
• large number of different devices
• large number of OS versions • in most cases, proposed minimum API value is 15 and
the current stable version is 23, which means that your
app has to work on 8 different API versions
• device vendors alter Android OS - they add custom
solutions and bloatware
• rooted Android devices - core functionalities can be altered
THINGS ARE GOING TO BECOME EVEN MORE
COMPLICATED.
Phones, tablets, phablets, freezers, smartwatches, cars, bikes,
boards….
ANDROID IS EXPANDING.
MultiWindow support, Jack compiler, new APIs, deprecated
APIs, new programming languages…
ANDROID IS GETTING NEW FEATURES.
THAT’S THE BEAUTY OF ANDROID.
DEVELOPERS WILL ALWAYS FIND A WAY.
Thank you!
Visit www.infinum.co or find us on social networks:
infinum.co infinumco infinumco infinum
TWITTER: @ZELJKOPLESAC EMAIL: [email protected]