0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Java で非同期処理 for Android :CompletableFuture 入門

Last updated at Posted at 2024-10-23

はじめに

非同期処理は、モダンなAndroidアプリケーション開発において欠かせない技術の一つです。アプリがユーザーの操作に対して素早く反応し、スムーズな体験を提供するためには、バックグラウンドでの作業(例: ネットワーク通信やファイル操作など)を効率的に行う必要があります。これを同期的に処理してしまうと、操作が完了するまでアプリ全体が固まったり、ユーザーに「フリーズしている」という印象を与えることになります。

非同期処理を正しく使うことで、こうした問題を回避し、ユーザーが操作中でもアプリがスムーズに動作し続けるように設計できます。特にAndroidアプリでは、メインスレッド(UIスレッド)で重たい処理を行うと、アプリが応答しなくなる恐れがあり、最悪の場合、システムから「アプリが応答していません」(ANR: Application Not Responding)エラーを出されてしまいます。

ここで役立つのが、Javaの標準ライブラリで提供されている CompletableFuture です。CompletableFuture は、Java 8 から導入された強力な非同期処理ツールで、手軽に非同期タスクを実行し、結果を処理することができます。Thread クラスや AsyncTaskExecutorServiceといった従来の非同期処理と比べて、よりシンプルかつ柔軟に非同期処理を行えるのが特徴です。

本記事では、CompletableFuture を使ってAndroidアプリで非同期処理を簡単に実装する方法を解説します。初めて非同期処理に触れる方でも、この記事を読み終わる頃には、CompletableFuture を使ってバックグラウンドで処理を実行し、その結果をメインスレッドに反映させる基本的な操作ができるようになります。

CompletableFuture の基本操作

ここでは、CompletableFuture を使った非同期処理の基本的な操作を具体的に説明します。非同期タスクの実行から、その完了時の処理をシンプルに書く方法を見ていきましょう。特に、supplyAsyncwhenComplete の使い方に焦点を当てます。

supplyAsync で非同期タスクを実行する

CompletableFuture.supplyAsync メソッドを使って、バックグラウンドで非同期にタスクを実行できます。タスクは別スレッドで処理され、メインスレッド(UIスレッド)に負荷をかけません。

次のコードでは、バックグラウンドで重い処理を実行しています。

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // バックグラウンドで実行される重い処理
    try {
        Thread.sleep(2000); // 2秒間の処理
    } catch (InterruptedException e) {
        throw new RuntimeException("処理が中断されました", e);
    }
    return "非同期処理完了";
});

CompletableFuture.supplyAsync はラムダ式を引数に取り、その中に実行したい処理を記述します。ここでは2秒の遅延をシミュレートしています。エラーが発生した場合は例外をスローします。

ここで重要なのは、CompletableFuture.supplyAsyncの呼び出し自体はすぐにリターンするということです。 この呼び出しにより別スレッド上で引数として渡した処理の実行が開始されます。実行するスレッドは、Java実行環境が管理する共有スレッドプールから選択されるため、明示的にスレッドを生成する必要はありません。

whenComplete でタスク完了時の処理を行う

whenComplete メソッドを使って、非同期タスクが完了した際の処理を実行できます。このメソッドは、タスクの結果と、エラー発生時の例外を受け取り、それに応じて処理を行います。

次のコードは、非同期タスクの完了後に結果をログに表示する例です。

future.whenComplete((result, exception) -> {
    if (exception == null) {
        // 正常にタスクが完了した場合の処理
        System.out.println("タスク成功: " + result);
    } else {
        // エラーが発生した場合の処理
        System.out.println("エラー発生: " + exception.getMessage());
    }
});

この例では、whenComplete メソッドで成功時には結果を表示し、エラー発生時にはエラーメッセージを表示しています。これにより、タスクの成功・失敗を分岐して処理できるようになります。

ここで重要なのは、whenComplete の呼び出し自体はすぐにリターンするということです。whenCompleteの引数として渡したタスクは、supplyAsyncで引数に指定した非同期タスクの完了後に実行されます。

supplyAsync 内でのエラー処理

非同期タスクの実行中にエラーが発生するケースも考慮しましょう。例えば、supplyAsync 内で例外がスローされた場合、そのエラーも whenComplete で処理することができます。以下は、その例です。

