More Related Content
Similar to AsyncTask アンチパターン (20)
AsyncTask アンチパターン
- 2. 背景
非同期処理で AsyncTask が使われているコードをよく見か
けるが、けっこう気軽に使われている
非同期処理の問題は起きるかどうかがタイミング次第なこ
とが多いので、再現や解決が困難なバグを生む
ネットに転がっている情報や解決方法は間違っていること
が多い印象
非同期処理は、人間にはまだ早すぎる...
- 3. ネタ元参考文献
Processes and Threads | 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) エ
ラーが表示される
描画に関係しない重い処理はバックグラウンドで処理する
- 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
画面回転などで Configuration Chage が発生すると、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> {
...
}
- 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 の利用も検討する