【備忘録】http通信関連(okhttp3)

#通信する
スマホやタブレットでアプリを動かす以上、避けて通れないのが通信。
今回はhttp通信についてまとめる。
流石に色々なライブラリがあるようだが、今回はOkhttp3を使った。

#gradleからインポート
まずはアプリでOkhttp3が使えるようにする。
そのためにbuild.gradle(モジュール:app)に追記する。
バージョンはお好きなものを。
追記したら忘れずに同期させよう。

```gradle:build.gradle
dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:2.0.2'
    implementation 'com.squareup.okhttp3:okhttp:4.8.0'  //←この1行を追記
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
```

#manifestsに追記
Androidはデフォルトではアプリの通信を許可していないようなので、AndroidManifests.xmlをいじくる。
今回はINTERNETのほかに、端末がネットワークにつながっているかのチェックに必要だったACCESS_NETWORK_STATEも追記する。
その状態でも通信が失敗するので、<application~>内に```android:usesCleartextTraffic="true"```
を合わせて追記する

```xml:AndroidManifests.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="sample.pack">

<!--この下2行を追記-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme"
    android:usesCleartextTraffic="true">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

</manifest>
```

#Okhttp3
最近のバージョンのAndroidでアプリが通信する場合、非同期処理にしないとエラーになるらしい。
正直初心者にはこの辺りはチンプンカンプンだったので、色々いじくっていたらたまたまOkhttpで動いたというのが選定理由である。
とりあえず完成品を載せておく。


```java:HttpConnector.java
package sample.pack;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;

import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

//WebAPIとの通信関連
public class HttpConnector  {
    //接続先のurl
    private String urlStr  = "";
    //レスポンス用文字列
    private String respStr = "";
    //レスポンス用JSONArray
    private JSONArray respJsonArr = null;
    //待機処理用
    private int Roop_Count;
    private int Timeout_Sec;
    private CountDownLatch countDownLatch;

    //setterとgetter
    public String geturlStr(){
        return urlStr;
    }
    public void seturlStr(String urlStr){
        this.urlStr = urlStr;
    }
    //setterとgetter(レスポンス用　非同期処理対応)
    public String getrespStr() throws InterruptedException {
        countDownLatch.await(Timeout_Sec, TimeUnit.SECONDS);
        return respStr;
    }
    public void setrespStr(String respStr){
        countDownLatch.countDown();
        this.respStr = respStr;
    }
    public JSONArray getrespJsonArr()  throws InterruptedException {
        countDownLatch.await(Timeout_Sec, TimeUnit.SECONDS);
        return respJsonArr;
    }
    public void setrespJsonArr(JSONArray respJsonArr){
        countDownLatch.countDown();
        this.respJsonArr = respJsonArr;
    }

    //コンストラクタ
    HttpConnector(int Roop_Count, int Timeout_Sec){
        this.Roop_Count = Roop_Count;
        this.Timeout_Sec = Timeout_Sec;
        this.countDownLatch = new CountDownLatch(Roop_Count);
    }

    //端末がネットワークに接続されているか確認
    public boolean checkConnection(Context context){
        ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkCapabilities info = cm.getNetworkCapabilities(cm.getActiveNetwork());
        return info != null;
    }

    //GET実行
    public void doGet(String urlStr) {
        Request request = new Request.Builder()
                .url(urlStr)
                .get()
                .build();
        conectHttp(request);
    }

    //POST実行
    public void doPost(String url, String postdata) {
        RequestBody body = RequestBody.create(postdata, MediaType.get("application/json; charset=Shift_JIS"));
        final Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        conectHttp(request);
    }

    //Requestを送りResponseを受け取る
    private void conectHttp(Request request) {
        OkHttpClient client = new OkHttpClient();
        client.newCall(request).enqueue(new Callback() {
            //通信に失敗した場合
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
                setrespStr("");
                setrespJsonArr(null);
                Log.d("状態","Failure");
            }
            //通信に成功した場合
            @Override
            public void onResponse(Call call, Response response) {
                try {
                    setrespStr(response.body().string());
                } catch (IOException | NullPointerException e) {
                    e.printStackTrace();
                    Log.d("状態","Empty");
                    setrespStr("");
                }
                try {
                    setrespJsonArr(new JSONArray(respStr));
                } catch (JSONException e) {
                    e.printStackTrace();
                    Log.d("状態","Empty");
                    setrespJsonArr(null);
                }
            }
        });
    }
}
```
正直全く納得のいかないコードなので不本意ではあるが、今の実力だとこれが限界だった…
使い方としてはnewしてurlを渡して結果をStringかJSONArrayをgetterで受け取る。
非同期処理なので、doGet,DoPostを呼んだあと、すぐにgetterを呼んでも値がセットされていないため、思った動きをしてくれない。
そこでCountDownLatchを使用してsetterで値がセットされたらgetterのawaitが抜けるようにした。
コンストラクタではループ回数とタイムアウト時間を設定するようにしているが、このあたりあまりスマートじゃない気がしている。

そもそもgetterを使用しているのはonResponseから値がreturnできなかったからだ。
共通化を諦めようかとも思ったが、使用頻度が高そうなのでこんな感じに。
因みにgetもpostも中身はほとんど共通にしてしまった。

checkConnection()は文字通り端末がネットワークに接続されているか確認するものだ。
当方ではこれを使ってアプリのオンライン/オフラインを切り替えるのに使用している。