ネットワークに接続する

アプリでネットワーク オペレーションを行うには、マニフェストに次の権限を含める必要があります。

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

安全なネットワーク通信のためのおすすめの方法

アプリにネットワーキング機能を追加する前に、アプリ内のデータと情報をネットワークで送信する際の安全性を確認する必要があります。そのためには、次に示すネットワーク セキュリティのおすすめの方法に従ってください。

  • ネットワーク経由で送受信するユーザーの機密情報や個人情報の量を最小限に抑えます。
  • アプリから発生するすべてのネットワーク トラフィックを SSL 経由で送信します。
  • ネットワーク セキュリティ構成の作成を検討します。これにより、アプリがカスタム CA を信頼することを許可するか、安全な通信のために信頼するシステム CA のセットを制限します。

安全なネットワーキングの原則を適用する方法の詳細については、ネットワーク セキュリティのヒントをご覧ください。

HTTP クライアントを選択する

ネットワークに接続するほとんどのアプリは、HTTP を使用してデータの送受信を行います。Android プラットフォームには HttpsURLConnection クライアントが組み込まれており、TLS や、ストリーミングのアップロード / ダウンロード、設定可能なタイムアウト、IPv6、接続プーリングがサポートされています。このトピックでは、HTTP クライアントを宣言的に作成できる Retrofit HTTP クライアント ライブラリを使用します。Retrofit は、リクエスト本文の自動シリアル化と、レスポンス本文のシリアル化解除もサポートしています。

DNS クエリを解決する

Android 10(API レベル 29)以降を搭載したデバイスでは、クリアテキスト ルックアップと DNS over TLS モードの両方を使用する特殊な DNS ルックアップがネイティブでサポートされています。DnsResolver API は汎用の非同期解決を提供し、SRVNAPTR などのレコードタイプを検索できます。応答の解析はアプリが行う必要があります。

Android 9(API レベル 28)以前を搭載したデバイスでは、プラットフォームの DNS リゾルバは A レコードと AAAA レコードのみをサポートします。これにより、名前に関連付けられた IP アドレスを検索できますが、他のレコードタイプはサポートされません。

NDK ベースのアプリについては、android_res_nsend をご覧ください。

リポジトリでネットワーク オペレーションをカプセル化する

アプリのさまざまな部分でネットワーク オペレーションの実行プロセスを簡略化し、コードの重複を減らすために、リポジトリ設計パターンを使用できます。リポジトリは、データ オペレーションを処理し、特定のデータやリソースに対してクリーンな API 抽象化を行うクラスです。

次の例のように、Retrofit を使用して、ネットワーク オペレーションの HTTP メソッド、URL、引数、レスポンス タイプを指定するインターフェースを宣言できます。

Kotlin

interface UserService {
    @GET("/users/{id}")
    suspend fun getUser(@Path("id") id: String): User
}

Java

public interface UserService {
    @GET("/user/{id}")
    Call<User> getUserById(@Path("id") String id);
}

リポジトリ クラス内で、関数はネットワーク オペレーションをカプセル化し、その結果を公開できます。このカプセル化により、リポジトリを呼び出すコンポーネントがデータの保存方法を知る必要がなくなります。また、データの保存方法に関する今後の変更は、リポジトリ クラスに分離されます。

Kotlin

class UserRepository constructor(
    private val userService: UserService
) {
    suspend fun getUserById(id: String): User {
        return userService.getUser(id)
    }
}

Java

class UserRepository {
    private UserService userService;

    public UserRepository(
            UserService userService
    ) {
        this.userService = userService;
    }

    public Call<User> getUserById(String id) {
        return userService.getUser(id);
    }
}

応答しない UI を作成しないようにするために、メインスレッドでネットワーク オペレーションを行わないでください。デフォルトでは、Android はメイン UI スレッド以外のスレッドでネットワーク オペレーションを行う必要があります。そうしないと、NetworkOnMainThreadException がスローされます。上のコード例で示した UserRepository クラスで、ネットワーク オペレーションは実際にはトリガーされません。UserRepository の呼び出し元は、コルーチンを使用するか、enqueue() 関数を使用してスレッド化を実装する必要があります。

構成の変更に対処する

画面の回転などの構成変更が発生すると、フラグメントまたはアクティビティが破棄され、再作成されます。フラグメントまたはアクティビティのインスタンス状態に保存されていないデータはすべて失われるため(少量のデータしか保持しません)、ネットワーク リクエストを再度行う必要が生じることがあります。

ViewModel を使用すると、構成を変更した後もデータを維持できます。ViewModel は、ライフサイクルを意識した方法で UI 関連のデータを保存し管理するように設計されたコンポーネントです。上で作成した UserRepository を使用して、ViewModel は必要なネットワーク リクエストを行い、その結果を、LiveData を使用してフラグメントまたはアクティビティに提供できます。

Kotlin

class MainViewModel constructor(
    savedStateHandle: SavedStateHandle,
    userRepository: UserRepository
) : ViewModel() {
    private val userId: String = savedStateHandle["uid"] ?:
        throw IllegalArgumentException("Missing user ID")

    private val _user = MutableLiveData<User>()
    val user = _user as LiveData<User>

    init {
        viewModelScope.launch {
            try {
                // Calling the repository is safe as it will move execution off
                // the main thread
                val user = userRepository.getUserById(userId)
                _user.value = user
            } catch (error: Exception) {
                // show error message to user
            }

        }
    }
}

Java

class MainViewModel extends ViewModel {

    private final MutableLiveData<User> _user = new MutableLiveData<>();
    LiveData<User> user = (LiveData<User>) _user;

    public MainViewModel(
            SavedStateHandle savedStateHandle,
            UserRepository userRepository
    ) {
        String userId = savedStateHandle.get("uid");
        Call<User> userCall = userRepository.getUserById(userId);
        userCall.enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                if (response.isSuccessful()) {
                    _user.setValue(response.body());
                }
            }

            @Override
            public void onFailure(Call<User> call, Throwable t) {
                // show error message to user
            }
        });
    }
}

このトピックの詳細については、次の関連ガイドをご覧ください。