AsyncTask アンチパターン 
2014-12-04 Android アンチパターン勉強会 
黒川 洋 / @hydrakecat
背景 
非同期処理で AsyncTask が使われているコードをよく見か 
けるが、けっこう気軽に使われている 
非同期処理の問題は起きるかどうかがタイミング次第なこ 
とが多いので、再現や解決が困難なバグを生む 
ネットに転がっている情報や解決方法は間違っていること 
が多い印象 
非同期処理は、人間にはまだ早すぎる...
ネタ元参考文献 
Processes and Threads | Android Developers 
Handling Runtime Changes | Android Developers 
Efficient Android Threading (Anders Goransson) 
Android の非同期処理についての解説。Thread や Looper か 
ら解説してあって勉強になる。
事前知識(おさらい) 
Android では View の操作を UI スレッド(メインスレッ 
ド)からしかできない 
重い処理を UI スレッドで行うと、ユーザーからはアプリが 
反応しないように見える 
5秒以上応答がないと ANR (Application Not Responding) エ 
ラーが表示される 
描画に関係しない重い処理はバックグラウンドで処理する
Android の非同期処理 
Basic Thread 
HandlerThread 
Executor 
AsyncTask 
Services 
AsyncQueryHandler 
Loader
Android の非同期処理 
Basic Thread 
HandlerThread 
Executor 
AsyncTask 
Services 
AsyncQueryHandler 
Loader
AsyncTask とは 
http://developer.android.com/reference/android/os/AsyncTask.html 
バックグラウンド処理を行って結果を UI スレッドに渡すた 
めのクラス。 
Thread や Handler のヘルパークラス。 
基本的には数秒程度の処理を行うのに使うもので、それ以 
上の処理を行いたい場合は、Executor などの使用が推奨さ 
れている。 
非常によく使われている。というか、非同期処理は、一般的 
に AsyncTask がむやみに使われていない印象。
AsyncTask の罠 (その1) 
@Override 
public void onCreate(Bundle savedInstanceState) { 
super.onCreate(savedInstanceState); 
... 
new AsyncTask<Void, String, Void>(){ 
@Override 
protected void onPreExecute() { 
super.onPreExecute(); 
mProgressDialog.show(); 
} 
@Override 
protected String doInBackground(Void... value) { 
try { 
Thread.sleep(15000); 
} catch (InterruptedException e) { 
} 
return "AsyncTask Done"; 
} 
@Override 
protected void onPostExecute(String result) { 
mProgressDialog.dismiss(); 
mTextView.setText(result); 
} 
}.execute();
IllegalArgumentException 
画面回転などで Configuration Chage が発生すると、Activity 
の再生成が行われる 
→ その際に Fragment が detach されるので 
mProgressDialog.dismiss() で 
IllegalArgumentException が発生する。 
ウェブの記事では、画面固定を推奨しているものもあるが、 
画面回転以外(キーボードの表示とか)にも Configuration 
Change は起き得るので、これではダメ。
IllegalArgumentException の 
とりあえずの対処 
onDestroy 時にダイアログを閉じて null にしておく 
@Override 
public void onCreate(Bundle savedInstanceState) { 
... 
new AsyncTask<Void, Void, String>(){ 
... 
@Override 
protected void onPostExecute(String result) { 
if (mProgressDialog != null) { 
mProgressDialog.dismiss(); 
} 
if (mTextView != null) { 
mTextView.setText(result); 
} 
} 
}.execute(); 
} 
@Override 
public void onDestroy() { 
if (mProgressDialog != null && mProgressDialog.isShowing()) { 
というか、mProgressDialog.ガイドライdismiss(); 
ン通り、ProgressDialog は使わない! 
} 
mProgressDialog = null; 
}
問題点 
Configuration change が起きて、Activity が再生成になる 
と、AsyncTask の結果は捨てられることになる 
それが嫌なら、Thread や AsyncTaskLoader などを使っ 
て、再生成された Activity にスレッドをアタッチする
AsyncTask の罠 (その2) 
Fragment#getActivity() が null になることがある 
画面回転などで Configuration Chage が発生すると、Fragment 
が detach されてしまう 
@Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
Bundle savedInstanceState) { 
... 
new AsyncTask<Void, Void, String[]>(){ 
... 
@Override 
protected void onPostExecute(String[] result) { 
mAdapter = new ArrayAdapter<String>(getActivity(), R.layout.listrow, result); 
mListView.setAdapter(mAdapter); 
} 
}.execute(); 
}
NullPointerException の対処 
Fragment#isAdded()で確認する 
あと、個人的には mAdapter はコールバックで作らない方 
が好き 
@Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
Bundle savedInstanceState) { 
... 
new AsyncTask<Void, Void, String[]>(){ 
... 
@Override 
protected void onPostExecute(String[] result) { 
if (isAdded() && mAdapter != null) { 
mAdapter.addAll(Arrays.asList(result)); 
mAdapter.notifyDatasetChanged(); 
} 
} 
}.execute(); 
}
AsyncTask の罠 (その3) 
長い処理を行うとメモリリークの可能性がある 
task = new AsyncTask<Void, String, Void>(){ 
... 
}.execute(); 
non-static な内部クラスは暗黙に親オブジェクト(Activity 
オブジェクト)への参照を持っている 
AsyncTask が内部的に利用しているスレッドが生きている 
限り、このオブジェクトは GC されない 
出典: Efficient Android Threading
メモリリークの対処法 
non-static な内部クラスにせず、static な内部クラスにする 
か別クラスにする 
Activity#onDestory() 時に Activity への参照を破棄し 
て AsyncTask#cancel() を呼ぶ 
もしくは、Activity への参照を弱参照(Weak Reference) 
にする 
@Override public void onCreate(Bundle savedInstanceState) { 
... 
mTask = new MyTask(this); 
mTask.execute(); 
} 
@Override protected void onDestroy () { 
super.onDestroy(); 
mTask.setActivity(null); 
mTask.cancel(true); 
} 
private static class MyTask extends AsyncTask<String, Bitmap, Void> { 
... 
}
メモリリークの対処法(再びAsyncTask#cancel(false)は、isCanceledをtrueに 
セットして、実行中のタスクが終わるまで待機する。 
AsyncTask#cancel(true)にしても、即座にスレッドが 
終了することを保証しない。 
private static class MyTask extends AsyncTask<Void, Void, String> { 
@Override 
protected String doInBackground(Void... params) { 
for (int i=0; i<NUM_TASKS; i++) { 
if (this.isCancelled()) { 
return null; 
} else { 
try { 
// Do any task 
} catch (InterruptedException iex) { 
// the blocking method throws an InterruptedException 
return null; 
} 
} 
} 
return "AsyncTask Done"; 
} 
}
AsyncTask の罠 (その4) 
AsyncTask を複数実行したときに、逐次実行される 
(sequential)か、同時実行される(concurrent)かは、呼 
び出し方、APIレベルで変わる 
実行環境はアプリケーション全体で同一(ある Service の 
AsyncTask が別 Activity の AsyncTask をブロックしうる) 
API targetSdkVersion execute executeOnExecutor 
1-3 Any Sequential Not available 
4-10 Any Concurrent Not available 
11-12 Any Concurrent Sequential/Concurrent (customizable) 
13+ <13 Concurrent Sequential/Concurrent (customizable) 
13+ ≥13 Sequential Sequential/Concurrent (customizable)
API レベルに関わらず処理を 
同じにするには 
逐次実行 
2.3系をサポートする限り無理です。AsyncTaskを諦めてく 
ださい。 
同時実行 
targetSdkVersion を < 13 にするか、API レベルによって処理 
を変える必要があります。 
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR1) { 
new MyAsyncTask().execute(); 
} else { 
new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 
}
まとめ 
オススメの AsyncTask の使い方 
軽い処理だけにしておく 
non-static な inner class にしない 
Activity#onDestroy()で、キャンセルと Activity の参 
照の解放を忘れずに行う 
場合に応じて AsyncTaskLoader 、 Executor 、 
HandlerThread の利用も検討する

