Edited at

Android Studio3 OkHttp3クライアントアプリを作ろう

More than 1 year has passed since last update.


前書き

前回 、スマホで「『Hello World!』を表示するだけ」アプリの動作確認はできましたので、そのアプリを改良して、適当なWebサイトにGET/POSTするアプリを作成していきます。



連載記事リスト:Androidアプリ開発


アプリ構築ポイント

・AndroidManifest のパーミッションで、「INTERNET」と「STORAGE(READ)」を許可

・http クライアントは『OkHttp3』ライブラリを利用

・非同期処理のThreadは『AsyncTask』で実装

・AsyncTaskへのデータ渡しは、Object配列で

・POST送信で使うファイルは、すでにスマホのストレージにあるファイルを利用


build.gradleに依存関係追記

OkHttp3 を追加します。


app/build.gradle

    dependencies {

implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.squareup.okhttp3:okhttp:3.9.1' // ①OkHttp3 を追加
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

①でOkHttp3を追加 OkHttp公式サイト

執筆時点(2017-12-15)では、最新のOkHttp3 は『ver 3.9.1』ですね。

AndroidStudio2までは、


app/build.gradle

compile 'com.squareup.okhttp3:okhttp:3.9.1'


という書式でしたが、AndroidStudio3 からは、


app/build.gradle

implementation 'com.squareup.okhttp3:okhttp:3.9.1'


と書くようです。エラーにはなりませんが…。


パーミッション設定追加

今回、パーミッションとして必要な「INTERNET」と「STORAGE(read)」を追加します。


AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="jp.co.visionarts.noriseto.pocapp">

<uses-permission android:name="android.permission.INTERNET" /> <!--①-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <!--②-->

<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">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

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


①「INTERNET」パーミッション追加

②「READ_EXTERNAL_STORAGE」パーミッション追加


レイアウトを修正

「GET」「POST」ボタンを用意し、レスポンスは「TextView」で表示します。


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="jp.co.visionarts.noriseto.pocapp.MainActivity">

<LinearLayout
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button_get"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GET"/>
<Button
android:id="@+id/button_post"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="POST"/>
<!-- ① -->
<ScrollView
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Response..."/>
</ScrollView>
</LinearLayout>

</android.support.constraint.ConstraintLayout>


①レスポンス表示の「TextView」は「ScrollView」タグで囲って縦スクロールできるようにします。


ソースコードを修正


MainActivity.java


MainActivity.java

package jp.co.visionarts.noriseto.pocapp;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;

public class MainActivity extends AppCompatActivity
implements View.OnClickListener { //①OnClickListener インタフェイスを継承

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

//②ボタンにこのActivityのonClickListenerを割り当てる
findViewById(R.id.button_get).setOnClickListener(this);
findViewById(R.id.button_post).setOnClickListener(this);
}

//③onClickコールバックをここですべてキャッチして、viewのidで動作を決める
public void onClick(View v) {
switch (v.getId()) {
case R.id.button_get:
//④GET リクエストサンプルとして「Livedoor天気情報」にアクセス
String weatherURL = "http://weather.livedoor.com/forecast/webservice/json/v1";
String queryString = "?city=130010"; //東京

//⑤GetAsyncTaskに渡すパラメータをObject配列に設定
Object[] getParams = new Object[2];
getParams[0] = weatherURL;
getParams[1] = queryString;

//⑥GetAsyncTaskを実行
new GetAsyncTask(this).execute(getParams);
break;

case R.id.button_post:
//⑦POSTリクエストサンプルとして、自前のファイル受信サイトへアクセス
String uploadURL = "http://va-fileupload.azurewebsites.net/uploadserver/api/upload/";
String title = "Upload image file(JPEG)";
String uploadFile = "/storage/emulated/0/Pictures/cat.jpg";

//⑧PostAsyncTaskに渡すパラメータをObject配列に設定
Object[] postParams = new Object[3];
postParams[0] = uploadURL;
postParams[1] = title;
postParams[2] = uploadFile;

//⑨PostAsyncTaskを実行
new PostAsyncTask(this).execute(postParams);
break;

default:
break;
}
}

}


