カテゴリー別アーカイブ: Android

経路情報をパースする 設計 3

RouteJsonParserさんに離婚されて、一人で子供や孫たちの面倒を見ることになったRoutesさん、母子家庭で大変なので整理してみます。
今はこういった現状

public class Routes {
	private String summary = "";
	private List<Legs> legs = null;
	private String waypoint_order = "";
	private List<OverviewPolyline> overview_polyline = null;
	private String bounds = "";
	private String copyrights = "";
	private String warnings = "";
	class Legs {
		private List<Steps> steps = null;
		private Distance distance = null;
		private Duration duration = null;
		private ArrivalTime arrival_time = null;
		private String departure_time = ""; //Time オブジェクト?
		private String start_location = "";
		private String end_location = "";
		private String start_address = "";
		private String end_address = "";
		class Steps {
			private String html_instructions = "";
			private Distance distance = null;
			private Duration duration = null;
			private String start_location = "";
			private String end_location = "";
			private List<Steps> sub_steps = null;
			private TransitDetails transit_details = null;
			class TransitDetails {
				// 乗り換えについては後で設計する
			}
		}
		class Distance {}
		class Duration {}
		class ArrivalTime {}
	}
	class OverviewPolyline {}
	
	Routes() {}

}

最初に手がけるのは、子供たちを自立させます。とくに子供や孫を抱えてるものたち!

class Legs、class OverviewPolylineを外部クラスにします。
アトリビュートを眺めると、内部クラスとするべきものがない。
ただし、クラスにすべきものがいくつかある。
waypoint_order 、bounds 、warnings などは配列もしくはデータ構造を持っている。
現在、その構造やオブジェクトの分析は行なっていないので、Stringのままとして扱う。

そうすると
Routes.java

public class Routes {
	private String summary = "";
	private List<Legs> legs = null;
	private String waypoint_order = "";
	private List<OverviewPolyline> overview_polyline = null;
	private String bounds = "";
	private String copyrights = "";
	private String warnings = "";

	Routes() {}
}

class Routes の要件は JSONObject から必要な値を取得することである。
その要件で書くと

public class Routes {
	/**
	 * ログのタグ
	 */
	private final String LOG_TAG = this.getClass().getSimpleName();
	/**
	 * JSON TAGS
	 */
	public enum Tags {
		SUMMARY("summary"),  
		WAYPOINTORDER("waypoint_order"), 
		BOUNDS("bounds"),
		COPYRIGHTS("copyrights"), 
		WARNINGS("warnings"), 
		LEGS("legs"), 
		OVERVIEWPOLYLINE("overview_polyline");
		private String name;
		Tags(String name){this.name = name;}
		public String getName(){return this.name;}
	}
	/**
	 * JSON オブジェクト
	 */
	JSONObject jsonRoutes = null;
	/**
	 * コンストラクタ
	 * @param json RouteJsonParser からわたされる
	 */
	public Routes(JSONObject json) {
		jsonRoutes = json;
	}
	public String getSummary() throws JSONException {
		return jsonRoutes.getString(Tags.SUMMARY.getName());
	}
	public String getWaypointOrder() throws JSONException {
		return jsonRoutes.getString(Tags.WAYPOINTORDER.getName());
	}
	public String getBounds() throws JSONException {
		return jsonRoutes.getString(Tags.BOUNDS.getName());
	}
	public String getCopyrights() throws JSONException {
		return jsonRoutes.getString(Tags.COPYRIGHTS.getName());
	}
	public String getWarnings() throws JSONException {
		return jsonRoutes.getString(Tags.WARNINGS.getName());
	}
	public List<Legs> getLegsList() {
		List<Legs> list = new ArrayList<Legs>();
		JSONArray array;
		try {
			array = jsonRoutes.getJSONArray(Tags.LEGS.getName());
			for(int i=0;i<array.length();i++) {
				list.add( new Legs(array.getJSONObject(i)) );
			}
		} catch (JSONException e) {
			list.clear();
			Log.e(LOG_TAG, "getLegsList() " + e.getMessage() );
		}
		return list;
	}
	public List<OverviewPolyline> getOverviewPolylineList(){
		List<OverviewPolyline> list = new ArrayList<OverviewPolyline>();
		JSONArray array;
		try {
			array = jsonRoutes.getJSONArray(Tags.OVERVIEWPOLYLINE.getName());
			for(int i=0;i<array.length();i++) {
				list.add( new OverviewPolyline(array.getJSONObject(i)) );
			}
		} catch (JSONException e) {
			list.clear();
			Log.e(LOG_TAG, "getOverviewPolyline() " + e.getMessage() );
		}
		return list;
	}
}