AsyncTask アンチパターン

  • 1.
    AsyncTask アンチパターン 2014-12-04Android アンチパターン勉強会 黒川 洋 / @hydrakecat
  • 2.
    背景 非同期処理で AsyncTaskが使われているコードをよく見か けるが、けっこう気軽に使われている 非同期処理の問題は起きるかどうかがタイミング次第なこ とが多いので、再現や解決が困難なバグを生む ネットに転がっている情報や解決方法は間違っていること が多い印象 非同期処理は、人間にはまだ早すぎる...
  • 3.
    ネタ元参考文献 Processes andThreads | Android Developers Handling Runtime Changes | Android Developers Efficient Android Threading (Anders Goransson) Android の非同期処理についての解説。Thread や Looper か ら解説してあって勉強になる。
  • 4.
    事前知識(おさらい) Android ではView の操作を UI スレッド(メインスレッ ド)からしかできない 重い処理を UI スレッドで行うと、ユーザーからはアプリが 反応しないように見える 5秒以上応答がないと ANR (Application Not Responding) エ ラーが表示される 描画に関係しない重い処理はバックグラウンドで処理する
  • 5.
    Android の非同期処理 BasicThread HandlerThread Executor AsyncTask Services AsyncQueryHandler Loader
  • 6.
    Android の非同期処理 BasicThread HandlerThread Executor AsyncTask Services AsyncQueryHandler Loader
  • 7.
    AsyncTask とは http://developer.android.com/reference/android/os/AsyncTask.html バックグラウンド処理を行って結果を UI スレッドに渡すた めのクラス。 Thread や Handler のヘルパークラス。 基本的には数秒程度の処理を行うのに使うもので、それ以 上の処理を行いたい場合は、Executor などの使用が推奨さ れている。 非常によく使われている。というか、非同期処理は、一般的 に AsyncTask がむやみに使われていない印象。
  • 8.
    AsyncTask の罠 (その1) @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... new AsyncTask<Void, String, Void>(){ @Override protected void onPreExecute() { super.onPreExecute(); mProgressDialog.show(); } @Override protected String doInBackground(Void... value) { try { Thread.sleep(15000); } catch (InterruptedException e) { } return "AsyncTask Done"; } @Override protected void onPostExecute(String result) { mProgressDialog.dismiss(); mTextView.setText(result); } }.execute();
  • 9.
    IllegalArgumentException 画面回転などで ConfigurationChage が発生すると、Activity の再生成が行われる → その際に Fragment が detach されるので mProgressDialog.dismiss() で IllegalArgumentException が発生する。 ウェブの記事では、画面固定を推奨しているものもあるが、 画面回転以外(キーボードの表示とか)にも Configuration Change は起き得るので、これではダメ。
  • 10.
    IllegalArgumentException の とりあえずの対処 onDestroy 時にダイアログを閉じて null にしておく @Override public void onCreate(Bundle savedInstanceState) { ... new AsyncTask<Void, Void, String>(){ ... @Override protected void onPostExecute(String result) { if (mProgressDialog != null) { mProgressDialog.dismiss(); } if (mTextView != null) { mTextView.setText(result); } } }.execute(); } @Override public void onDestroy() { if (mProgressDialog != null && mProgressDialog.isShowing()) { というか、mProgressDialog.ガイドライdismiss(); ン通り、ProgressDialog は使わない! } mProgressDialog = null; }
  • 11.
    問題点 Configuration changeが起きて、Activity が再生成になる と、AsyncTask の結果は捨てられることになる それが嫌なら、Thread や AsyncTaskLoader などを使っ て、再生成された Activity にスレッドをアタッチする
  • 12.
    AsyncTask の罠 (その2) Fragment#getActivity() が null になることがある 画面回転などで Configuration Chage が発生すると、Fragment が detach されてしまう @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ... new AsyncTask<Void, Void, String[]>(){ ... @Override protected void onPostExecute(String[] result) { mAdapter = new ArrayAdapter<String>(getActivity(), R.layout.listrow, result); mListView.setAdapter(mAdapter); } }.execute(); }
  • 13.
    NullPointerException の対処 Fragment#isAdded()で確認する あと、個人的には mAdapter はコールバックで作らない方 が好き @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ... new AsyncTask<Void, Void, String[]>(){ ... @Override protected void onPostExecute(String[] result) { if (isAdded() && mAdapter != null) { mAdapter.addAll(Arrays.asList(result)); mAdapter.notifyDatasetChanged(); } } }.execute(); }
  • 14.
    AsyncTask の罠 (その3) 長い処理を行うとメモリリークの可能性がある task = new AsyncTask<Void, String, Void>(){ ... }.execute(); non-static な内部クラスは暗黙に親オブジェクト(Activity オブジェクト)への参照を持っている AsyncTask が内部的に利用しているスレッドが生きている 限り、このオブジェクトは GC されない 出典: Efficient Android Threading
  • 15.
    メモリリークの対処法 non-static な内部クラスにせず、staticな内部クラスにする か別クラスにする Activity#onDestory() 時に Activity への参照を破棄し て AsyncTask#cancel() を呼ぶ もしくは、Activity への参照を弱参照(Weak Reference) にする @Override public void onCreate(Bundle savedInstanceState) { ... mTask = new MyTask(this); mTask.execute(); } @Override protected void onDestroy () { super.onDestroy(); mTask.setActivity(null); mTask.cancel(true); } private static class MyTask extends AsyncTask<String, Bitmap, Void> { ... }
  • 16.
    メモリリークの対処法(再びAsyncTask#cancel(false)は、isCanceledをtrueに セットして、実行中のタスクが終わるまで待機する。 AsyncTask#cancel(true)にしても、即座にスレッドが 終了することを保証しない。 private static class MyTask extends AsyncTask<Void, Void, String> { @Override protected String doInBackground(Void... params) { for (int i=0; i<NUM_TASKS; i++) { if (this.isCancelled()) { return null; } else { try { // Do any task } catch (InterruptedException iex) { // the blocking method throws an InterruptedException return null; } } } return "AsyncTask Done"; } }
  • 17.
    AsyncTask の罠 (その4) AsyncTask を複数実行したときに、逐次実行される (sequential)か、同時実行される(concurrent)かは、呼 び出し方、APIレベルで変わる 実行環境はアプリケーション全体で同一(ある Service の AsyncTask が別 Activity の AsyncTask をブロックしうる) API targetSdkVersion execute executeOnExecutor 1-3 Any Sequential Not available 4-10 Any Concurrent Not available 11-12 Any Concurrent Sequential/Concurrent (customizable) 13+ <13 Concurrent Sequential/Concurrent (customizable) 13+ ≥13 Sequential Sequential/Concurrent (customizable)
  • 18.
    API レベルに関わらず処理を 同じにするには 逐次実行 2.3系をサポートする限り無理です。AsyncTaskを諦めてく ださい。 同時実行 targetSdkVersion を < 13 にするか、API レベルによって処理 を変える必要があります。 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR1) { new MyAsyncTask().execute(); } else { new MyAsyncTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }
  • 19.
    まとめ オススメの AsyncTaskの使い方 軽い処理だけにしておく non-static な inner class にしない Activity#onDestroy()で、キャンセルと Activity の参 照の解放を忘れずに行う 場合に応じて AsyncTaskLoader 、 Executor 、 HandlerThread の利用も検討する