①OnClickListener インタフェイスを継承

②ボタンにこのActivityのonClickListenerを割り当てる

③onClickコールバックをここですべてキャッチして、viewのidで動作を決める

④GET リクエストサンプルとして「Livedoor天気情報」にアクセス

⑤GetAsyncTaskに渡すパラメータをObject配列に設定

⑥GetAsyncTaskをパラメータ付で実行

⑦POSTリクエストサンプルとして、自前のファイル受信サイトへアクセス

 POSTリクエストは、適当なサイトが無かったので、自前サイトにファイルをアップロードしてみます。

⑧PostAsyncTaskに渡すパラメータをObject配列に設定

⑨PostAsyncTaskをパラメータ付で実行

 HTTP通信部分は、GET、POSTメソッドそれぞれ、AsyncTaskを継承した独自クラスに実装していきます。

自前のアップロードサイトは、Azure WebApp + Springboot で構築してまして、『簡単にWebサーバ作れますよ!』的なところも、いつの日か触れたいと思います。


GetAsyncTask.java


GetAsyncTask.java

package jp.co.visionarts.noriseto.pocapp;

import android.app.Activity;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.TextView;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.lang.ref.WeakReference;

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

/*
* AsyncTask for GET Method
* Created by noriseto on 2017/11/28.
*/

public class GetAsyncTask extends AsyncTask<Object, Void, Object> {

//①USE WEEK REFERENCE
private final WeakReference<Activity> w_Activity;

//②コンストラクタで、 呼び出し元Activityを弱参照で変数セット
GetAsyncTask(Activity activity) {
this.w_Activity = new WeakReference<>(activity);
}

//③バックグラウンド処理
@Override
protected Object doInBackground(Object[] data) {

//Object配列でパラメータを持ってこれたか確認
String url = (String)data[0];
String queryString = (String)data[1];

//④HTTP処理用オプジェクト作成
OkHttpClient client = new OkHttpClient();

//⑤送信用リクエストを作成
Request request = new Request.Builder().url(url + queryString).get().build();

//⑥受信用オブジェクトを作成
Call call = client.newCall(request);
String result = "";

//⑦送信と受信
try {
Response response = call.execute();
ResponseBody body = response.body();
if (body != null) {
result = body.string();
}
} catch (IOException e) {
e.printStackTrace();
}
//⑧結果を返し、onPostExecute で受け取る
return result;
}

//⑨バックグラウンド完了処理
@Override
protected void onPostExecute(Object result) {
super.onPostExecute(result);
Log.i("onPostExecute", (String)result);
//⑩簡単にJSONレスポンスをパースしてみる
String title = "no response";
String description = "";
String publicTime = "";
try {
JSONObject json = new JSONObject((String)result);
title = json.getString("title");
JSONObject descriptionObj = (JSONObject)json.get("description");
description = descriptionObj.getString("text");
publicTime = descriptionObj.getString("publicTime");
} catch (JSONException je) {
je.getStackTrace();
}

//⑪画面表示
Activity activity = w_Activity.get();
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
// activity is no longer valid, don't do anything!
return;
}
TextView tv = activity.findViewById(R.id.textview);
String showText = title + "\n" + publicTime + "\n" + description;
tv.setText(showText);
}

}


①最近は、Activity contextをそのまま変数に入れると「This field leaks context object」というワーニングが出るので、弱参照とする

②コンストラクタで、 呼び出し元Activityを弱参照で変数セット

③バックグラウンド処理でWebサイトにアクセス

④HTTP処理用オプジェクト作成

⑤送信用リクエストを作成。引数のURLとQueryパラメータをセットする

⑥受信用オブジェクトを作成

⑦送信と受信

⑧結果を返し、onPostExecute で受け取る

⑨バックグラウンド処理の最後にレスポンスを画面表示

⑩JSONレスポンスは以下のJSONフォーマット (使うところだけ抜粋)

{

"title": "東京都 東京 の天気",
"description": {
"text": " 本州付近は...",
"publicTime": "2017-11-28T16:39:00+0900"
}
}