ちゃっかり実装してますが、してないこととして見てください。(笑)
子供たちを自立させたので、内部クラスは存在しません。ただ、アトリビュートを enum Tags で宣言しています。
enum Tags を インターフェイスにしても問題はありません。
インターフェイスについてはここでは触れません。何かに機会があれば、書くかもしれませんが・・・

このクラスの要件はJSONObjectから取得するなので、set関数は存在しません。
すべて、get関数です。
コンストラクタだけは、RouteJsonParser から 子供たちの養育費(JSONObject)をもらっています。(笑)
内部クラスであった、子や孫たちも同じように設計していけば完了です。

これだと、アトリビュートが追加されたり、バリュー(値)に若干の変更があってもクラスを派生させるなり、直接改修するなりしても他のクラスには依存してないので問題ありません。
大規模な構造変化の場合は最初から作り直さなければなりませんが(笑)

内部クラスは、その他のクラスと依存関係がないものだけにしましょう。
そのようにすると、そのクラスだけで完結できます。ただし、例外的なものはありますが、ここではふれません。

Routesさんに後日聞いてみました。
「子供たちはどうしました?」
「みんな自立して働いてます。必要なときに養育費をわけてます(依存関係)」

経路情報をパースする 設計 2

RouteJsonParser の構造は、いまだと必要のないアトリビュートが存在している。

status、class Routes に関連する子や孫たちである。

ストーリを読んでみると、RouteJsonParserさんは大変だ。自分の仕事だけでなく、子供、孫まで面倒見てるんだね。
子供が生まれたらそのつど、かみさんにはまかせておけないから、RouteJsonParserさんが面倒見るんだ、しかもその孫たちまでも、もしかしたら曾孫も面倒見るのかな?

といった感じ!

JSONデータのバージョンが変わったらそのつど、RouteJsonParser を改修し複数のバージョンに対応していく。
・・・ありえん!
これは、内部クラスのひとつの弊害である。
お手伝いしたプロジェクトで、こんな事例によく遭遇した。

なので、RouteJsonParser を書き直します。

public class RouteJsonParser {
	/**
	 * 元になる経路情報 
	 */
	private JSONObject jsonRoute = null;
	/**
	 * パースされた経路情報
	 */
	private List<Routes> routes = null;
	/**
	 * パースできない場合のエラーコード
	 */
	private int error = 0;
	/**
	 * GoogleDirectionsAPI のバージョン
	 * 2014/10/01 現在リリースされているバージョン 1 を初期値とする
	 */
	private int verGoogleDirectionsAPI = 1;
	/**
	 * コンストラクタ
	 * Google Directions API のバージョンは 1
	 */
	public RouteJsonParser() {}
	/**
	 * コンストラクタ
	 * @param ver Google Directions API のバージョン
	 */
	public RouteJsonParser(int ver) {
		verGoogleDirectionsAPI = ver;
	}
	/**
	 * JSONデータをパースする
	 * @param jsonString JSON文字列
	 * @return パースした経路情報
	 */
	public List<Routes> parse(String jsonString) {
		// バージョンごとに対応、JSONデータをパースする
		switch(verGoogleDirectionsAPI) {
		case 1:
			break;

		default:
			
		}
		return routes;
	}
	/**
	 * パースされた経路情報を取得
	 * @return
	 */
	public List<Routes> getParseListt() {
		return routes;
	}
	/**
	 * JSONデータが有効であるか検査する
	 * @return true:有効 false:無効
	 */
	public boolean isSuccess() {
		return true;
	}
	/**
	 * ステータスコードの取得
	 * @return
	 */
	public String getStatus() {
		return "";
	}
	/**
	 * エラーコードを取得
	 * @return
	 */
	public int getError() {
		return error;
	}
}

だいぶすっきり、明確になりました。

ストーリーを読んでみると、
RouteJsonParserさんのお仕事はJSONデータをパースすることで、パースした内容はいつでも引き出せるように保持する。
パースするデータがバージョンの異なるデータが放り込まれても、生まれたときに対応するバージョンが決められているので、決められたことしかしない。なので、生まれる前にバージョンをきめといてね!
パースのお仕事が済んだら、うまくできたか報告することと、ちょっとだけパースした中身のステータスを教えてくれるそうだ.
また、パースできなかった原因も報告してくれるらしい。

こんな感じで、私には読めました。いかがですか?

ちなみに、RouteJsonParserさんに Routesさんのことを聞いてみました。
「俺、Routesさんと付き合ってるんだけど、よく知らないんだ」
とのお答えでした。
以前のソースでは結婚し子供まで作っていたのに・・・・

経路情報をパースする 設計

内部クラス、無名クラスのことでぼやいていたので、そんじゃ、どうすればいいか書いてなかった。
入社1年目のシステムエンジニアやプログラマに、教えていた方法で、経路情報のパース処理について設計、実装を書いておく。

