アプリでネットワーク オペレーションを行うには、マニフェストに次の権限を含める必要があります。
<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 は汎用の非同期解決を提供し、SRV
、NAPTR
などのレコードタイプを検索できます。応答の解析はアプリが行う必要があります。
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 } }); } }
関連ガイド
このトピックの詳細については、次の関連ガイドをご覧ください。