Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Journey to 99% Crash Free
Search
Fumihiko Shiroyama
March 09, 2017
Technology
5
4k
Journey to 99% Crash Free
DroidKaigi 2017 Day 2 (17:10 - 17:40 Room 1)
「テスト0から目指すクラッシュフリー率99%」
Fumihiko Shiroyama
March 09, 2017
Tweet
Share
More Decks by Fumihiko Shiroyama
See All by Fumihiko Shiroyama
The world of Android wireless communications without Internet
srym
1
120
AWS Device FarmとCircleCIでAndroidのUIテストを自動化しよう
srym
1
4.9k
Spring BootをKotlinで作成しAmazon Elastic Container Service (ECS) で稼働させる
srym
6
1.9k
iOSDC_2019_DeviceFarm.pdf
srym
8
19k
世界で戦うエンジニアになるために_公開用.pdf
srym
18
45k
Unit Testing in a Nutshell - DroidKaigi 2018
srym
11
15k
Clean Architecture & TDD
srym
1
3.7k
はやい・やすい・うまい!スタートアップでも使える Retrofit + RxJava で瞬間APIクッキングレシピ
srym
2
590
I/O 2017 Short Report
srym
0
300
Other Decks in Technology
See All in Technology
ドメインの本質を掴む / Get the essence of the domain
sinsoku
2
150
Can We Measure Developer Productivity?
ewolff
1
150
AWS Lambdaと歩んだ“サーバーレス”と今後 #lambda_10years
yoshidashingo
1
170
TypeScript、上達の瞬間
sadnessojisan
46
13k
強いチームと開発生産性
onk
PRO
34
11k
Security-JAWS【第35回】勉強会クラウドにおけるマルウェアやコンテンツ改ざんへの対策
4su_para
0
180
Terraform CI/CD パイプラインにおける AWS CodeCommit の代替手段
hiyanger
1
240
Oracle Cloud Infrastructureデータベース・クラウド:各バージョンのサポート期間
oracle4engineer
PRO
28
12k
TanStack Routerに移行するのかい しないのかい、どっちなんだい! / Are you going to migrate to TanStack Router or not? Which one is it?
kaminashi
0
580
安心してください、日本語使えますよ―Ubuntu日本語Remix提供休止に寄せて― 2024-11-17
nobutomurata
1
990
OCI Vault 概要
oracle4engineer
PRO
0
9.7k
【令和最新版】AWS Direct Connectと愉快なGWたちのおさらい
minorun365
PRO
5
750
Featured
See All Featured
Building Adaptive Systems
keathley
38
2.3k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
8
860
GitHub's CSS Performance
jonrohan
1030
460k
The Language of Interfaces
destraynor
154
24k
jQuery: Nuts, Bolts and Bling
dougneiner
61
7.5k
For a Future-Friendly Web
brad_frost
175
9.4k
Fontdeck: Realign not Redesign
paulrobertlloyd
82
5.2k
Being A Developer After 40
akosma
86
590k
The Invisible Side of Design
smashingmag
298
50k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
4
370
Intergalactic Javascript Robots from Outer Space
tanoku
269
27k
Transcript
Journey to 99% Crash Free Fumihiko Shiroyama
Slide URL • https://goo.gl/zwxdyy
About Me • Fumihiko Shiroyama • Android App Developer •
Nikkei Inc. • https://github.com/srym • https://twitter.com/fushiroyama
Agenda • Face up to the reality of crash! •
Take the first step for Unit Testing • Make biggest gains without Unit Testing
The Fact Your App is Crashing
The Fact Your App is Crashing • 99% Crash Free?
Good! • DAU 30000
The Fact Your App is Crashing • 99% Crash Free?
Good! • DAU 30000 $SBTIFT
The Fact Your App is Crashing • 99% Crash Free?
Good! • DAU 30000 • 99.9% Crash Free? Great!!
The Fact Your App is Crashing • 99% Crash Free?
Good! • DAU 30000 • 99.9% Crash Free? Great!! 4UJMM$SBTIFT
Keep Going!!!
None
Fabric • Crashlytics • Beta • Answers • Acquired by
Google
"41MVHJO
Crashlytics • Automatically collects crash info • No need for
uploading obfuscation mappings • Crashlytics#logException(e) for non-fatal
Crashlytics private void logUser() { // info about user Crashlytics.setUserIdentifier("12345");
Crashlytics.setUserEmail("
[email protected]
"); Crashlytics.setUserName("Test User"); }
Crashlytics private void logUser() { // info about user Crashlytics.setUserIdentifier("12345");
Crashlytics.setUserEmail("
[email protected]
"); Crashlytics.setUserName("Test User"); }
Firebase Crash Reporting • Similar to Crashlytics
"41MVHJO
Crash Reporting • Automatically collects crash info as well •
Need for uploading obfuscation mappings (there is a helper gradle plugin) • FirebaseCrash.report(e) for non-fatal
Crash Reporting Normal Stack Trace
Crash Reporting Device Info
Crash Reporting Contextual History
Crash Reporting Contextual History $00- &WFOU -PH
Integration
None
Timber dependencies { compile 'com.jakewharton.timber:timber:4.5.1' }
Timber dependencies { compile 'com.jakewharton.timber:timber:4.5.1' } #FUUFS-PHE
Timber public class CrashReportingTree extends Timber.Tree { @Override protected void
log(int priority, String tag, String message, Throwable t) { if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) { return; } if (t != null) { Crashlytics.logException(t); FirebaseCrash.report(t); } } }
Timber public class CrashReportingTree extends Timber.Tree { @Override protected void
log(int priority, String tag, String message, Throwable t) { if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) { return; } if (t != null) { Crashlytics.logException(t); FirebaseCrash.report(t); } } }
Timber public class CrashReportingTree extends Timber.Tree { @Override protected void
log(int priority, String tag, String message, Throwable t) { if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) { return; } if (t != null) { Crashlytics.logException(t); FirebaseCrash.report(t); } } }
Timber public class CrashReportingTree extends Timber.Tree { @Override protected void
log(int priority, String tag, String message, Throwable t) { if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) { return; } if (t != null) { Crashlytics.logException(t); FirebaseCrash.report(t); } } } /PO'BUBM
Timber public class MyApplication extends Application { @Override public void
onCreate() { super.onCreate(); initTimber(); } private void initTimber() { if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); } else { Timber.plant(new CrashReportingTree()); } } }
Timber public class MyApplication extends Application { @Override public void
onCreate() { super.onCreate(); initTimber(); } private void initTimber() { if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); } else { Timber.plant(new CrashReportingTree()); } } }
Timber public class MyApplication extends Application { @Override public void
onCreate() { super.onCreate(); initTimber(); } private void initTimber() { if (BuildConfig.DEBUG) { Timber.plant(new Timber.DebugTree()); } else { Timber.plant(new CrashReportingTree()); } } }
Timber Timber.d("log message"); // or Timber.e(error, error.getMessage());
Take the first step for Unit Testing
Why can't you write test?
Typical Case
Fat Activity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { User user = fetchUser(userId); processUser(user); } }); }
Fat Activity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { User user = fetchUser(userId); processUser(user); } }); }
Fat Activity @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { User user = fetchUser(userId); processUser(user); } }); }
Typical Case private User fetchUser(long userId) { return apiClient.findUserById(userId); }
Typical Case private User fetchUser(long userId) { return apiClient.findUserById(userId); }
pFMEBDDFTT
How do I test MyActivity#fetchUser ?
That's impossible
Make it testable private User fetchUser(long userId) { return apiClient.findUserById(userId);
}
Make it testable private User fetchUser(ApiClient apiClient, long userId) {
return apiClient.findUserById(userId); }
private User fetchUser(ApiClient apiClient, long userId) { return apiClient.findUserById(userId); }
3FNPWFE pFMEBDDFTT Make it testable
private User fetchUser(ApiClient apiClient, long userId) { return apiClient.findUserById(userId); }
Make it testable
public class UserFetcher { private User fetchUser(ApiClient apiClient, long userId)
{ return apiClient.findUserById(userId); } } Make it testable
public class UserFetcher { public User fetchUser(ApiClient apiClient, long userId)
{ return apiClient.findUserById(userId); } } Make it testable
public class UserFetcher { private final ApiClient apiClient; public UserFetcher(ApiClient
apiClient) { this.apiClient = apiClient; } public User fetchUser(long userId) { return apiClient.findUserById(userId); } } Make it testable
public class UserFetcher { private final ApiClient apiClient; public UserFetcher(ApiClient
apiClient) { this.apiClient = apiClient; } public User fetchUser(long userId) { return apiClient.findUserById(userId); } } Make it testable
Make it testable • This is so-called Dependency Injection •
DI can be done without any libraries! • You can use DI library at any time though
Anything else?
Problem is Activity
Everything is in Activity @Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { User user = fetchUser(userId); processUser(user); } }); }
Let's split this into parts!
MVP
What is MVP • Model - View - Presenter •
View…Activity (Fragment) • Model…Business Logic (e.g. fetching data) • Presenter…Bridge between V & M
Introduce MVP public interface UserContract { public interface View {
void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } }
Introduce MVP public interface UserContract { public interface View {
void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } } $POUSBDUCFUXFFO 7JFXBOE1SFTFOUFS
Introduce MVP public interface UserContract { public interface View {
void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } }
Introduce MVP public interface UserContract { public interface View {
void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } } $BMMCBDLGSPN 1SFTFOUFS
Introduce MVP public interface UserContract { public interface View {
void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } }
Introduce MVP public interface UserContract { public interface View {
void showUser(User user); void showError(Exception error); } public interface Interaction { void clickButton(long userId); } } &WFOUGSPN 6*
Introduce MVP public class UserPresenter implements UserContract.Interaction { private final
UserContract.View view; private final UserFetcher userFetcher; public UserPresenter(UserContract.View view, UserFetcher userFetcher) { this.view = view; this.userFetcher = userFetcher; } @Override public void clickButton(long userId) { } }
Introduce MVP public class UserPresenter implements UserContract.Interaction { private final
UserContract.View view; private final UserFetcher userFetcher; public UserPresenter(UserContract.View view, UserFetcher userFetcher) { this.view = view; this.userFetcher = userFetcher; } @Override public void clickButton(long userId) { } }
Introduce MVP public class UserPresenter implements UserContract.Interaction { private final
UserContract.View view; private final UserFetcher userFetcher; public UserPresenter(UserContract.View view, UserFetcher userFetcher) { this.view = view; this.userFetcher = userFetcher; } @Override public void clickButton(long userId) { } }
Introduce MVP @Override public void clickButton(long userId) { userFetcher.findUserAsync(userId, new
UserFetcher.OnSuccessCallback() { @Override public void onSuccess(User user) { view.showUser(user); } }, new UserFetcher.OnFailureCallback() { @Override public void onFailure(Exception e) { view.showError(e); } }); }
Introduce MVP @Override public void clickButton(long userId) { userFetcher.findUserAsync(userId, new
UserFetcher.OnSuccessCallback() { @Override public void onSuccess(User user) { view.showUser(user); } }, new UserFetcher.OnFailureCallback() { @Override public void onFailure(Exception e) { view.showError(e); } }); }
Introduce MVP @Override public void clickButton(long userId) { userFetcher.findUserAsync(userId, new
UserFetcher.OnSuccessCallback() { @Override public void onSuccess(User user) { view.showUser(user); } }, new UserFetcher.OnFailureCallback() { @Override public void onFailure(Exception e) { view.showError(e); } }); }
Introduce MVP public class MyActivity extends BaseActivity implements UserContract.View {
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void showUser(User user) {} @Override public void showError(Exception error) {} }
Introduce MVP public class MyActivity extends BaseActivity implements UserContract.View {
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void showUser(User user) {} @Override public void showError(Exception error) {} }
Introduce MVP @Override protected void onCreate(@Nullable Bundle savedInstanceState) { final
UserPresenter presenter = new UserPresenter(this, userFetcher); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.clickButton(userId); } }); } @Override public void showUser(User user) { toastMessage(user.getName()); } @Override public void showError(Exception e) { toastMessage(e()); } }
Introduce MVP @Override protected void onCreate(@Nullable Bundle savedInstanceState) { final
UserPresenter presenter = new UserPresenter(this, userFetcher); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.clickButton(userId); } }); } @Override public void showUser(User user) { toastMessage(user.getName()); } @Override public void showError(Exception e) { toastMessage(e()); } }
Introduce MVP @Override protected void onCreate(@Nullable Bundle savedInstanceState) { final
UserPresenter presenter = new UserPresenter(this, userFetcher); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.clickButton(userId); } }); } @Override public void showUser(User user) { toastMessage(user.getName()); } @Override public void showError(Exception e) { toastMessage(e()); } }
Introduce MVP @Override protected void onCreate(@Nullable Bundle savedInstanceState) { final
UserPresenter presenter = new UserPresenter(this, userFetcher); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { presenter.clickButton(userId); } }); } @Override public void showUser(User user) { toastMessage(user.getName()); } @Override public void showError(Exception e) { toastMessage(e()); } }
Result • Now objects' responsibilities are clearer • Dependency is
provided at constructor • Thus testable!
Make biggest gains without Unit Testing
It turned out...
Quite a few crashes were caused by...
IllegalStateException java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
IllegalStateException java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState 'SBHNFOU5SBOTBDUJPODPNNJU
FragmentTransaction • FragmentTransaction#commit • DialogFragment#show
FragmentTransaction public void show(FragmentManager manager, String tag) { mDismissed =
false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commit(); }
FragmentTransaction public void show(FragmentManager manager, String tag) { mDismissed =
false; mShownByMe = true; FragmentTransaction ft = manager.beginTransaction(); ft.add(this, tag); ft.commit(); }
Don't commit after... • AsyncTask#onPostExecute • LoaderCallbacks#onLoadFinished • Actually ANY
KINDS OF ASYNC CALLBACKS are not safe
RxLifecycle?
RxLifecycle • RxLifecycle IS GREAT!! • Not all programmers, nor
projects always need this. • Vulture is for you!
Vulture • https://github.com/srym/vulture • Handles async callback safely • Based
on PauseHandler* picture from wikimedia.org * https://goo.gl/g6w3CS
Vulture void fetchAsynchronously() { /* do heavy asynchronous task here
*/ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); }
Vulture void fetchAsynchronously() { /* do heavy asynchronous task here
*/ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); }
Vulture void fetchAsynchronously() { /* do heavy asynchronous task here
*/ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); }
Vulture void fetchAsynchronously() { /* do heavy asynchronous task here
*/ doCallback("finished!"); } void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } 6OTBGF
Vulture @ObserveLifecycle public class YourActivity extends AppCompatActivity { void fetchAsynchronously()
{ SafeMainActivity.doCallbackSafely("finished!"); } @SafeCallback void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } }
Vulture @ObserveLifecycle public class YourActivity extends AppCompatActivity { void fetchAsynchronously()
{ SafeMainActivity.doCallbackSafely("finished!"); } @SafeCallback void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } }
Vulture @ObserveLifecycle public class YourActivity extends AppCompatActivity { void fetchAsynchronously()
{ SafeMainActivity.doCallbackSafely("finished!"); } @SafeCallback void doCallback(@NonNull String message) { FinishDialog.newInstance().show( getSupportFragmentManager(), "TAG"); } }
Vulture @Override protected void onResume() { super.onResume(); SafeMainActivity.register(this); } @Override
protected void onPause() { SafeMainActivity.unregister(); super.onPause(); }
Vulture @Override protected void onResume() { super.onResume(); SafeMainActivity.register(this); } @Override
protected void onPause() { SafeMainActivity.unregister(); super.onPause(); }
Supported Types • Primitive types and its boxed types •
String, Bundle, Parcelable, Serializable • ParcelableArray • ParcelableArrayList
Vulture • Currently Beta release • I need your feedbacks!
picture from wikimedia.org
Thank You!
Special Thanks • Illustrations by ͍Β͢ͱ • http://www.irasutoya.com/ • Thank
you so much!!! • https://www.wikimedia.org/ • All the audiences! Thank you!