JSONデータの構造は、こちら Google Directions API を参考にした

RouteJsonParser.java

import org.json.JSONObject;

/**
 * @author Ryan's Factory
 *
 */
public class RouteJsonParser {
	private JSONObject jsonRoute = null;
	private String status = "";
	private List<Routes> routes = null;
	class Routes {
		private String summary = "";
		private List<Legs> legs = null;
		private String waypoint_order = "";
		private List<OverviewPolyline> overview_polyline = null;
		private String bounds = "";
		private String copyrights = "";
		private String warnings = "";
	}
	class Legs {
		private List<Steps> steps = null;
		private Distance distance = null;
		private Duration duration = null;
		private ArrivalTime arrival_time = null;
		private String departure_time = ""; //Time オブジェクト?
		private String start_location = "";
		private String end_location = "";
		private String start_address = "";
		private String end_address = "";
	}
	class OverviewPolyline {
	}
	class Steps {
		private String html_instructions = "";
		private Distance distance = null;
		private Duration duration = null;
		private String start_location = "";
		private String end_location = "";
		private List<Steps> sub_steps = null;
		private TransitDetails transit_details = null;
	}
	class Distance {
	}
	class Duration {
	}
	class ArrivalTime {
	}
	class TransitDetails {
		// 乗り換えについては後で設計する
	}
	RouteJsonParser() {
	}
	public List<Routes> parse(String jsonString) {
		return routes;
	}
}

書かれていたドキュメントを信じて(?)そのままをクラス構造としている。
実装するアトリビュート、オブジェクトはこの段階では具体的に書けないが、少なくとも、Jsonであることがわかっているので、基本を String と JSONObject にしている。

ドキュメントには配列の記述があるが、配列のままでは、メモリアロケーションに問題がある。
どういうことかと簡単に説明すると、配列の要素数が可変する場合、そのつどアロケーションしなければならないので、ListかMapのほうが効率がよい。
List or Map のどちらにするか?
Mapのほうが扱いやすいが、格納順序も考慮しなければいけないようなので、Listが最適だろうと判断する。

その他のオブジェクトについて判明しているのは、内部クラスで記述している。
Javaの利点、クラス設計が考えているままを記述できる。
元のデータが、構造化および機能などが設計されているので、そのままを書ける。
現段階では、内部クラスだが、設計が進めば外部クラスにしなければならないオブジェクトが見えてくる。

実装がなくても、コンパイルができるので、ほかのチームに迷惑がかからない(笑)

コーディングは文章です。Javaという言語を用いて、相手が内容を理解できるように書くのが基本です。
ソースを見れば、書いた人間のひととなりがうかがえます。

読みやすいソースは、ストーリをそのまま受け入れられます。
読みにくいソースは、ストーリが混乱したままよくわかりません。

非同期タスクでSandBoxにアクセスする 4

使用例として、GOOGLEマップの経路情報を取得してみる。

	private void searchRoute(LatLng origin, LatLng dest) {
		SandBox sandbox = new SandBox();
		sandbox.setScheme("https");
		sandbox.setAuthority("maps.googleapis.com");
		sandbox.setPath("/maps/api/directions/json");
		sandbox.putRequestor("origin", origin.latitude + "," + origin.longitude);
		sandbox.putRequestor("destination", dest.latitude + "," + dest.longitude);
		sandbox.putRequestor("language", "ja");
		sandbox.putRequestor("sensor", "true");
		sandbox.putRequestor("mode","walking");

		sandbox.setProgress(findViewById(R.id.progressBar1));
		SandBoxAsyncTask aTask = new SandBoxAsyncTask(this, sandbox, new RouteReceiver());
		aTask.execute(sandbox.getRequestor());
	}

this:アクティビティ
R.id.progressBar1:プログレスのID
RouteReceiver:受信処理で呼び出されるクラス

1、SandBox クラスを生成
2、サーバのアクセスに必要なパラメータなどを設定する
3、プログレスバーのオブジェクトを設定する
4、SandBoxAsyncTask(this, sandbox, new RouteReceiver()) で非同期タスクを生成する
5、タスクを実行する

コードはこんだけで、簡単にサンドボックスから情報を取得できる。
タスクを生成するとき設定するコールバックは、アクティビティで実装してもしても良いが、アクティビティがでかくなりすぎて可読性が悪くなる上お行儀が悪い!
なので、RouteReceiverというクラスを実装している。

RouteReceiver.java

import android.content.Context;
import android.view.View;

/**
 * @author Ryan's Factory
 *
 */