⑪画面表示部分で、表示する直前に弱参照Activityから強参照のActivityを取得する


PostAsyncTask.java


PostAsyncTask.java

package jp.co.visionarts.noriseto.pocapp;

/*
* AsyncTask for POST Method
* Created by noriseto on 2017/11/28.
*/

import android.app.Activity;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.TextView;

import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;

import okhttp3.Call;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

public class PostAsyncTask extends AsyncTask<Object, Void, Object> {

//①USE WEEK REFERENCE
private final WeakReference<Activity> w_Activity;

//②コンストラクタで、 呼び出し元Activityを弱参照で変数セット
PostAsyncTask(Activity activity) {
// USE WEEK REFERENCE
this.w_Activity = new WeakReference<>(activity);
}

//③バックグラウンド処理
@Override
protected Object doInBackground(Object[] data) {

//Object配列でパラメータを持ってこれたか確認
String url = (String)data[0];
String description = (String)data[1];
String filePath = (String)data[2];

//④HTTP処理用オプジェクト作成
OkHttpClient client = new OkHttpClient();

//⑤送信用POSTデータを構築(Multipartでお願いします!)
final String BOUNDARY = String.valueOf(System.currentTimeMillis());
final MediaType TEXT = MediaType.parse("text/plain; charset=utf-8");
final MediaType IMAGE = MediaType.parse("image/jpeg");
RequestBody requestBody = new MultipartBody.Builder(BOUNDARY)
.setType(MultipartBody.FORM)
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"description\""),
RequestBody.create(TEXT, description)
)
.addFormDataPart(
"upload_file",
"cat.jpg",
RequestBody.create(IMAGE, new File(filePath))
)
.build();

//⑥送信用リクエストを作成
Request.Builder requestBuilder = new Request.Builder();
requestBuilder.url(url);
requestBuilder.post(requestBody);
Request request = requestBuilder.build();

//⑦受信用オブジェクトを作成
Call call = client.newCall(request);
String result = "";

//⑧送信と受信
try {
Response response = call.execute();
ResponseBody body = response.body();
if (body != null) {
result = body.string();
}
} catch (IOException e) {
e.printStackTrace();
}
//⑨結果を返し、onPostExecute で受け取る
return result;
}

//⑩バックグラウンド完了処理
@Override
protected void onPostExecute(Object result) {
super.onPostExecute(result);
Log.i("onPostExecute", (String)result);

//⑪画面表示
Activity activity = w_Activity.get();
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
// activity is no longer valid, don't do anything!
return;
}
TextView tv = activity.findViewById(R.id.textview);
tv.setText((String)result);
}

}


①最近は、Activity contextをそのまま変数に入れると「This field leaks context object」というワーニングが出るので、弱参照とする

②コンストラクタで、 呼び出し元Activityを弱参照で変数セット

③バックグラウンド処理でWebサイトにアクセス

④HTTP通信オプジェクト作成

⑤送信用POSTデータは、どうせならとMultipartでファイルを送ってみる

⑥送信用リクエストを作成

⑦受信用オブジェクトを作成

⑧送信と受信

⑨結果を返し、onPostExecute で受け取る

⑩バックグラウンド処理の最後にレスポンスを画面表示

⑪画面表示部分で、表示する直前に弱参照Activityから強参照のActivityを取得する


[POC App]アプリを実行

Storage の許可は、今回手抜きしたので、下記手順でPermissionを与えてください。

android-4-01.png

[設定] -> [アプリ] で、今回作成しているアプリ [POC App] をタップ

⇒いまはまだ、何も許可されてないですね。

[許可] -> [ストレージ] を On に変更

android-4-02.png

⇒ストレージが許可されました。

[GET]ボタンをタップ

android-4-03.png

⇒Livedoor天気情報から東京の概況を取ってこれました!

[POST]ボタンをタップ

android-4-04.png

⇒スマホの画像を自前のサーバにアップロードできました!


次回予告

次回は、スマホに搭載されている各種センサーについて、アプリを開発しながら探ってみようと思います。

Enjoy the holidays!