daiki0508'足跡

Android11でAsyncTaskが非推奨になった話

f:id:daiki0508:20210503222911p:plain

少し前の情報なのですが、Android11でAsyncTaskが非推奨となったという話を聞きました。
当時の自分はまだAndroidアプリの開発について勉強していた時期で、書籍を使って勉強していたのですが勿論、その書籍では当然AsyncTaskを使った手法で書かれていました。

そして、最近このことを知ったので実際にその代替実装手法としてどのようなものがあるのかを知るために調べて実装してみたので、その時のメモとして記事に残しました。

目次

AsyncTaskって?

そもそもAsyncTaskって何?って方に説明すると、Android非同期処理画面UIの更新を同時に行うのを実装するために使うクラスのことです。

次に非同期処理の説明をするのですが、ここで気になる方もいると思います。

「非」ということは「非」ではない処理もあるのか?

結論から言うと、勿論あります!

それを同期処理と言います。

同期処理と非同期処理の違い

では、同期処理と非同期処理の違いとは何でしょうか?
一言でいえば処理を順次に行うか、並列で行うかの違いです。

この説明だと少しイメージしにくい感じもするので、料理に例えたチャート(流れ図)を書いてみました。

同期処理

f:id:daiki0508:20210503174636p:plain

この処理では、料理を初めて1品目を作ってから2品目を作り始めています。
逆に言えば「1品目を作り終えるまでは2品目の作成に入れない」ということです。

つまり、1品目で膨大な時間が掛かってその間に2品目を作れる時間があっても作り始めることは出来ないということです。

そしてこのイメージを実際のアプリに適応すると以下のようになります。

1.処理1を実行(時間が掛かる処理)
2.処理2を実行(画面表示系の処理)
3.処理の終了

こうするとアプリとしては画面処理をしたいが、処理1が終わっていないので画面表示系の処理を行うことが出来ないことになってしまいます。
そしてユーザには「アプリがフリーズしたのか?」という風に思われることになってしまいます。

非同期処理

f:id:daiki0508:20210503175649p:plain

この処理では、1品目の食材を切った後にその食材を炒めながら2品目の食材を切り始めています。
こうすることで、たとえ1品目の調理に時間が掛かったとしても2品目の調理も並行して進めているのでさほど大きな問題は起きません。

これも先ほどと同様に、実際のアプリに適用すると以下のようになります。

1.処理1を実行(時間が掛かる処理)
2.処理2を実行(画面表示系の処理 -> 処理1を待たない)
3.処理の終了

よってアプリは処理1の結果を待たずに画面表示系の処理を行うことが出来るので、ユーザにアプリがフリーズしたようには思われなくなります。

これが非同期処理の強みです!!

実際に見てみる

同期処理と非同期処理の違いで同期処理と非同期処理の違いを文字とチャートで説明したので、今度は実際にアプリとして確認してみましょう!

同期処理

まず最初に、同期処理のアプリの流れを説明します。
ソース全体はこちらに掲載しています。

1.アプリが起動する
2.SlowProcessClass内でViewTextが実行される(処理が遅い)
3.画面表示が行われる
4.WebViewが読み込まれる

では実際にこちらからapkをダウンロード・インストールして、アプリを実行してみてください。

これからは、このアプリの処理についてもう少し詳しく説明します。

2.SlowProcessClass内でViewTextが実行される(処理が遅い)

f:id:daiki0508:20210503221605p:plain

Thread.sleep(10 * 1000);

SllowProcessClass内のViewTextメソッドに上記の記述があるので、10000ミリ秒(=10秒)の間、処理が止まっている。
この間3.画面表示が行われるが実行できないので上記画像の状態で10000ミリ秒(=10秒)の間、画面が動かずユーザからはアプリがフリーズしているかのように見える。

処理が終了すると

This process was slow...

と表示される。

3.画面表示が行われる

f:id:daiki0508:20210503221718p:plain

アプリ名や「HelloWorld!」といった文字列が表示される。

4.WebViewが読み込まれる

f:id:daiki0508:20210503221743p:plain

最後にWebViewとしてGoogleのサイトが表示されたと思います。

非同期処理

まず最初に、同期処理のアプリの流れを説明します。
ソース全体はこちらに掲載しています。