public class RouteReceiver implements SandBoxAsyncTaskCallback {
	/* (non-Javadoc)
	 * @see com.ryans.factory.introcatmap.SandBoxAsyncTaskCallback#onPreExecuteSandBox(android.content.Context, java.lang.Object)
	 */
	@Override
	public void onPreExecuteSandBox(Context context, Object progress) {
		if (progress instanceof View) {
			((View) progress).setVisibility(View.VISIBLE);
		}
	}
	/* (non-Javadoc)
	 * @see com.ryans.factory.introcatmap.SandBoxAsyncTaskCallback#onSuccessSandBox(java.lang.String, android.content.Context, java.lang.Object)
	 */
	@Override
	public void onSuccessSandBox(String jsonString, Context context, Object progress) {
		if (progress instanceof View) {
			((View) progress).setVisibility(View.GONE);
		}
		// パース処理を実装するならここに記述する
	}
	/* (non-Javadoc)
	 * @see com.ryans.factory.introcatmap.SandBoxAsyncTaskCallback#onFailedSandBox(int, android.content.Context, java.lang.Object)
	 */
	@Override
	public void onFailedSandBox(int error, Context context, Object progress) {
		if (progress instanceof View) {
			((View) progress).setVisibility(View.GONE);
		}
		// エラー処理を実装するならここに記述する
	}
}

onPreExecuteSandBox() 通信前にプログレスバーをぐるぐると表示する
onSuccessSandBox() 通信が成功したのでプログレスバーを消してJSONデータをパースする
onFailedSandBox() 通信が失敗したのでプログレスバーを消してエラーだとユーザに通知する

前に、サンプルソースや参考書などの可読性が悪いと書いたが、あるブログにアプリケーションの実行速度を高めるためにする方法なるものがあった。
その内容は、
1、get/set関数は使用しない
2、内部クラスもしくは無名クラスを多用する
3、速度優先するリソースの場合は静的に配置する
などなど・・・

そんなんじゃ、可読性が悪くなるはずだ。
そんなことやって、少しはましになるだろうけど、メンテナンスはどうするのだろう・・・
処理速度優先ならば、C/C++で書きましょう!
Javaは遅いです。Javaの利点を無くしてコードを工夫して速度を高めるなんてやめましょう!

30年ほど前に、Cでは遅すぎてユーザのストレスが高くなるのでCソース最適化なんてものがあったけれど、
アセンブラで書けばそんなことしなくても、処理速度やリソースを有効に利用できる。

それと同じじゃ!

時代が変わっても、同じような輩が発生するんだな。

非同期タスクでSandBoxにアクセスする 3

通信を実行するクラス
SandBox .java

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.util.EntityUtils;

import android.net.Uri;
import android.net.Uri.Builder;
import android.util.Log;

/**
 * サンドボックス 通信処理クラス
 * @author Ryan's Factory
 * @since 2014/10/01
 */
public class SandBox {
	
	/**
	 * ログのタグ
	 */
	private final String LOG_TAG = this.getClass().getSimpleName();
	/**
	 * クエリーパラメータのクラス
	 */
	public class Requestor {
		/**
		 * パラメータの名前
		 */
		private String name;
		/**
		 * パラメータの値
		 */
		private String parmeter;
		
		public Requestor(String name,String parmeter) {
			this.name = name;
			this.parmeter = parmeter;
		}

		public String getName() {
			return name;
		}

		public String getParmeter() {
			return parmeter;
		}
	}
	/**
	 * 接続のタイムアウト
	 * 初期値:3分
	 */
	private static final int CONNECTION_TIMEOUT = 1000 * 60 * 3;
	private int connectiion_timeout = CONNECTION_TIMEOUT;
	/**
	 * ソケットのタイムアウト
	 * 初期値:3分
	 */
	private static final int SOKET_TIMEOUT =  1000 * 60 * 3;
	private int soket_timeout = SOKET_TIMEOUT;
	/**
	 * キャラセット
	 */
	private static final String CHARSET = "utf-8";
	/**
	 * エラーコード
	 */
	private int error = 0;
	/**
	 * IOException が発生した
	 */
	public static final int IO_ERROR = 1000;
	/**
	 * ClientProtocolException が発生した
	 */
	public static final int CLIENTPROTOCOL_ERROR = 1001;
	/**
	 * プロトコル
	 */
	private String scheme;
	/**
	 * ドメイン
	 */
	private String authority;
	/**
	 * パス
	 */
	private String path;
	/**
	 * クエリーパラメータ
	 */
	private Map<String, String> mapRequestor = null;
	/**
	 * プログレスオブジェクト
	 */
	private Object progress = null;
	/**
	 * コンストラクタ
	 */
	public SandBox() {
		mapRequestor = new HashMap<String,String>();
	}
	
	public void setScheme(String scheme) {
		this.scheme = scheme;
	}

