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
4.2k
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
140
AWS Device FarmとCircleCIでAndroidのUIテストを自動化しよう
srym
1
5.1k
Spring BootをKotlinで作成しAmazon Elastic Container Service (ECS) で稼働させる
srym
6
2k
iOSDC_2019_DeviceFarm.pdf
srym
8
20k
世界で戦うエンジニアになるために_公開用.pdf
srym
18
45k
Unit Testing in a Nutshell - DroidKaigi 2018
srym
11
15k
Clean Architecture & TDD
srym
1
3.8k
はやい・やすい・うまい!スタートアップでも使える Retrofit + RxJava で瞬間APIクッキングレシピ
srym
2
630
I/O 2017 Short Report
srym
0
310
Other Decks in Technology
See All in Technology
アジャイル脅威モデリング#1(脅威モデリングナイト#8)
masakane55
3
190
PicoRabbit: a Tiny Presentation Device Powered by Ruby
harukasan
PRO
2
170
Porting PicoRuby to Another Microcontroller: ESP32
yuuu
3
380
50人の組織でAIエージェントを使う文化を作るためには / How to Create a Culture of Using AI Agents in a 50-Person Organization
yuitosato
6
3.4k
DETR手法の変遷と最新動向(CVPR2025)
tenten0727
2
1.3k
От ручной разметки к LLM: как мы создавали облако тегов в Lamoda. Анастасия Ангелова, Data Scientist, Lamoda Tech
lamodatech
0
690
はてなの開発20年史と DevOpsの歩み / DevOpsDays Tokyo 2025 Keynote
daiksy
6
1.5k
クォータ監視、AWS Organizations環境でも楽勝です✌️
iwamot
PRO
1
270
watsonx.data上のベクトル・データベース Milvusを見てみよう/20250418-milvus-dojo
mayumihirano
0
110
AWS全冠芸人が見た世界 ~資格取得より大切なこと~
masakiokuda
5
5.4k
Vision Pro X Text to 3D Model ~How Swift and Generative Al Unlock a New Era of Spatial Computing~
igaryo0506
0
260
.mdc駆動ナレッジマネジメント/.mdc-driven knowledge management
yodakeisuke
24
12k
Featured
See All Featured
Being A Developer After 40
akosma
91
590k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
331
21k
Site-Speed That Sticks
csswizardry
5
480
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
34
2.2k
jQuery: Nuts, Bolts and Bling
dougneiner
63
7.7k
Unsuck your backbone
ammeep
670
57k
Reflections from 52 weeks, 52 projects
jeffersonlam
349
20k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
13
670
Build The Right Thing And Hit Your Dates
maggiecrowley
35
2.6k
A designer walks into a library…
pauljervisheath
205
24k
Thoughts on Productivity
jonyablonski
69
4.6k
Faster Mobile Websites
deanohume
306
31k
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!