CompletableFuture<String> futureWithError = CompletableFuture.supplyAsync(() -> {
    // バックグラウンドでの処理中にエラーを発生させる
    if (true) {
        throw new RuntimeException("意図的なエラー");
    }
    return "非同期処理完了";
});

futureWithError.whenComplete((result, exception) -> {
    if (exception == null) {
        // 正常にタスクが完了した場合
        System.out.println("タスク成功: " + result);
    } else {
        // エラーが発生した場合
        System.out.println("エラー発生: " + exception.getMessage());
    }
});

このコードでは、supplyAsync の中で意図的に例外をスローし、whenComplete でその例外をキャッチして処理しています。このようにして、非同期処理の中でエラーが発生した場合でも、それを適切に扱うことができます。
エラーハンドリングを含めて、非同期処理の流れを理解できるようになると、アプリがより堅牢になり、ユーザーにスムーズな体験を提供できるようになります。

CompletableFuture の実装例

DataFetcher クラスの実装

public class DataFetcher {

    // 非同期でサーバーからデータを取得するメソッド
    public CompletableFuture<String> fetchDataFromServer() {
        return CompletableFuture.supplyAsync(() -> {
            try {
                // サーバーからのデータ取得処理(ダミー)
                Thread.sleep(2000); // 2秒間の遅延をシミュレート
            } catch (InterruptedException e) {
                throw new RuntimeException("データ取得が中断されました", e);
            }
            return "サーバーからのデータ";
        });
    }
}

Activity からの呼び出し

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView textView = findViewById(R.id.textView);
        Button fetchButton = findViewById(R.id.fetchButton);
        DataFetcher dataFetcher = new DataFetcher();

        // ボタンを押したときに非同期でデータを取得
        fetchButton.setOnClickListener(view -> {
            dataFetcher.fetchDataFromServer().whenComplete((result, exception) -> {
                // メインスレッドでUIを更新
                runOnUiThread(() -> {
                    if (exception == null) {
                        textView.setText("取得したデータ: " + result);
                    } else {
                        textView.setText("エラー発生: " + exception.getMessage());
                    }
                });
            });
        });
    }
}

解説

  1. DataFetcher クラスの使用
    DataFetcher クラスは、非同期でサーバーからデータを取得するロジックを担当します。これにより、MainActivity にはビジネスロジックが含まれず、責務が分離されている状態です。

  2. onCreate での呼び出し
    onCreate 内で fetchButton がクリックされた際に、DataFetcherfetchDataFromServer メソッドを直接呼び出して、非同期で処理を実行します。

  3. UI の更新
    whenComplete 内で結果を取得した後、runOnUiThread を使って TextView を更新します。これにより、バックグラウンド処理の結果を メインスレッドで受け取り、UIに反映させることができます。

このシンプルな実装例では、ボタンを押したときに非同期でサーバーからデータを取得し、その結果を画面に表示します。DataFetcher クラスにビジネスロジックを分けることで、Activity はUIの制御に専念でき、非同期処理も簡潔に管理できます。この方法は、メインスレッドをブロックせず、アプリの応答性を維持しながら非同期タスクを実装する上で有効です。

CompletableFuture を使った非同期処理の利点

従来の非同期処理との比較

Androidアプリ開発では、非同期処理を行うために従来から AsyncTaskHandlerHandlerThreadExecutorServiceクラスが使用されてきました。これらの方法には次のような問題がありました:

  1. AsyncTask のデメリット
    AsyncTask は、非同期処理とメインスレッドへの結果の受け渡しを比較的簡単に実装できるものの、構造が複雑になりやすく、コードが煩雑になる傾向があります。また、AsyncTask は Android API 30 で廃止されるため、新しいプロジェクトでは推奨されません。

  2. HandlerHandlerThread のデメリット
    HandlerHandlerThread を使用して非同期処理を行う場合、明示的にスレッドを作成し、そのスレッドに対して Handler を用いてメッセージを送信する実装になります。非同期で処理した結果の受け取りやエラーハンドリングを行うために、煩雑なコードが必要になります。処理の流れも追いにくくなります。また、スレッドの終了処理を忘れるとメモリリークに繋がります。

  3. ExecutorServiceのデメリット
    JavaにおけるFutureパターンの実装としてExecutorService.submitを使う例がありますが、ここで使われるFutureクラスは、CompletableFuture.whenComplete に相当するメソッドを持たず、Future.getで値を受け取る必要があるため、結局メインスレッドをブロックすることになってしまいます。また、Handler の時と同様に、スレッドの管理を行う必要があり、終了処理を忘れるとメモリリークに繋がります。