	public void setAuthority(String authority) {
		this.authority = authority;
	}

	public void setPath(String path) {
		this.path = path;
	}

	public void setConnectiion_timeout(int connectiion_timeout) {
		this.connectiion_timeout = connectiion_timeout;
	}

	public void setSoket_timeout(int soket_timeout) {
		this.soket_timeout = soket_timeout;
	}
	
	public void putRequestor(String key,String value) {
		mapRequestor.put(key, value);
	}
	
	public Requestor[] getRequestor() {
		Requestor[] params;
		if ( mapRequestor.isEmpty() ) {
			params = new Requestor[1];
			params[0]= new Requestor("","");
		} else {
			params = new Requestor[mapRequestor.size()];
			int i = 0;
			for (Iterator<Map.Entry<String, String>> it = mapRequestor.entrySet().iterator(); it.hasNext();i++ ) {
			    Map.Entry<String, String> entry = it.next();
			    params[i] = new Requestor(entry.getKey(),entry.getValue());
			}
		}
		return params;
	}
	
	/**
	 * プログレスオブジェクトを設定
	 * @param progress
	 */
	public void setProgress(Object progress) {
		this.progress = progress;
	}

	/**
	 * プログレスオブジェクトを取得
	 * @return
	 */
	public Object getProgress() {
		return progress;
	}

	public String request(Requestor[] params) {
		String resulte = "";
		error = 0;
		// クライアント生成
		HttpClient httpClient = createHttpClient();
		// リクエスト生成
		HttpUriRequest httpRequest = createHttpUriRequest(params);
		// レスポンス取得
		HttpResponse httpResponse = getHttpResponse(httpClient,httpRequest);
		// レスポンス正常?
		if ( isResponseOK(httpResponse) ) {
			// データ取得
			try {
				resulte = getTextResponse(httpResponse);
			} catch (IOException e) {
				error = IO_ERROR;
				Log.e(LOG_TAG, "" + e.getMessage() );
			}
		}
		// 切断
		httpClient.getConnectionManager().shutdown();
		return resulte;
	}
	
	public boolean isSuccess() {
		if ( 0 == error) {
			return true;
		}
		return false;
	}

	public int getErrorCode() {
		return error;
	}
	
	private boolean isResponseOK(HttpResponse httpResponse) {
		if (httpResponse != null ) {
			if ( httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK ) {
				return true;
			}
			error = httpResponse.getStatusLine().getStatusCode();
			Log.e(LOG_TAG, "" + httpResponse.getStatusLine().getReasonPhrase() );
		}
		return false;
	}
	
	private String getTextResponse(HttpResponse httpResponse) throws IOException {
		String resulte = "";
		HttpEntity httpEntity = httpResponse.getEntity();
		if( null == httpEntity ) {
			error = IO_ERROR;
			return "";
		}
		try {
			resulte = EntityUtils.toString(httpEntity, CHARSET);
		} catch (Exception e) {
			error = IO_ERROR;
			Log.e(LOG_TAG, "" + e.getMessage() );
		} finally {
			httpEntity.consumeContent();
		}
		return resulte;
	}

	private HttpClient createHttpClient() {
		DefaultHttpClient httpClient = new DefaultHttpClient();
		HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), connectiion_timeout);
		HttpConnectionParams.setSoTimeout(httpClient.getParams(), soket_timeout);
		return httpClient;
	}

	private HttpUriRequest createHttpUriRequest(Requestor[] params) {
		return new HttpGet(createUriBuilder(params).toString());
	}
	
	private Builder createUriBuilder(Requestor[] params) {
		Uri.Builder uriBuilder = new Uri.Builder();
		uriBuilder.scheme(scheme);
		uriBuilder.authority(authority);
		uriBuilder.path(path);
		for(Requestor req: params) {
			uriBuilder.appendQueryParameter(req.getName(),req.getParmeter());
		}
		return uriBuilder;
	}
	
	private HttpResponse getHttpResponse(HttpClient httpClient, HttpUriRequest httpRequest)  {
		HttpResponse httpResponse = null;
		try {
			httpResponse = httpClient.execute(httpRequest);
		} catch (ClientProtocolException e) {
			error = CLIENTPROTOCOL_ERROR;
			Log.e(LOG_TAG, "" + e.getMessage() );
		} catch (IOException e) {
			error = IO_ERROR;
			Log.e(LOG_TAG, "" + e.getMessage() );
		}
		return httpResponse;
	}
	
}

通信処理を行なう関数 String request(Requestor[] params)
パラメータを格納するクラス Requestor は使い回しするクラスではないから内部クラスとする。

通信処理が成功したか検査する関数  isSuccess()

細かいところは実際に使用する例で説明する

非同期タスクでSandBoxにアクセスする 2

