SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

2020年版Androidの非同期処理

JavaによるAndroid非同期処理の基本

2020年版Androidの非同期処理 第1回

  • X ポスト
  • このエントリーをはてなブックマークに追加

UIスレッドとの連携

 前節の内容で、非同期処理の基本形がほぼできあがり、これで問題なく動作するように思えます。しかし、ここにはAndroid特有の問題が含まれています。そこを解決していきましょう。

バックグランド処理の結果をUIスレッドに戻す

 前節で紹介した非同期処理のコードパターンを図2のような図に起こしてみると、図3のようになります。

図3:ExecutorServiceを利用した処理の流れ
図3:ExecutorServiceを利用した処理の流れ

 例えば、ネット上から画像ファイルをダウンロードしてストレージに格納する処理といったような、バックグラウンド処理の内容が処理終了後もそのままで問題ないならば、つまり、処理がバックグラウンドのみで完結するならば、リスト1のコードパターンで問題ありません。

 一方、本稿が題材にしている、お天気情報をWeb APIから取得して、その内容を画面に表示させる処理といったように、バックグラウンド処理の終了後、その結果をもとにUIスレッドで何か処理を行わないといけない場合は、もう一工夫必要です(図4)。

図4:非同期処理の結果を受けてUIスレッドで処理を行う必要がある
図4:非同期処理の結果を受けてUIスレッドで処理を行う必要がある

非同期処理をUIスレッドに戻すHandler

 バックグラウンド処理の終了後に、その結果をもとにUIスレッドで処理を行おうとすると、実は、ピュアJavaだけでは難しいです。それを想定してか、Androidでは、独自のSDKとして、その仕組みを実現するクラスとしてHandlerとLooperというのが用意されています。これらを利用します。

 Handleは、スレッド間の通信を行ってくれるオブジェクトです。元となるスレッドで事前にHandlerオブジェクトを用意しておき、これを、BackgroundTaskに渡します。BackgroundTaskのrun()メソッド内で、このオブジェクトのpost()メソッドを実行することで、その時点でHandlerオブジェクトを生成した元スレッドで処理を行ってくれます。その処理内容を、これまたRunnableインスタンスとしてpost()の引数に渡します。

 ここまでの内容をコードパターンにすると、リスト2のようになります。

[リスト2]MainActivity.java
private class BackgroundTask implements Runnable {
	private final Handler _handler;  // (1)
	public BackgroundTask(Handler handler) {  // (2)
		_handler = handler;  // (2)
	}
	@Override
	public void run() {
		Log.i("Async-BackgroundTask", "ここに非同期処理を記述する");
		PostExecutor postExecutor = new PostExecutor();
		_handler.post(postExecutor);  // (3)
	}
}

private class PostExecutor implements Runnable {  // (4)
	@Override
	public void run() {
		Log.i("Async-PostExecutor", "ここにUIスレッドで行いたい処理を記述する");  // (5)
	}
}


 先述の通り、Handlerオブジェクトは、元となるスレッドで用意する必要があります。ということは、バックグラウンドスレッドで実行するBackgroundTaskでは、これを受け取り、内部で保持する必要があります。それを実現するために、コンストラクタで受け取るようにしています(リスト2の(2))。コンストラクタで受け取ったHandlerオブジェクトを格納するためのフィールドがリスト2の(1)です。ただし、このような複数スレッドを跨いで利用される変数は、各スレッドから書き換えが起きれば大変です。そこで書き換えができないようにfinalキーワードを付与して、複数のスレッドで問題なく動作するようにしておきます。このことを、スレッドセーフといいます。

 そのようにして利用できるようにしたHandlerオブジェクトのpost()メソッドを実行しているのが(3)です。非同期処理を実行した後に実行しています。その引数として渡しているのが、(4)のPostExecutorをnewしたオブジェクトです。この(4)のrun()メソッドに、バックグラウンド処理終了後にUIスレッドで実行したい処理を記述することで、非同期で処理を戻すことが可能となります。リスト2では、(5)が該当し、ここでも単純にログへの書き出し処理のみを行っています。

確実にUIスレッドに処理を戻すにはLooperが必要

 ところで、リスト2では、そもそもHandlerオブジェクトの取得方法が記述されていません。実は、Handlerオブジェクトは、あくまでスレッド間通信を行うためのオブジェクトであり、バックグラウンド処理の後に確実にUIスレッドを実行させるためには、Looperオブジェクトに登場してもらう必要があります。これも、Android SDKで用意されたクラスです。Handlerオブジェクトを生成する際に、LooperのgetMainLooper()メソッドの戻り値を渡すことで、確実にgetMainLooper()を実行したスレッド、つまり、UIスレッドに処理を戻すことができます。

 ここまでの内容をまとめたコードパターンは、リスト3のようになります。

[リスト3]MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
	〜省略〜
	Looper mainLooper = Looper.getMainLooper();  // (1)
	Handler handler = HandlerCompat.createAsync(mainLooper);  // (2)
	BackgroundTask backgroundTask = new BackgroundTask(handler);  // (3)
	ExecutorService executorService  = Executors.newSingleThreadExecutor();
	executorService.submit(backgroundTask);
}

