Androidはメインスレッド(UIスレッドとも呼ばれる)でユーザの操作を受け付けたり画面の描画処理を行っている。そのため外部との通信や大量のデータ処理等、時間のかかる処理をメインスレッドで走らせるとアプリが無反応になってしまう。あまりに処理時間が長いとOSがアプリを強制終了させてしまうこともあるので重たい処理は別スレッドで実行するよう推奨されている
そんなわけで別スレッドで処理を実行する方法を記しておく。
Threadクラスを継承する方法
Threadクラスを継承したクラスを作成し、別スレッドで実行したい非同期処理をrun()メソッドをオーバーライドして記述する。
class ThreadSleep1Second extends Thread {
@Override
public void run() {
sleep(1000); //1秒待機
}
}
上の例は1秒間待機するだけのクラスだが、通常はrun()メソッドにもっと長いコードを書くことになる。
クラスの定義ができれば、そのインスタンスを作成し、start()メソッドを呼び出す。run()メソッドを直接呼ぶと、別スレッドではなくメインスレッドで実行されてしまうので注意が必要だ。
ThreadSleep1Second thread1 = new ThreadSleep1Second();
thread1.start();
上の例のThreadSleep1Secondクラスは1秒待機したらそのまま何もせずに終了してしまう。処理が終了したことをメインスレッドに通知するにはHandlerクラスを使う。下の例は1秒待機した後にToastでメッセージを表示している。
class ThreadSleep1Second extends Thread {
private Context context;
public ThreadSleep1Second (Context context) {
this.context = context;
}
@Override
public void run() {
sleep(1000); //1秒待機
Handler mainThreadHandler = new Handler(Looper.getMainLooper());
mainThreadHandler.post(new Runnable() {
@Override
public void run() {
//メインスレッドで実行する処理
Toast.makeText(this.context,"1 second", Toast.LENGTH_SHORT).show();
}
});
}
}
ネットを検索するとHandlerをメインスレッドで作成して、コンストラクタの引数で渡している例があるが、ローカル変数でもインスタンス作成時にメインスレッドのLooper(Looper.getMainLooper())を指定することでメインスレッドにMessageやRunnableを送ることができる。
Handlerの使い方はこちらのサイトが参考になるので見て欲しい
https://qiita.com/8yabusa/items/f8c9bb7eb81175c49e97
http://d.hatena.ne.jp/sankumee/20120329/1333021847
AsyncTaskクラスを継承する方法
Threadクラスでスレッドを作ると、メインスレッドとのやり取りにHandler、Runnable、Messageといったクラスを組み合わせて記述しなければならない。そして大抵の場合、無名クラスを多用してコードがあちこちに散らばってしまい、可読性が悪くなる。そういった面倒をなくすためにパッケージ化したのがAsyncTaskクラスである。
■AsyncTaskに用意されている主なメソッド
onPreExecute()
doInBackgroundメソッドの実行前にメインスレッドで実行される。
doInBackground()
メインスレッドとは別のスレッドで実行される。ThreadクラスのRun()メソッドに相当する。このメソッドだけは必ず実装する必要がある。
onProgressUpdate()
メインスレッドで実行される。非同期処理の進行状況をプログレスバーで表示したい時などに使う。
onPostExecute()
doInBackgroundメソッドの実行後にメインスレッドで実行さる。doInBackgroundメソッドの戻り値をこのメソッドの引数として受け取り、その結果を画面に反映させることができる。
前出例のThreadSleep1Secondクラスと同じ内容をAsyncTaskクラスを使って書くと次のようになる
class AsyncSleep1Second extends AsyncTask<Void, Void, Void>{
private Context context;
public AsyncSleep1Second (Context context) {
this.context = context;
}
@Override
protected void onPreExecute() {
//何もしない
}
@Override
protected Void doInBackground(Void... voids){
Thread.sleep(1000); //1秒待機
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
Toast.makeText(this.context, "1 second", Toast.LENGTH_SHORT).show();
}
}
このように別スレッドで実行される処理の他に、前処理や後処理も同じクラスの中に書くことができる。
これのインスタンスを作成し、execute()メソッドを呼ぶと別スレッドで非同期処理が実行される。
AsyncSleep1Second task = new AsyncSleep1Second(this);
task.execute();
ジェネリクスの指定は<Void, Void, Void>となっているが、意味は次の通り。
第一変数
doInBackground()の引数の型。
execute()メソッドの引数が配列で渡される
値が一つだけの時はインデックス0で取得する
第二変数
onProgressUpdate()の引数の型
第三変数
onPostExecute()の引数の型
doInBackground()の戻り値がonPostExecute()の引数となる
例では引数を使用していないのですべてVoid型になっている。
個人的にはAsyncTaskが簡潔にコードを記述できるので多用しているが、スレッド間で複雑な情報のやり取りが発生する場合はThreadクラスを使っている。どちらのやり方も覚えておいて損はない。