インターフェイス
SandBoxAsyncTaskCallback.java

import android.content.Context;

/**
 * 非同期タスクのコールバックインターフェイス
 * @author Ryan's Factory
 * @since 2014/10/01
 *
 */
public interface SandBoxAsyncTaskCallback {
	/**
	 * 通信する前に呼び出される
	 * @param context コンストラクタにわたされた Context
	 * @param progress プログレスオブジェクト {@link SandBox#setProgress(Object)}
	 */
	public void onPreExecuteSandBox(Context context,Object progress);
	/**
	 * SandBox から JSONデータを取得できたときに呼び出される
	 * @param jsonString 取得できたJSONデータ
	 * @param context コンストラクタにわたされた Context
	 * @param progress プログレスオブジェクト {@link SandBox#setProgress(Object)}
	 */
	public void onSuccessSandBox(String jsonString,Context context,Object progress);
	/**
	 * 通信が失敗したときに呼び出される
	 * @param error サンドボックス内で発生した例外コード、もしくは HttpStatus のエラーコード
	 * @param context コンストラクタにわたされた Context
	 * @param progress プログレスオブジェクト {@link SandBox#setProgress(Object)}
	 */
	public void onFailedSandBox(int error,Context context,Object progress);
}

onPreExecuteSandBox()
コンテキストやプログレスオブジェクトが引数としてあるので、通信を開始する前にダイアログやビューなどを表示できる

onSuccessSandBox()
通信が成功したらJSONデータをパースしたり、ダイアログやビューなどを表示・消去したりできる

onFailedSandBox()
通信が失敗したときの処理を書く。ダイアログなりテキストビューなりなんなりと

非同期タスクでSandBoxにアクセスする

Android ではアクティビティ内に通信処理を直接書くとコンパイルできない。
仕方がないのでスレッド生成して書こうとしたら、AsyncTask で処理をしろと神様(Android SDK)がおっしゃってる。

案の定Web上には、中途半端なソースしかない。
サンプルを参考にしながら使い回しができるクラスを書いた。

SandBoxAsyncTask.java

/**
 * 非同期タスクでSandBoxにアクセスする
 * @author Ryan's Factory
 * @since 2014/10/01
 */
public class SandBoxAsyncTask extends AsyncTask<SandBox.Requestor, Void, String> {

	/**
	 * コンテキスト
	 */
	private Context context;
	/**
	 * コールバック
	 */
	private SandBoxAsyncTaskCallback callback;
	/**
	 * 通信処理クラス
	 */
	private SandBox sandbox;
	/**
	 * コンストラクタ
	 */
	@SuppressWarnings("unused")
	private SandBoxAsyncTask() {};
	/**
	 * コンストラクタ
	 * @param context コンテキスト
	 * @param sandbox 通信処理クラス
	 * @param callback コールバック
	 */
	public SandBoxAsyncTask(Context context, SandBox sandbox, SandBoxAsyncTaskCallback callback) {
		this.context = context;
		this.sandbox = sandbox;
		this.callback = callback;
	}
	/**
	 * 通信処理を行なう前に呼び出される
	 */
	@Override
	protected void onPreExecute() {
		callback.onPreExecuteSandBox(context,sandbox.getProgress());
	}
	/**
	 * 通信処理を呼び出す
	 */
	@Override
	protected String doInBackground(SandBox.Requestor... params) {
		return sandbox.request(params);
	}
	/**
	 * 通信処理が完了した
	 */
	@Override
	protected void onPostExecute(String result) {
		if( sandbox.isSuccess() ) {
			// 取得できた
			callback.onSuccessSandBox(result,context,sandbox.getProgress());
		} else {
			// 取得できなかった
			callback.onFailedSandBox(sandbox.getErrorCode(),context,sandbox.getProgress());
		}
	}
}
AsyncTask<SandBox.Requestor, Void, String>

第一パラメータ SandBox.Requestor:通信処理を実行する関数に渡されるパラメータ

サンドボックスクラス内で宣言する。

第二パラメータ Void:処理中に何かしたいとき中断とか、だけど実装しないのでVoidにする

第三パラメータ String:処理終了後にわたされるパラメータ

JSONデータが目的なのでStringにする

インターフェイスについては次へ

カメラのビュークラス 5

7、画像のサイズを設定
ユーザが指定する画像サイズを、設定する。
これも、機種に依存する。サポートしない画像サイズをぶっこむとどうなるかわからない。また、縦にプレビューしていても、横にしか保存されない。
保存するクラス、もしくはメソッドで縦のプレビューを横にするとか実装しなければならない。

まぁ、このメソッドで考えてもしょうがないことだから、ここまでにしておく。

8、カメラへ設定値を適用
プレビューに必要な設定値を使用するカメラに適用する。

