VolleyのRequest編
21692 ワード
Request関連クラス Request:要求を実装するベースクラス HttpHeaderParse:応答ヘッダの解析を実現する StringRequest:Stringタイプを実装する要求クラス JsonRequest:Jsonタイプを実装するリクエストベースクラス JsonObjectRequest:JsonObjectタイプを実装するリクエストクラス JsonArrayRequest:JsonArrayタイプを実装するリクエストクラス 1.Requestクラスの実装共通変数の定義 POSTタイプに対する要求体データの解析: 実装要求の優先度
ここで、優先度の高いスレッドに対応する数値は小さいことに注意してください.ordinal関数の使用については、対応する変数に値を割り当てないと、デフォルトで最初の値は0で、1つずつ増加します.次のようになります.キャンセル要求イベント
実際,ここではログ記録スレッド時間を学習する際に,イベントの秩序性を保証するためにメインスレッドコミット方式を採用することに重点を置いている.そうでなければ,他のスレッド処理に渡すと,イベントの優先順位を確保できない.
2.HttpHeaderParseの実現応答ヘッダの文字符号化 を取得する.
具体的な例は上記のように、charsetの対応するコンテンツを取得することです.応答ヘッダの解析:
具体例は以上のように,我々が取得する情報は主にEntryオブジェクトを構築するために用いられる.
注意すべき点は、日付の処理をLongタイプに変換して保存することです
3.JsonRequestの実装
注意すべき点:は、Requestベースクラスで定義されたContent-Typeにjsonデータが含まれていないため、リクエストボディのContent-Typeタイプを再定義します. 要求体のgetBody内容を再確認し、Json定義の文字符号化の下でbyte配列タイプ に変換することを保証する.
4.JsonObjectRequestの実装
実際、JsonRequest実装に基づいて、追加するコンテンツは非常に簡単です.ただし,そのコンストラクション関数の使用に注意し,入力されたJSOnObjectタイプパラメータから空か否かを判断し,その要求方式を決定する.
5.JsonArrayRequestの実装
実際,この実装はJsonObjectRequestと同様であるが,応答を解析する際に返されるデータ型が異なるにすぎない.
6.StringRequestの実装
実際には,結果をlistenerで配布するステップと,応答をStringタイプに解析するステップの2つを行った.
7.ImageRequestの実装 ImageRequestで一般的な変数の定義 応答を解析し、Bitmapオブジェクト に戻る
解析過程は単一スレッドで行うことを保証し,同じ図で複数回解析することを防止し,不要な浪費をもたらすことが分かる.
以上が画像の解析過程であり,主な流れは,画像の予想される幅を取得すること,画像のスケーリング比を取得すること,対応するBitmapオブジェクトを取得することである.
画像の予想幅を取得
注意FIT_XYとCENTER_CROPの2種類の異なる処理
画像の最適圧縮比を取得します.
以上のように、圧縮比2のべき乗次数を保証する
//
private static final String DEFAULT_PARAMS_ENCODING = "UTF-8";
//
public interface Method {
int DEPRECATED_GET_OR_POST = -1;
int GET = 0;
int POST = 1;
int PUT = 2;
int DELETE = 3;
int HEAD = 4;
int OPTIONS = 5;
int TRACE = 6;
int PATCH = 7;
}
// ,
private final VolleyLog.MarkerLog mEventLog = VolleyLog.MarkerLog.ENABLED ? new VolleyLog.MarkerLog() : null;
private final int mMethod;
private final String mUrl;
//
private final int mDefaultTrafficStatsTag;
/** Listener interface for errors. */
private final Response.ErrorListener mErrorListener;
// , , FIFO
private Integer mSequence;
//
private RequestQueue mRequestQueue;
//
private boolean mShouldCache = true;
//
private boolean mCanceled = false;
//
private boolean mResponseDelivered = false;
/** Whether the request should be retried in the event of an HTTP 5xx (server) error. */
private boolean mShouldRetryServerErrors = false;
//
private RetryPolicy mRetryPolicy;
//
private Cache.Entry mCacheEntry = null;
// ,
private Object mTag;
public Request(String url, Response.ErrorListener listener){
this(Method.DEPRECATED_GET_OR_POST, url, listener);
}
public Request(int method, String url, Response.ErrorListener listener){
mMethod = method;
mUrl = url;
mErrorListener = listener;
mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
setRetryPolicy(new DefaultRetryPolicy());
}
//
public byte[] getPostBody() throws AuthFailureError{
Map postParams = getParams();
if (postParams != null && postParams.size() > 0){
return encodeParameter(postParams, getPostParamsEncoding());
}
return null;
}
// , byte
private byte[] encodeParameter(Map params, String paramEncoding){
StringBuilder encodedParams = new StringBuilder();
try {
for (Map.Entry entry : params.entrySet()){
encodedParams.append(URLEncoder.encode(entry.getKey(),paramEncoding));
encodedParams.append("=");
encodedParams.append(URLEncoder.encode(entry.getValue(), paramEncoding));
encodedParams.append("&");
}
return encodedParams.toString().getBytes(paramEncoding);
} catch (UnsupportedEncodingException uee){
throw new RuntimeException("Encoding not supported: " + paramEncoding, uee);
}
}
public enum Priority{
LOW,
NORMAL,
HIGH,
IMMEDIATE
}
//
public Priority getPriority(){
return Priority.NORMAL;
}
// “ ”: , ,
@Override
public int compareTo(Request o) {
//
Priority left = this.getPriority();
Priority right = o.getPriority();
// High-priority requests are "lesser" so they are sorted to the front.
// Equal priorities are sorted by sequence number to provide FIFO ordering.
//
return left == right ?
this.mSequence - o.mSequence :
right.ordinal() - left.ordinal();
}
ここで、優先度の高いスレッドに対応する数値は小さいことに注意してください.ordinal関数の使用については、対応する変数に値を割り当てないと、デフォルトで最初の値は0で、1つずつ増加します.次のようになります.
enum Priority{
LOW,
NORMAL,
HIGH
}
public static void main(String[] args){
System.out.print(Priority.LOW.ordinal());
System.out.print(Priority.NORMAL.ordinal());
System.out.println(Priority.HIGH.ordinal());
}
:
0 1 2
//
void finish(final String tag){
if (mRequestQueue != null){
mRequestQueue.finish(this);
}
if (VolleyLog.MarkerLog.ENABLED){
final long threadId = Thread.currentThread().getId();
// ,
if (Looper.getMainLooper() != Looper.myLooper()){
Handler mainThread = new Handler(Looper.getMainLooper());
mainThread.post(new Runnable() {
@Override
public void run() {
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
}
});
return;
}
mEventLog.add(tag, threadId);
mEventLog.finish(this.toString());
}
}
実際,ここではログ記録スレッド時間を学習する際に,イベントの秩序性を保証するためにメインスレッドコミット方式を採用することに重点を置いている.そうでなければ,他のスレッド処理に渡すと,イベントの優先順位を確保できない.
2.HttpHeaderParseの実現
Content-Type: application/x-www-form-urlencoded;charset=utf-8;
具体的な例は上記のように、charsetの対応するコンテンツを取得することです.
//
public static String parseCharset(Map headers, String defaultCharset){
//
String contentType = headers.get("Content-Type");
if (contentType != null){
String[] params = contentType.split(";");
for (int i = 0; i < params.length; i ++){
//trim
String[] pair = params[i].trim().split("=");
if (pair.length == 2 && pair[0].equals("charset")){
return pair[1];
}
}
}
return defaultCharset;
}
public static String parseCharset(Map headers){
return parseCharset(headers, HTTP.DEFAULT_CONTENT_CHARSET);
}
HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain
Cache-Control: no-cache
具体例は以上のように,我々が取得する情報は主にEntryオブジェクトを構築するために用いられる.
// , Entry
public static Cache.Entry parseCacheHeaders(NetworkResponse response){
//
long now = System.currentTimeMillis();
Map headers = response.headers;
//
long serverDate = 0;
//
long lastModified = 0;
//
// : , Entry softTtl ttl
long serverExpires = 0;
long softExpire = 0;
long finalExpire = 0;
//
long maxAge = 0;
//
long staleWhileRevalidate = 0;
//
boolean hasCacheControl = false;
//
boolean mustRevalidate = false;
String serverETag = null;
String headValue;
headValue = headers.get("Date");
if (headValue != null){
serverDate = parseDateAsEpoch(headValue);
}
headValue = headers.get("Cache-Control");
//
if (headValue != null){
hasCacheControl = true;
String[] tokens = headValue.split(",");
for (int i = 0; i < tokens.length; i ++){
String token = tokens[i].trim();
// , null
if (token.equals("no-cache") || token.equals("no-store")){
return null;
} else if (token.startsWith("max-age=")){
try {
maxAge = Long.parseLong(token.substring(8));
} catch (Exception e){
}
} else if (token.startsWith("stale-with-revalidate=")){
try {
staleWhileRevalidate = Long.parseLong(token.substring(23));
} catch (Exception e){
}
} else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")){
mustRevalidate = true;
}
}
}
headValue = headers.get("Expires");
if (headValue != null){
serverExpires = parseDateAsEpoch(headValue);
}
headValue = headers.get("last-Modified");
if (headValue != null){
lastModified = parseDateAsEpoch(headValue);
}
serverETag = headers.get("ETag");
// Entry ttl softTtl
if (hasCacheControl){
// :
softExpire = now + maxAge * 1000;
// ,
finalExpire = mustRevalidate ?
softExpire : softExpire + staleWhileRevalidate * 1000;
} else if (serverDate > 0 && serverExpires >= serverDate){
softExpire = now + (serverExpires - serverDate);
finalExpire = softExpire;
}
// Entry
Cache.Entry entry = new Cache.Entry();
entry.data = response.data;
entry.eTag = serverETag;
entry.softTtl = softExpire;
entry.ttl = finalExpire;
entry.serverDate = serverDate;
entry.lastModified = lastModified;
entry.responseHeaders = headers;
return entry;
}
// Long
private static long parseDateAsEpoch(String headValue) {
try {
return DateUtils.parseDate(headValue).getTime();
} catch (DateParseException e){
return 0;
}
}
注意すべき点は、日付の処理をLongタイプに変換して保存することです
3.JsonRequestの実装
public abstract class JsonRequest extends Request{
//
protected static final String PROTOCOL_CHARSET = "utf-8";
//
private static final String PROTOCOL_CONTENT_TYPE =
String.format("application/json;charset=%s",PROTOCOL_CHARSET);
private final Response.Listener mListener;
//
private final String mRequestBody;
public JsonRequest(String url, String requestBody, Response.Listener listener,
Response.ErrorListener errorListener){
this(Method.DEPRECATED_GET_OR_POST, url, requestBody, listener, errorListener);
}
public JsonRequest(int method, String url, String requestBody, Response.Listener listener,
Response.ErrorListener errorListener) {
super(method, url, errorListener);
mListener = listener;
mRequestBody = requestBody;
}
@Override
protected abstract Response parseNetworkResponse(NetworkResponse response);
@Override
protected void deliverResponse(T response) {
mListener.onResponse(response);
}
@Override
public String getPostBodyContentType() {
return getBodyContentType();
}
// Content-Type
@Override
public String getBodyContentType() {
return PROTOCOL_CONTENT_TYPE;
}
@Override
public byte[] getPostBody() throws AuthFailureError {
return getBody();
}
// , byte
@Override
public byte[] getBody() throws AuthFailureError {
try {
return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);
} catch (UnsupportedEncodingException e){
VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s",
mRequestBody, PROTOCOL_CHARSET);
return null;
}
}
}
注意すべき点:
public String getBodyContentType(){
return "application/x-www-form-urlencoded;charset=" + getParamsEncoding();
}
4.JsonObjectRequestの実装
public class JsonObjectRequest extends JsonRequest {
// JsonObject
public JsonObjectRequest(int method, String url, JSONObject jsonRequest, Response.Listener listener, Response.ErrorListener errorListener) {
super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(),
listener, errorListener);
}
// JsonObject
public JsonObjectRequest(String url, JSONObject jsonRequest, Response.Listener listener, Response.ErrorListener errorListener) {
this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest,
listener, errorListener);
}
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
try {
// String
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
// JsonObject
return Response.success(new JSONObject(jsonString),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e){
return Response.error(new ParseError(e));
} catch (JSONException je){
return Response.error(new ParseError(je));
}
}
}
実際、JsonRequest実装に基づいて、追加するコンテンツは非常に簡単です.ただし,そのコンストラクション関数の使用に注意し,入力されたJSOnObjectタイプパラメータから空か否かを判断し,その要求方式を決定する.
5.JsonArrayRequestの実装
public class JsonArrayRequest extends JsonRequest {
public JsonArrayRequest(String url, Response.Listener listener,
Response.ErrorListener errorListener){
this(Method.GET, url, null, listener, errorListener);
}
public JsonArrayRequest(int method, String url, JSONArray jsonRequest, Response.Listener listener, Response.ErrorListener errorListener) {
super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, errorListener);
}
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
try {
String jsonString = new String(response.data,
HttpHeaderParser.parseCharset(response.headers));
// JsonArray
return Response.success(new JSONArray(jsonString),
HttpHeaderParser.parseCacheHeaders(response));
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
} catch (JSONException je){
return Response.error(new ParseError(je));
}
}
}
実際,この実装はJsonObjectRequestと同様であるが,応答を解析する際に返されるデータ型が異なるにすぎない.
6.StringRequestの実装
public class StringRequest extends Request {
private final Response.Listener mListener;
public StringRequest(String url, Response.Listener listener,
Response.ErrorListener errorListener) {
super(url, errorListener);
mListener = listener;
}
public StringRequest(int method, String url, Response.Listener listener,
Response.ErrorListener errorListener){
super(method, url, errorListener);
mListener = listener;
}
// ,
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e){
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response ));
}
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
}
実際には,結果をlistenerで配布するステップと,応答をStringタイプに解析するステップの2つを行った.
7.ImageRequestの実装
//
public static final int DEFAULT_IMAGE_TIMEOUT_MS = 1000;
//
public static final int DEFAULT_IMAGE_MAX_RETRIES = 2;
public static final float DEFAULT_IMAGE_BACKOFF_MULT = 2f;
private final Response.Listener mListener;
// , ARGB_8888
private final Bitmap.Config mDecodeConfig;
//
private final int mMaxWidth;
private final int mMaxHeight;
//
private final ImageView.ScaleType mScaleType;
// , ,
private static final Object sDecodeLock = new Object();
public ImageRequest(String url, Response.Listener listener, int maxWidth, int maxHeight,
ImageView.ScaleType scaleType, Bitmap.Config decodeConfig, Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
setRetryPolicy(new DefaultRetryPolicy(DEFAULT_IMAGE_TIMEOUT_MS,DEFAULT_IMAGE_MAX_RETRIES
, DEFAULT_IMAGE_BACKOFF_MULT));
mListener = listener;
mDecodeConfig = decodeConfig;
mMaxHeight = maxHeight;
mMaxWidth = maxWidth;
mScaleType = scaleType;
}
public ImageRequest(String url, Response.Listener listener, int maxWidth, int maxHeight,
Bitmap.Config decodeConfig, Response.ErrorListener errorListener) {
this(url, listener, maxWidth, maxHeight, ImageView.ScaleType.CENTER_INSIDE,
decodeConfig, errorListener);
}
@Override
protected Response parseNetworkResponse(NetworkResponse response) {
//
synchronized (sDecodeLock){
try {
return doParse(response);
} catch (OutOfMemoryError e){
VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length);
return Response.error(new ParseError(e));
}
}
解析過程は単一スレッドで行うことを保証し,同じ図で複数回解析することを防止し,不要な浪費をもたらすことが分かる.
//
private Response doParse(NetworkResponse response) {
byte[] data = response.data;
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
Bitmap bitmap = null;
// 0,
if (mMaxWidth == 0 && mMaxHeight == 0){
//
decodeOptions.inPreferredConfig = mDecodeConfig;
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
} else {
//
decodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
//
int actualWidth = decodeOptions.outWidth;
int actualHeight = decodeOptions.outHeight;
//
int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight, actualWidth,
actualHeight, mScaleType);
int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight,
actualWidth, mScaleType);
// , Bitmap
decodeOptions.inJustDecodeBounds = false;
//
decodeOptions.inSampleSize = findBestSimpleSize(actualWidth, actualHeight,
desiredWidth, desiredHeight);
//???
decodeOptions.inPreferredConfig = mDecodeConfig;
// Bitmap
Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length,
decodeOptions);
// ,
if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
tempBitmap.getHeight() > desiredHeight)){
bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
// Bitmap
tempBitmap.recycle();
} else {
bitmap = tempBitmap;
}
}
//
if (bitmap == null){
return Response.error(new ParseError(response));
} else {
return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
}
}
以上が画像の解析過程であり,主な流れは,画像の予想される幅を取得すること,画像のスケーリング比を取得すること,対応するBitmapオブジェクトを取得することである.
画像の予想幅を取得
//
private int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
int actualSecondary, ImageView.ScaleType scaleType) {
// ,
if (maxPrimary == 0 && maxSecondary == 0){
return actualPrimary;
}
//FIT_XY: ,
if (scaleType == ImageView.ScaleType.FIT_XY){
if (maxPrimary == 0){
return actualPrimary;
}
return maxPrimary;
}
// If primary is unspecified, scale primary to match secondary's scaling ratio.
if (maxPrimary == 0) {
double ratio = (double) maxSecondary / (double) actualSecondary;
return (int) (actualPrimary * ratio);
}
if (maxSecondary == 0){
return maxPrimary;
}
double ratio = (double) actualSecondary / (double) actualPrimary;
int resized = maxPrimary;
//CENTER_CROP: , ,
if (scaleType == ImageView.ScaleType.CENTER_CROP){
if ((resized * ratio) < maxSecondary){
resized = (int)(maxSecondary / ratio);
}
return resized;
}
// ,
if ((resized * ratio) > maxSecondary){
resized = (int)(maxSecondary / ratio);
}
return resized;
}
注意FIT_XYとCENTER_CROPの2種類の異なる処理
画像の最適圧縮比を取得します.
static int findBestSimpleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
double wr = (double) actualWidth / desiredWidth;
double hr = (double) actualHeight / desiredHeight;
double ratio = Math.min(wr, hr);
float n = 1.0f;
while ((n * 2) <= ratio){
n *= 2;
}
return (int)n;
}
以上のように、圧縮比2のべき乗次数を保証する