AndroidでWebAPIにGetとアクセスしてJson処理する（とりあえずJava）


AndroidはHttp通信、JSON処理もいろいろオプションがあるのでたまに使う人間にはつらい。。。
最近はOkHttpが流行ってるらしいので使ってみる。

## 前提

* Http通信にはOkHttpを利用する
* JsonのパースはJSONObjectで処理（簡単な処理しかしない＋追加ライブラリいらないので）
* 言語はJavaを利用する（大人の事情）
* 開発環境はAndroid Studio 3.4.x on Mac

## 仕様

とりあえずボタンを押したらWebAPIをキックしてJson取得して、パースして、表示したい。

サーバ上に下記のJsonを返すプログラムを設置。

```json
{"status":"OK","message":"Hello2019-05-31 07:06:23"}
```

* ボタンクリックでJsonリクエスト
* 取得したJsonからstatuを取り出し、上記画面に表示。

下記のようなイメージ。

![スクリーンショット 2019-05-31 7.08.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/70466f01-6ff4-cf9b-773f-bacdaad04b0e.png)


## 準備

実装前にいくつか準備。

### INTERNETアクセスの許可

AndroidManifest.xmlに以下を追加。当たり前だが、当たり前過ぎて忘れている場合がある。

```xml
<uses-permission android:name="android.permission.INTERNET" />
```

### 通信ドメインの許可（非https）

どうやらAndroid9.xからhttps以外の通信はエラーとなるよう。下記のようなエラーが出る。

```
java.io.IOException: Cleartext HTTP traffic to hoge.com not permitted
```

>hoge.comはアクセスしたいドメイン、サブドメイン。

許可するためには設定ファイルが必要。めんどうだが対応する。

#### 設定ファイルの設置

下記の内容を記したxmlファイルを容易する。
別にファイル名や場所はどこでもいいみたい（次の過程で明示的に指定するので）。

```xml:app/res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">www.bluecode.jp</domain>
    </domain-config>
</network-security-config>
```

#### 設定ファイルの指定

指定した設定ファイルをAndroidManifest.xmlで指定します。

```diff:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.bluecode.http01">

    <application
+       android:networkSecurityConfig="@xml/network_security_config"
        android:allowBackup="true"
        ...
```

### OkHttpの設定

GrandleのModule:appの方に追加してSync。

```
...
dependencies {
    ...
    implementation 'com.squareup.okhttp3:okhttp:4.0.0-alpha02'
}
...
```

準備は以上。

## 実装

いよいよ実装。もうずいぶん前から通信処理等はメインスレッドではできない。
別スレッドで起動し、かつ、非同期処理するというのが常識。OkHttpでは下記のように記述するみたい。

### MainActivity.java

```java:MainActivity.java
package jp.bluecode.http01;

import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;

import java.io.IOException;

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

public class MainActivity extends AppCompatActivity {

    //Widget宣言
    TextView txt01;
    Button btn01;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //Widget初期化
        txt01 = findViewById(R.id.txt01);
        btn01 = findViewById(R.id.btn01);

        //ボタンクリック
        btn01.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //httpリクエスト
                try{
                    //okhttpを利用するカスタム関数（下記）
                    httpRequest("http://www.bluecode.jp/test/api.php");
                }catch(Exception e){
                    Log.e("Hoge",e.getMessage());
                }

            }
        });
    }

    void httpRequest(String url) throws IOException{

        //OkHttpClinet生成
        OkHttpClient client = new OkHttpClient();

        //request生成
        Request request = new Request.Builder()
                .url(url)
                .build();

        //非同期リクエスト
        client.newCall(request)
                .enqueue(new Callback() {

                    //エラーのとき
                    @Override
                    public void onFailure(@NotNull Call call, @NotNull IOException e) {
                        Log.e("Hoge",e.getMessage());
                    }

                    //正常のとき
                    @Override
                    public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {

                        //response取り出し
                        final String jsonStr = response.body().string();
                        Log.d("Hoge","jsonStr=" + jsonStr);

                        //JSON処理
                        try{
                            //jsonパース
                            JSONObject json = new JSONObject(jsonStr);
                            final String status = json.getString("status");

                            //親スレッドUI更新
                            Handler mainHandler = new Handler(Looper.getMainLooper());
                            mainHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    txt01.setText(status);
                                }
                            });


                        }catch(Exception e){
                            Log.e("Hoge",e.getMessage());
                        }

                    }
                });
    }
}
```

### activity_main.xml

レイアウトはお好みでどう実装してもいい気がしますが、とりあえず参考まで。

```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/txt01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="200dp"
        android:text="Hello World!"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="Button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/txt01" />

</android.support.constraint.ConstraintLayout>
```

## 参考

* https://codeday.me/jp/qa/20190321/444585.html (okhttp)
* https://tkm0on.hatenablog.com/entry/2015/05/21/183608 (jsonパース）