ネットワークに接続する

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

<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、接続プーリングがサポートされています。

ネットワーキング オペレーション用に上位の API を提供するサードパーティ ライブラリも利用できます。リクエスト本文のシリアル化やレスポンス本文のシリアル化解除など、さまざまな便利な機能がサポートされています。

  • Retrofit: OkHttp の上に構築された、Square による JVM 用のタイプセーフな HTTP クライアント。Retrofit では、クライアント インターフェースの作成を宣言でき、複数のシリアル化ライブラリがサポートされています。
  • Ktor: Kotlin 専用に構築された、JetBrains による HTTP クライアント。コルーチンを利用しています。Ktor は、さまざまなエンジン、シリアライザ、プラットフォームをサポートしています。

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);
}

リポジトリ クラス内で、関数はネットワーク オペレーションをカプセル化し、その結果を公開できます。このカプセル化により、リポジトリを呼び出すコンポーネントがデータの保存方法を把握する必要がなくなります。また、データの保存方法に関する今後の変更は、リポジトリ クラスに分離されます。たとえば、API エンドポイントの更新などのリモートでの変更、ローカル キャッシュの実装などが考えられます。

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 を作成しないようにするため、メインスレッドでネットワーク オペレーションは行わないでください。デフォルトでは、メイン UI スレッド以外のスレッド上でネットワーク オペレーションを実行する必要があります。メインスレッドでネットワーク オペレーションを実行しようとすると、NetworkOnMainThreadException がスローされます。

上のコード例では、ネットワーク オペレーションは実際にはトリガーされません。UserRepository の呼び出し元は、コルーチンを使用するか、enqueue() 関数を使用してスレッド化を実装する必要があります。詳細については、Codelab のインターネットからデータを取得するをご覧ください。Kotlin コルーチンを使用してスレッド化を実装する方法について説明しています。

構成の変更に対処する

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

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 moves 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
            }
        });
    }
}

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