VolleyのRequest編

21692 ワード

Request関連クラス
  • Request:要求を実装するベースクラス
  • HttpHeaderParse:応答ヘッダの解析を実現する
  • StringRequest:Stringタイプを実装する要求クラス
  • JsonRequest:Jsonタイプを実装するリクエストベースクラス
  • JsonObjectRequest:JsonObjectタイプを実装するリクエストクラス
  • JsonArrayRequest:JsonArrayタイプを実装するリクエストクラス
  • 1.Requestクラスの実装
  • 共通変数の定義
  • //         
    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());
    }
    
  • POSTタイプに対する要求体データの解析:
  • //      
    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;
            }
        }
    }
    

    注意すべき点:
  • は、Requestベースクラスで定義されたContent-Typeにjsonデータが含まれていないため、リクエストボディのContent-Typeタイプを再定義します.
  • public String getBodyContentType(){
        return "application/x-www-form-urlencoded;charset=" + getParamsEncoding();
    }
    
  • 要求体のgetBody内容を再確認し、Json定義の文字符号化の下でbyte配列タイプ
  • に変換することを保証する.
    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の実装
  • 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);
    }
    
  • 応答を解析し、Bitmapオブジェクト
  • に戻る
    @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のべき乗次数を保証する