1.アプリが起動する
2.SlowProcessClass内でViewTextが実行される(処理が遅い -> 非同期処理)
3.画面表示が行われる(2の処理の結果を待たない)
4.WebViewが読み込まれる(2の処理の結果を待たない)

では実際にこちらからapkをダウンロード・インストールして、アプリを実行してみてください。

これからは、このアプリの処理についてもう少し詳しく説明します。
(処理が終了した順に説明していくので、アプリの処理の流れの時の順序と前後して分かりにくくなるかもしれませんがすいません…。)

3.画面表示が行われる(2の処理の結果を待たない)

f:id:daiki0508:20210503222351p:plain

2.SlowProcessClass内でViewTextが実行されるが非同期処理として実行されているため、上記画像のように既にアプリ名HelloWorld!等の画面表示系の処理が行われています。

4.WebView

f:id:daiki0508:20210503222459p:plain

WebViewとしてGoogleのホームページが表示されたと思います。

2.ViewTextメソッドの処理が終了する

f:id:daiki0508:20210503221743p:plain

ここでようやく、非同期で記述した処理が全て終了して

This process was slow...

と表示される。

非同期処理の記述方法

それでは実際にどのように非同期処理を記述したらいいのかを説明していきます。
(これから説明する内容はあくまで手法の1つです)

ExecutorServiceの利用

ExcutorServiceとは

終了を管理するメソッド、および1つ以上の非同期タスクの進行状況を追跡するFutureを生成できるメソッドを提供するExecutorです。

つまりこれを使えばスレッドを分割して上手く非同期の機能を実装出来そうです。

サンプルコードは以下のような感じです。

executorService = Executors.newSingleThreadExecutor(); 
executorService.execute(new Runnable() {
    @Override
    public void run() {
        // バックグランドで処理したい内容を記述
        String result = spc.ViewText();
    }
});

private void shutdown(){
    if (executorService == null){
        return;
    }
    try {
        executorService.shutdown();
        if (!executorService.awaitTermination(1L, TimeUnit.SECONDS)){
            executorService.shutdownNow();
        }
    }catch (InterruptedException e){
        executorService.shutdownNow();
    }finally {
        executorService = null;
        Thread.currentThread().interrupt();
    }
}

@Override
protected void onDestroy(){
    super.onDestroy();

    shutdown();
}

ここで注意すべきはExecutorServiceの終了を忘れないようにすることです。
(※ExecutorServiceを使ったらシャットダウンするくせを付けておくと良いと思います)

ExecutorServiceをシャットダウンする理由としては以下のような理由が挙げられます。

スレッドプールはタスクを待ち続けるので、スレッドプールが待機したままになるため、 メインスレッドが終了してもプログラムは実行したままになる。

UIを更新する方法

スレッドを分けた場合でありがちなミスなのですが、UIの更新はメインスレッドで行う必要があることに注意してください。

この問題を解決するのがHandlerです。
Handlerpostメソッド内にUIの更新処理をバッググラウンド処理の内部に追記します。

サンプルコードは以下のようになります。

// この上にはバックグラウンド処理が記述されている
final Handler handler = new Handler();
handler.post(new Runnable() {
    @Override
    public void run() {
        textView.setText(result);
    }
});

まとめ

今までAsyncTaskを使ってきた方には、ExecutorServiceHandlerを使った非同期処理はとても複雑に見えることでしょう。
(実際私は理解するのにかなり時間が掛かりました(笑))

と言っても、今はまだ非推奨になってから時間があまり経っていないので問題となっていませんが、この先のことを考えるとこのままずっとAsyncTaskを使い続けるわけにはいかないですし、だからといって非同期処理を使わないというのは論外です。

なので、ぜひこの方法を覚えて積極的に使っていきましょう!!

引用・参考情報

https://outofmem.tumblr.com/post/94711883294/android-executor-3

https://www.it-swarm-ja.com/ja/java/executorservice%e3%81%a7shutdown%ef%bc%88%ef%bc%89%e3%82%92%e5%91%bc%e3%81%b3%e5%87%ba%e3%81%99%e7%90%86%e7%94%b1/1072285315/

https://tips.priart.net/52/

https://rightcode.co.jp/blog/information-technology/android-os-asynctask