9、プレビューの開始
設定変更が終了したので、カメラのプレビューを再開する。

例えれば、「工場長、機械を止めて設定終了しましたから、機械を動かします。ちなみに、停止していた間の映像は、消えてどこにもありませんからご了承ください」

10、例外処理
エラーが発生したら、とりあえずログに出力しておく。
機種に依存するため、例外の種類や復帰するための情報が乏しいので、今のところは何もしない。
camera = null として例外発生後は処理しないようにと思ったけど、スレッドでどんなことが起こっているかわからないから、暴挙は止めておこう。

補足として、エラーログについて書いておく。

	/**
	 * ログのタグをクラス名とする
	 */
	private final String LOG_TAG = this.getClass().getSimpleName();

Log.e(LOG_TAG, “Can not preview. ” + e.getMessage()) の LOG_TAG は、クラスの名前としている。

カメラのビュークラス 4

3、カメラの設定値を取得
使用しているカメラの情報を取得しておく。
設定を変更したらそれらを設定しなおさなきゃいけないからね。

4、プレビューサイズを選択
さてと、これがいろいろ問題を引き起こすところです。
CameraInformation に、カメラのオブジェクトとプレビューする幅と高さをわたして、カメラがサポートしているプレビューのサイズを取得する。
実装の説明は、CameraInformation の記述で書きます。
今のところ、要件として、サポートされているビューサイズの中から適切なサイズを取得してほしい。
将来、ズームアップしたプレビューなどの要件が追加されても、このメソッドが使用できるように CameraInformation で工夫しないといけないかも・・・

5、プレビューのサイズを設定
選択したプレビューサイズを、設定する。

6、画像サイズを取得
プレビューデータを保存するとき、そのサイズを設定しておかなければならないらしいが、ひとまず、ここで、サポートされている画像サイズの中から指定する画像サイズを取得する。
画像サイズの指定は、ユーザがどれどれにしたいと要望を唱えるはずだから、外部から設定する仕組みを実装しなければならない。
それが、

	/**
	 * 画像サイズの設定値 画像サイズのインデックス 0:最大 値が増えると画像サイズは小さくなる<br>
	 * 画像サイズは機種に依存するので、アプリケーションの設定などから取得、設定される。
	 */
	private int pictureSize = 0;

	/**
	 * 画像サイズを取得
	 * 
	 * @return 画像サイズのインデックス 0:最大 値が増えると画像サイズは小さくなる
	 */
	public int getPictureSize() {
		return pictureSize;
	}

	/**
	 * 画像サイズを設定
	 * 
	 * @param pictureSize
	 *            機種に依存する<br>
	 *            サポートされている画像サイズを知っておく必要がある。サポートされていない画像サイズを設定すると、プレビュー開始時に例外が発生する。<br>
	 */
	public void setPictureSize(int pictureSize) {
		this.pictureSize = pictureSize;
	}

メンバー変数の pictureSize である。

メンバー変数の命名について、少し触れておく。サンプルソースに private int mSize などと、mのSizeですと宣言されているのが良く見かける。
C++で m_ をメンバー変数につけてメンバー変数であることがわかりやすいと、規約されていたことがあったが、もしかして、その m ?
もしそうであれば、私はあえてそうはしない!
だって、メンバー変数かどうか、ソースの可読性を良くする手段とはおもえないから!
メソッド内で宣言している以外の変数であればメンバー変数でしょう。C++ではグローバル変数やマクロ変数などクラスやメソッド内で使用される変数のスコープが曖昧である。そのために、g_ だとか m_ だとか区別していた。
Javaでは必要ないでしょう。もし、必要であるなら、クラス設計そのものがおかしい!

それと、やたら public  変数が多い。
get 、setをコーディングするのが面倒? いやいやツールが自動生成してくれる。
参考書として説明を書いていくとページ内で収まらない? いやいやそんなことするから、使えないSE、プログラマを量産する、仕組みを構築していくのだ!
あくまでも参考書というのは、初心者やそれを学ぼうとしている者が読むものである。
報告書類、論文や研究成果の発表ならばいいけど・・・

なので、私は m には なりたくないから m を つけない。

わき道がたくさんあるので、次へとつづく・・・ To Be Continued…

カメラのビュークラス 3

