非同期処理が必要なケースは、メインスレッドでアプリがUIを表示させたり、ユーザーが入力したりしている裏でいろいろと処理をして欲しい場合です。(API 30から非推奨)
API 29
AsyncTask
代案は、java.util.concurrent あるいは Kotlin concurrency utilities のようです
非同期にはHandlerやThreadを使いますが、そのHelper ClassとしてAsyncTaskがあり簡単に非同期を扱うことができます。
また、GoogleのReferenceによれば、スレッドを長期間継続させる場合には使用せず、数秒程度の処理にすべきとのことです。
AsyncTaskの基本的構造
AsynTaskはbackground Threadで実行されその結果をUI threadに表示させます。基本的な構造は、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class TestTask extends AsyncTask<Integer, Integer, Integer> { // 非同期処理 @Override protected Integer doInBackground(Integer... params) { return params[0] ; } // 途中経過をメインスレッドに返す @Override protected void onProgressUpdate(Integer... progress) { // ... } // 非同期処理が終了後、結果をメインスレッドに返す @Override protected void onPostExecute(Integer result) { // ... } } |
非同期を実装するためにAsyncTaskを継承して作ります。
1 |
AsyncTask<Params, Progress, Result> |
<Param, …>はジェネリクスな変数を意味しています。
変数型にはInteger型やString型などがあるのですが、それぞれのデータ型で同じように処理することが出来きるようにするためのものです、なのでここにはStringやIntegerなどを入れて使えます。
その変数は3つのメソッドに関連しています
- doInBackground(Params…)
- バックグラウンドでの非同期処理、何か重い処理を記述する
- ここに渡されるパラメータの型は1つめのParams
- onProgressUpdate(Progress…)
- 途中経過をメインスレッドで表示する場合
- doInBackground内でpublishProgressを設定するとメインスレッドから呼ばれ、パラメータの型は2つめのProgress
- onPostExecute(Result)
- バックグラウンドでの非同期処理が終了後メインスレッドに結果を返す
- パラメータは3つめのResult
このクラスの非同期を実行させるためにはexecute(…)をメインスレッドから呼び出します。実際に使ってみましょう。
AsyncTaskの実装
このサンプルは重たい処理として1秒間のスリープを10回繰り返し、毎回のスリープをListenerで知らせてViewに表示させる例です。
非同期の他にinterfaceを使ってListenerを実装していますが、それは後半に出てきます。
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
//package your.package.name; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends AppCompatActivity { private TestTask testTask; private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.text_view); Button buttonStart = findViewById(R.id.button_start); buttonStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // ボタンをタップして非同期処理を開始 testTask = new TestTask(); // Listenerを設定 testTask.setListener(createListener()); testTask.execute(0); } }); Button buttonClear = findViewById(R.id.button_clear); buttonClear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { textView.setText(String.valueOf(0)); } }); } @Override protected void onDestroy() { testTask.setListener(null); super.onDestroy(); } private TestTask.Listener createListener() { return new TestTask.Listener() { @Override public void onSuccess(int count) { textView.setText(String.valueOf(count)); } }; } } |
非同期処理を行うクラスです。パラメータはintを使いたいのですが、ジェネリックに指定できるのは参照型のみで基本型のintは使えません。それでIntegerでラッパークラスを使用します。
TestTask.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
//package your.package.name; import android.os.AsyncTask; import android.util.Log; public class TestTask extends AsyncTask<Integer, Integer, Integer> { private Listener listener; // 非同期処理 @Override protected Integer doInBackground(Integer... params) { // 10秒数える処理 do{ try { // 1sec sleep Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } Log.d("debug",""+params[0]); params[0]++; // 途中経過を返す publishProgress(params[0]); }while(params[0]<10); return params[0] ; } // 途中経過をメインスレッドに返す @Override protected void onProgressUpdate(Integer... progress) { if (listener != null) { listener.onSuccess(progress[0]); } } // 非同期処理が終了後、結果をメインスレッドに返す @Override protected void onPostExecute(Integer result) { if (listener != null) { listener.onSuccess(result); } } void setListener(Listener listener) { this.listener = listener; } interface Listener { void onSuccess(int count); } } |
レイアウトです
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" tools:context=".MainActivity"> <TextView android:id="@+id/text_view" android:layout_margin="20dp" android:textSize="200sp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/zero" /> <Button android:id="@+id/button_start" android:text="@string/start" android:layout_margin="10dp" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:id="@+id/button_clear" android:text="@string/clear" android:layout_margin="10dp" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> |
リソースです
strings.xml
1 2 3 4 5 6 |
<resources> <string name="app_name">YourAppName</string> <string name="zero">0</string> <string name="start">Start</string> <string name="clear">Clear</string> </resources> |
AsyncTaskのwarning
「This field leaks a context object …」
こんな warning でこのページを見つけたのであれば、ドンピシャです。
非同期側からActivityやViewのインスタンスを参照するとリークするぞという警告です。非同期の作業が終わったらTextViewで結果を表示させようとした例です。
WeakReferenceにするという方法もありますが、どうもしっくり来ないと思っていたところ同じ気持ちの人を発見しました。
You don’t have to use WeakReference to avoid memory leaks
Novodaのエキスパートらしいのですが、WeakReferenceは最終手段でNovodaでは使っとらへんでーということらしいです。Listenerを使うのですが、至極理にかなっていますね。
非同期作業を別にやらせているので、メインスレッドからはいつ作業が終わるのかわかりません。毎回ポーリングするのは無駄が多いですし、作業が終わったら教えてくれればいいのです。それを聞く耳は立てておくということでしょう。
Listenerを独自に作成する方法はこちらを参照してください。
余談ですが、syncは同期という意味合いですが、asyncで言う「a」は接頭辞で「not」の意味を持ちます。リスニングでは聞き逃しそうですね。
関連ページ:
- カスタム Listener を interface を使って実装してみる
- 非同期処理 AsyncTaskの使い方
- HttpURLConnection POST でデータを送信する
- HttpURLConnection GET で画像をダウンロードする
References:
GoogleのReference
ThreadPoolExecutor | Android Developers