We'll demonstrate few techniques how to provide a better crash experience - advanced logging, detecting memory leaks and hiding crashes from our users.
6. 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
8. 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
10. 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
12. 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)
14. MEMORY LEAKS
• they will cause problems and crash your applications
• a lot of great tools
15. 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
16. • 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
19. 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
20. 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;
}
}
21. 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();
}
}
22. 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;
}
}
}
23. 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();
}
}
25. 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?
26.
27. I don’t care about
warnings, only errors.
- KING HENRIK VIII.
28. TRY-CATCH BLOCK AND EXCEPTIONS
• you should care about your handled exceptions
• they have to be logged and analysed
• should contain useful information
29. 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();
}
}
30. 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();
}
}
31. 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();
}
}
}
34. 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);
}
}
}
35. CRASH REPORTING TREE
@Override
public 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());
}
}
36. 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();
}
}
}
37. /**
* 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);
}
}
}
40. 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!
41. 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
45. 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
46. 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
48. YOUR APPS WILL CRASH IN
PRODUCTION, AND THERE IS
NOTHING YOU CAN DO TO
PREVENT IT.
49. 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
55. Thank you!
Visit www.infinum.co or find us on social networks:
infinum.co infinumco infinumco infinum
TWITTER: @ZELJKOPLESAC
EMAIL: ZELJKO.PLESAC@INFINUM.CO