private class BackgroundTask implements Runnable {
	private final Handler _handler;
	public BackgroundTask(Handler handler) {
		_handler = handler;
	}
	@Override
	public void run() {
		Log.i("Async-BackgroundTask", "ここに非同期処理を記述する");
		PostExecutor postExecutor = new PostExecutor();
		_handler.post(postExecutor);
	}
}

private class PostExecutor implements Runnable {
	@Override
	public void run() {
		Log.i("Async-PostExecutor", "ここにUIスレッドで行いたい処理を記述する");
	}
}

 リスト3の(1)で、LooperのgetMainLooper()メソッドを実行し、その戻り値をmainLooperとしています。この実行処理をUIスレッドで行うことで、mainLooperが確実にUIスレッドを表すことになります。これを引数として、リスト(2)のようにHandlerCompatのcreateAsync()メソッドを利用してHandlerオブジェクトを生成します。この段階で、このHandlerオブジェクトであるhandlerが、戻り先としてUIスレッドを保証してくれます。これを、バックグラウンド処理を行うBackgroundTaskに渡します。それが、リスト3の(3)です。

@UIThreadと@WorkerThread

 リスト3のコードパターンで、バックグラウンド処理、および、その後のUIスレッドでの処理パターンに対応したコードが記述できました。これらを受けて、もう一段階進めて完成に近づけます。

 Androidでは、ここまで説明したように、UIスレッドとバックグラウンド処理であるワーカースレッドとのやり取りを想定したSDK設計になっています。そのため、それぞれの処理を記述したソースコードには、それぞれに対応したアノテーションを付与できるようになっています。

 リスト3では、(1)のmainLooperの取得からsubmit()の実行までがUIスレッドでの処理となります。また、PostExecutorのrun()メソッドもUIスレッドでの処理となります。一方、BackgroundTaskのrun()メソッドはワーカースレッドです。これらをアノテーションとして明記すると、リスト4のようになります。

[リスト4]MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
	〜省略〜
	asyncExecute();
}

@UiThread  // (1)
public void asyncExecute() {  // (2)
	Looper mainLooper = Looper.getMainLooper();
	Handler handler = HandlerCompat.createAsync(mainLooper);
	BackgroundTask backgroundTask = new BackgroundTask(handler);
	ExecutorService executorService  = Executors.newSingleThreadExecutor();
	executorService.submit(backgroundTask);
}

private class BackgroundTask implements Runnable {
	private final Handler _handler;
	public BackgroundTask(Handler handler) {
		_handler = handler;
	}
	@WorkerThread  // (3)
	@Override
	public void run() {
		Log.i("Async-BackgroundTask", "ここに非同期処理を記述する");
		PostExecutor postExecutor = new PostExecutor();
		_handler.post(postExecutor);
	}
}

private class PostExecutor implements Runnable {
	@UiThread  // (4)
	@Override
	public void run() {
		Log.i("Async-PostExecutor", "ここにUIスレッドで行いたい処理を記述する");
	}
}

 先述のように、リスト3のmainLooperの取得からsubmit()の実行までがひとつの纏まった処理ですので、これをasyncExecute()というメソッドにまとめています(リスト4の(2))。もちろん、メソッド名は何でもかまいません。大切なのは、これらをまとめてひとつのメソッドにして、それに対して(1)のように「@UiThread」というアノテーションを付与しています。これによって、このメソッドがUIスレッドで実行されることがコンパイラによって保証されます。もし不都合があるならば、コンパイルエラーの形で教えてくれます。

 同様なのが、リスト4の(4)です。PostExecutorのrun()メソッド内の処理は、バックグラウンド処理後にUIスレッドで実行される処理ですので、@UiThreadを付与しています。

 一方、BackgroundTaskクラスのrun()メソッドは、バックグラウンド処理であるワーカースレッドで実行されるため、リスト4の(3)のように@WorkerThreadアノテーションを付与しています。

 ここで注意してほしいのは、アノテーションを付与するのは、あくまでメソッドに対してである、ということです。例えば、BackgroundTaskクラスに対して@WorkerThreadアノテーションを付与することも可能ですが、その瞬間にコンパイルエラーとなります。というのは、BackgroundTaskクラスのコンストラクターの処理はUIスレッドで行われるからです。そのため、確実に、UIスレッド行われるメソッドに対して@UiThreadアノテーションを、バックグラウンド処理が行われるメソッドに対して@WorkerThreadアノテーションを付与するようにしましょう。

まとめ

 ライブドアのお天気情報サービスが終了となり、さらには、非同期処理で基本として利用してきたAsyncTaskクラスが非推奨となり、それらを補うために現状に合った処理方法を紹介する本稿はいかがでしたか?

 今回はJavaによるAndroidの非同期処理を紹介しましたが、あくまでコードパターンしか紹介していません。実際に、新しいWeb APIサービスからお天気情報を取得して表示する内容などは、何も紹介していません。

 次回、今回紹介したコードパターンを、実際のAndroidアプリとして組み込み、さらには、現在利用可能なOpen WeatherのWeb APIサービスを利用して、実際にHTTPアクセスを行い、お天気情報を表示する方法を紹介します。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
2020年版Androidの非同期処理連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 齊藤 新三(サイトウ シンゾウ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。Web系製作会社のシステム部門、SI会社を経てフリーランスとして独立。屋号はSarva(サルヴァ)。HAL大阪の非常勤講師を兼務。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/13199 2020/11/17 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング