【Kotlin/Android】OkHttpの使い方!HTTP通信でAPIを取得
この記事からわかること
- Android Studio/KotlinでOkHttpの使い方
- HTTP通信を実装してAPIを取得する方法
- QiitaAPIを使用した記事取得アプリの実装方法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Android Studio:Flamingo
- Kotlin:1.8.20
OkHttpとは?
OkHttpはAndroidアプリ開発で使用できるHTTPクライアントライブラリです。HTTP/2およびSPDYをサポートしており、複数のリクエストやレスポンスを同時に処理することができたり、ネットワークが不安定な際の自動的なリトライ処理、キャッシュ機能など様々や機能が提供されています。
開発したのはSquare社という会社でオープンソースのライブラリになっています。Square社が開発したライブラリには同じような役割の「Retrofit」があります。こちらは内部的にOKHttpを使用しており、「Retrofit」ではOkHttpよりJSONのシリアライズ/デシリアライズに長けており、RESTful APIでの通信に活用できます。
OkHttpでAPIを叩く実装の流れ
- OkHttpの導入
- インターネットの利用を可能にする
- HTTP通信を実装する
今回はテストとしてQiitaのAPIを使用してみます。
記事(新着順20個):https://qiita.com/api/v2/items
1.OkHttpの導入
OkHttpをAndroid Studioで使用するために「bundle.gradle(Module)」に以下の文を追加して「Sync Now」をクリックします。
dependencies {
// OkHttp
implementation "com.squareup.OkHttp:okhttp:4.11.0"
}
2.インターネットの利用を可能にする
AndroidアプリからWeb通信をさせるためにはマニフェストファイルへで適切なパーミッションを定義する必要があります。デフォルトではインターネットアクセスが許可されていないので「AndroidManifest.xml」に<uses-permission android:name="android.permission.INTERNET" />
を追加しておきます。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
+ <uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
3.HTTP通信を実装する
OkHttp
でHTTP通信を実装するためにはOkHttpClient
インスタンスを作成します。このクラスがGETやPOSTなどのHTTPリクエストを送信する役割を持っています。
private val client = OkHttpClient()
次にRequest
型のリクエストを作成します。url
メソッドの引数にAPIで実行したいエンドポイントを指定します。何も指定しなければGETリクエストになります。
val request = Request.Builder()
.url("https://qiita.com/api/v2/items")
.build()
実行するにはnewCall(request)
メソッドに引数にリクエストを渡し、execute
メソッドを使用します。execute
はHTTPリクエストを同期的に実行し、レスポンスを返します。response.isSuccessful
でHTTPステータスが200番台(成功)であるかどうか識別でき、headers
でヘッダー情報をbody
でレスポンスの中身を取得できます。
var response: Response? = null
try {
response = client.newCall(request).execute()
// ステータスコードが200番台でない場合の処理
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for ((name, value) in response.headers) {
Log.d("OkHttp", "$name: $value")
}
Log.d("OkHttp", response.body!!.string())
} catch (e: IOException) {
// エラー処理
e.printStackTrace()
} finally {
// Responseリソースを閉じる
response?.close()
}
またHTTPリクエストはメインスレッド以外で実行しないとandroid.os.NetworkOnMainThreadException
を吐きます。
実装例
class MainActivity : AppCompatActivity() {
private val client = OkHttpClient()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button: Button = findViewById(R.id.request_button)
button.setOnClickListener{
executeApi()
}
}
private fun executeApi() {
// 明示的にバックグラウンドスレッドを指定
lifecycleScope.launch(Dispatchers.IO) {
// リクエストを生成
val request = Request.Builder()
.url("https://qiita.com/api/v2/items")
.build()
var response: Response? = null
try {
// 同期的にHTTPリクエストを実行
response = client.newCall(request).execute()
// テータスコードが200番台でない場合の処理
if (!response.isSuccessful) throw IOException("Unexpected code $response")
for ((name, value) in response.headers) {
Log.d("OkHttp", "$name: $value")
}
Log.d("OkHttp", response.body!!.string())
// [{"rendered_body":"\u003ch1 data-sourcepos=\"1:1-1:14\"\u003e\n\u003cspan id=\"はじめに\" class=\"fragment\"\u003e\u003c/span\u003e\u003ca href=\"#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB\"\u003e\u003ci class=\"fa fa-link\"\u003e\u003c/i\u00......
} catch (e: IOException) {
// エラー処理
e.printStackTrace()
} finally {
// Responseリソースを閉じる
response?.close()
}
}
}
}
useメソッドでリソース管理をスマートに
Response
はCloseable
を継承しているので使用後は明示的にclose
メソッドでリソースを閉じる必要があります。Closeable
なのでuse
メソッドを使用することで明示的にリソースを解放処理を実装しなくても済むのでスッキリした記述にすることができます。
try {
client.newCall(request).execute().use { response ->
// ステータスコードが200番台でない場合の処理
if (!response.isSuccessful) throw IOException("Unexpected code $response")
// ヘッダーの処理
for ((name, value) in response.headers) {
Log.d("OkHttp", "$name: $value")
}
// レスポンスボディの処理
Log.d("OkHttp", response.body!!.string())
}
} catch (e: IOException) {
// エラー処理
e.printStackTrace()
}
enqueue:非同期的に実行する
enqueue
メソッドはHTTPリクエストを非同期的に実行します。引数にはCallback
型で結果を処理するコールバックを指定します。
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
// エラー処理
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
if (!response.isSuccessful) {
throw IOException("Unexpected code $response")
}
val responseData = response.body?.string()
println(responseData)
}
})
cache:キャッシュを有効にする
OkHttpではHTTPリクエストのレスポンスをキャッシュする仕組みが用意されています。キャッシュの保存先と最大保存容量を指定してCache
インスタンスを生成しcache
メソッドでOkHttpClient
に渡します。
val cacheDir = File(cacheDir, "http_cache")
val cacheSize: Long = 10 * 1024 * 1024 // 10MB
val cache = Cache(cacheDir, cacheSize)
val client = OkHttpClient.Builder()
.cache(cache)
.build()
キャッシュがある場合は基本的にキャッシュされているデータを返すようになるので通信コストを抑えることが可能になります。
レスポンスがキャッシュかどうか識別
Response
のcacheResponse
でレスポンスがキャッシュかネットワークか識別することが可能です。
// キャッシュからのレスポンスかネットワークからのレスポンスかを確認
val cacheResponse = response.cacheResponse
val networkResponse = response.networkResponse
if (cacheResponse != null) {
println("Response from cache")
}
if (networkResponse != null) {
println("Response from network")
}
CacheControl:キャッシュを明示的に使用しない
キャッシュはCacheControl
を使用してキャッシュの使用の有無を変更できます。noCache
を指定するとキャッシュを使わず、常にサーバーから取得するようにできます。
val cacheDir = File(cacheDir, "http_cache")
val cacheSize: Long = 10 * 1024 * 1024 // 10MB
val cache = Cache(cacheDir, cacheSize)
val client = OkHttpClient.Builder()
.cache(cache)
.build()
val cacheControl = CacheControl.Builder()
.noCache() // キャッシュを使わず、常にサーバーから取得する
.build()
val request = Request.Builder()
.url("https://qiita.com/api/v2/items")
.cacheControl(cacheControl)
.build()
- noCache:キャッシュを使わず、常にサーバーから取得する
- noStore:キャッシュを完全に無効にし、キャッシュに保存されないようにする
- maxAge(int, TimeUnit):キャッシュが有効である最大時間を設定
- onlyIfCached:キャッシュがある場合のみレスポンスを返す
キャッシュを削除
キャッシュを削除したい場合は保存先のキャッシュディレクトリを削除します。
if (cacheDir.isDirectory) {
cacheDir.deleteRecursively()
}
タイムアウト処理
タイムアウト処理を設けたい場合はOkHttpClient
からそれぞれ呼び出して設定します。
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) // 接続タイムアウトを10秒に設定
.readTimeout(30, TimeUnit.SECONDS) // 読み取りタイムアウトを30秒に設定
.writeTimeout(15, TimeUnit.SECONDS) // 書き込みタイムアウトを15秒に設定
.cache(cache)
.build()
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。