サーフェイス変更イベントについて

	/*
	 * (non-Javadoc) サーフェイス変更イベントの処理
	 * 
	 * @see android.view.SurfaceHolder.Callback#surfaceChanged(android.view.SurfaceHolder, int, int, int)
	 */
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		CameraInformation info = new CameraInformation();
		Size previewSize = null;
		Size pictureSize = null;
		Log.d(LOG_TAG, "DefaultDisplay Rotation=" + getDefaultDisplayRotation());
		Log.d(LOG_TAG, "format=" + format + " width=" + width + " height=" + height);
		try {
			if (null != camera) {
				// プレビューの停止
				camera.stopPreview();
				// ローテーションを設定
				info.setRotation(getDefaultDisplayRotation());
				// カメラの設定値を取得
				Camera.Parameters params = camera.getParameters();
				// プレビューサイズを選択
				previewSize = info.getPreviewSize(camera, width, height);
				// プレビューのサイズを設定
				params.setPreviewSize(previewSize.width, previewSize.height);
				// 画像サイズを取得
				pictureSize = info.getPictureSize(camera, this.pictureSize);
				// 画像のサイズを設定
				params.setPictureSize(pictureSize.width, pictureSize.height);
				// カメラへ設定値を適用
				camera.setParameters(params);
				// プレビューの開始
				camera.startPreview();

				Log.d(LOG_TAG, "preview width=" + previewSize.width + " height=" + previewSize.height);
				Log.d(LOG_TAG, "picture width=" + pictureSize.width + " height=" + pictureSize.height);
			}
		} catch (Exception e) {
			Log.e(LOG_TAG, "Can not preview. " + e.getMessage());
		}
	}

カメラのプレビュー表示につて、説明をします。
1、プレビューの停止
使用しているカメラを camera.stopPreview() で停止する。

プレビューの停止しなくてもいいけど、したほうが安全である。
なぜか?

例えば、工場の機械を設定してくれよとお客様から要望があったとする。
「機械は止めちゃだめだよ。止めたら損害賠償を請求するからね!」なんて無茶な要件と同じである。
コンベアに巻き込まれて、手や足が挟まれてぐちゃぐちゃになってもいい、動いている機械の内部に入って設定するぞ!
なんて、猛者ならいいですよ。

自分にはできない・・・けど、金銭保障するからお願いと、懇願されたらするかも・・・

2、 ローテーションを設定
スマホやタブレットを立てて使用しているか横向きで使用しているかを設定します。

ほとんどのサイトで、横で使用するのが標準だけど立てて使用する。(呪縛)
標準が横なら横でいいじゃん!
使う人が、横で使おうがねっころがって逆さまで使おうがいいのでは?
回転させたくなかったら、使う人が回転をロック(固定)すればいいだけでしょう?
なので、「横でも縦でも好きにしてくれ!」要件を実装します。

さてと、回転の情報をどこから取得するかというと、ウインドウマネージャに聞くといいらしいので、getDefaultDisplayRotation() を実装します。

	/**
	 * 回転の状態を取得
	 * 
	 * @return Surface.ROTATION_0 or Surface.ROTATION_90 , or Surface.ROTATION_180, or Surface.ROTATION_270.
	 */
	public int getDefaultDisplayRotation() {
		return getWindowManager().getDefaultDisplay().getRotation();
	}

	/**
	 * ウインドウマネージャーを取得
	 * 
	 * @return
	 */
	protected WindowManager getWindowManager() {
		return (WindowManager) this.getContext().getSystemService(Context.WINDOW_SERVICE);
	}

ウインドウマネージャーを呼び出して、デフォルトのディスプレイ情報から回転情報を取得します。
そんじゃ、ウインドウマネジャーはどこから呼び出せばいいかというと、コンテキストからサービスを指定して取得します。
ウインドウマネジャーは、よそものから使われたくないので protected にしておきます。

参考書やよくあるサイトだと

public static void setCameraDisplayOrientation(Activity activity, 
         int cameraId, android.hardware.Camera camera) {
      android.hardware.Camera.CameraInfo info =
              new android.hardware.Camera.CameraInfo();
      android.hardware.Camera.getCameraInfo(cameraId, info);
      int rotation = activity.getWindowManager().getDefaultDisplay() 
             .getRotation();
      int degrees = 0;
      switch (rotation) {
          case Surface.ROTATION_0: degrees = 0; break;
          case Surface.ROTATION_90: degrees = 90; break;
          case Surface.ROTATION_180: degrees = 180; break;
          case Surface.ROTATION_270: degrees = 270; break;
      }
       int result;
      if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
          result = (info.orientation + degrees) % 360;
          result = (360 - result) % 360;  // compensate the mirror
      } else {  // back-facing
          result = (info.orientation - degrees + 360) % 360;
      }
      camera.setDisplayOrientation(result);
}  

「このソースを参考にして回転情報の取得メソッドを実装します。」とか書かれてるけど、なんでやねん!
難しいこと書かんで、いいでしょう!

「コンテキストからサービスを指定して、ウインドウマネージャーを呼び出し、デフォルトのディスプレイ情報から回転情報を取得します。」
これでいいのだ!

長くなりそうなので、次へと続く・・・ To Be Continued…