CompletableFuture の利点

CompletableFuture を使用すると、従来の方法に比べて以下の利点があります。

  1. スレッド管理の簡便さ
    CompletableFuture を使うと、内部でスレッドプールを管理してくれるため、開発者がスレッドを直接扱う必要がありません。これにより、複雑なスレッド管理が不要になり、コードがシンプルに保たれます。

  2. エラーハンドリングの一貫性
    whenCompleteexceptionally メソッドを使えば、エラーが発生した場合でも簡単に処理を行えます。従来の方法では、エラーハンドリングが煩雑になりがちですが、CompletableFuture では一貫してシンプルに実装できます。

  3. シンプルで読みやすいコード
    CompletableFuture を使えば、非同期処理が終了した後の動作を whenComplete などのメソッドで簡潔に記述できます。コードが自然なフローで書かれるため、可読性が向上します。

    dataFetcher.fetchDataFromServer().whenComplete((result, exception) -> {
        if (exception == null) {
            textView.setText(result);
        } else {
            textView.setText("エラー発生");
        }
    });
    

    このコード例では、非同期処理の開始から結果の取得、エラーハンドリングまでが一連のフローで書かれており、読みやすくなっています。

  4. 柔軟な非同期タスクの組み合わせ
    CompletableFuture を使えば、複数の非同期タスクを簡単に組み合わせたり、チェーンさせることが可能です。例えば、データの取得が終わった後に別の非同期処理を行いたい場合でも、thenApplythenCompose といったメソッドを使って自然な流れで処理を記述できます。

    dataFetcher.fetchDataFromServer()
        .thenApply(result -> processResult(result))  // 結果を処理
        .whenComplete((finalResult, exception) -> {
            if (exception == null) {
                textView.setText(finalResult);
            } else {
                textView.setText("エラー発生");
            }
        });
    

実際のプロジェクトでの使用例

CompletableFuture は、小規模なプロジェクトだけでなく、大規模なプロジェクトでも非常に役立ちます。特に、次のようなシナリオで有効です:

  • ネットワーク通信
    サーバーからデータを取得し、それに基づいてUIを更新する処理を非同期で行う場合に最適です。

  • データベースアクセス
    ローカルのデータベース(例えば、SQLite)にアクセスし、その結果を元に次のアクションを実行する場合にも、非同期処理の組み合わせが役立ちます。

  • 複数の非同期処理の組み合わせ
    複数のネットワークリクエストやデータ処理を連続して実行する場合でも、CompletableFuture のメソッドチェーンを使うことで簡潔に記述できます。

まとめ

この記事では、Android アプリ開発における非同期処理の基本として、CompletableFuture を使ったシンプルな例を紹介しました。CompletableFuture は、従来の非同期処理手法よりもシンプルで直感的にコードを記述でき、スレッド管理やエラーハンドリングも簡潔に行える強力なツールです。

  • CompletableFuture の基本操作として、supplyAsync を使った非同期処理の実行方法と、whenComplete を使った結果処理やエラーハンドリングについて説明しました。
  • ビジネスロジックの分離を意識して、DataFetcher クラスを用いることで、Activity からロジックを切り離し、UI の操作と非同期処理をスッキリと実装する手法を紹介しました。
  • Android アプリ開発における利点として、スレッド管理を意識せずに済むこと、柔軟な非同期処理の組み合わせが可能であることを挙げました。

CompletableFuture を活用することで、UI の応答性を損なうことなく、複雑な非同期処理を簡単に管理できるようになります。今回紹介した基礎的な使い方を学ぶことで、実際のプロジェクトでも効果的に非同期処理を扱えるようになるでしょう。ぜひ、積極的に活用してみてください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?