【Kotlin/Android】OkHttpの使い方!HTTP通信でAPIを取得

【Kotlin/Android】OkHttpの使い方!HTTP通信でAPIを取得

この記事からわかること

  • Android Studio/KotlinOkHttp使い方
  • HTTP通信実装してAPI取得する方法
  • QiitaAPIを使用した記事取得アプリの実装方法

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

OkHttpとは?

公式リファレンス:OkHttp

OkHttpはAndroidアプリ開発で使用できるHTTPクライアントライブラリです。HTTP/2およびSPDYをサポートしており、複数のリクエストやレスポンスを同時に処理することができたり、ネットワークが不安定な際の自動的なリトライ処理、キャッシュ機能など様々や機能が提供されています。

開発したのはSquare社という会社でオープンソースのライブラリになっています。Square社が開発したライブラリには同じような役割の「Retrofit」があります。こちらは内部的にOKHttpを使用しており、「Retrofit」ではOkHttpよりJSONのシリアライズ/デシリアライズに長けており、RESTful APIでの通信に活用できます。

OkHttpでAPIを叩く実装の流れ

  1. OkHttpの導入
  2. インターネットの利用を可能にする
  3. 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メソッドを使用します。executeHTTPリクエストを同期的に実行し、レスポンスを返しますresponse.isSuccessfulHTTPステータスが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メソッドでリソース管理をスマートに

ResponseCloseableを継承しているので使用後は明示的に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()

キャッシュがある場合は基本的にキャッシュされているデータを返すようになるので通信コストを抑えることが可能になります。

レスポンスがキャッシュかどうか識別

ResponsecacheResponseレスポンスがキャッシュかネットワークか識別することが可能です。

// キャッシュからのレスポンスかネットワークからのレスポンスかを確認
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()

キャッシュを削除

キャッシュを削除したい場合は保存先のキャッシュディレクトリを削除します。

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()

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index