##前書き
前回 、スマホで「『Hello World!』を表示するだけ」アプリの動作確認はできましたので、そのアプリを改良して、適当なWebサイトにGET/POSTするアプリを作成していきます。
連載記事リスト:Androidアプリ開発
##アプリ構築ポイント
・AndroidManifest のパーミッションで、「INTERNET」と「STORAGE(READ)」を許可
・http クライアントは『OkHttp3』ライブラリを利用
・非同期処理のThreadは『AsyncTask』で実装
・AsyncTaskへのデータ渡しは、Object配列で
・POST送信で使うファイルは、すでにスマホのストレージにあるファイルを利用
##build.gradleに依存関係追記
OkHttp3 を追加します。
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までは、
compile 'com.squareup.okhttp3:okhttp:3.9.1'
という書式でしたが、AndroidStudio3 からは、
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
と書くようです。エラーにはなりませんが…。
##パーミッション設定追加
今回、パーミッションとして必要な「INTERNET」と「STORAGE(read)」を追加します。
<?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」で表示します。
<?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
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
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
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を与えてください。
[設定] -> [アプリ] で、今回作成しているアプリ [POC App] をタップ
⇒いまはまだ、何も許可されてないですね。
[許可] -> [ストレージ] を On に変更
⇒ストレージが許可されました。
[GET]ボタンをタップ
⇒Livedoor天気情報から東京の概況を取ってこれました!
[POST]ボタンをタップ
⇒スマホの画像を自前のサーバにアップロードできました!
##次回予告
次回は、スマホに搭載されている各種センサーについて、アプリを開発しながら探ってみようと思います。
Enjoy the holidays!