ImageRequest.java revision ced8a98b9ffa3612656b7979f8933ae9cf19d657
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.volley.toolbox; 18 19import com.android.volley.DefaultRetryPolicy; 20import com.android.volley.NetworkResponse; 21import com.android.volley.ParseError; 22import com.android.volley.Request; 23import com.android.volley.Response; 24import com.android.volley.VolleyLog; 25 26import android.graphics.Bitmap; 27import android.graphics.Bitmap.Config; 28import android.graphics.BitmapFactory; 29import android.widget.ImageView.ScaleType; 30 31/** 32 * A canned request for getting an image at a given URL and calling 33 * back with a decoded Bitmap. 34 */ 35public class ImageRequest extends Request<Bitmap> { 36 /** Socket timeout in milliseconds for image requests */ 37 private static final int IMAGE_TIMEOUT_MS = 1000; 38 39 /** Default number of retries for image requests */ 40 private static final int IMAGE_MAX_RETRIES = 2; 41 42 /** Default backoff multiplier for image requests */ 43 private static final float IMAGE_BACKOFF_MULT = 2f; 44 45 private final Response.Listener<Bitmap> mListener; 46 private final Config mDecodeConfig; 47 private final int mMaxWidth; 48 private final int mMaxHeight; 49 private ScaleType mScaleType; 50 51 /** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */ 52 private static final Object sDecodeLock = new Object(); 53 54 /** 55 * Creates a new image request, decoding to a maximum specified width and 56 * height. If both width and height are zero, the image will be decoded to 57 * its natural size. If one of the two is nonzero, that dimension will be 58 * clamped and the other one will be set to preserve the image's aspect 59 * ratio. If both width and height are nonzero, the image will be decoded to 60 * be fit in the rectangle of dimensions width x height while keeping its 61 * aspect ratio. 62 * 63 * @param url URL of the image 64 * @param listener Listener to receive the decoded bitmap 65 * @param maxWidth Maximum width to decode this bitmap to, or zero for none 66 * @param maxHeight Maximum height to decode this bitmap to, or zero for 67 * none 68 * @param scaleType The ImageViews ScaleType used to calculate the needed image size. 69 * @param decodeConfig Format to decode the bitmap to 70 * @param errorListener Error listener, or null to ignore errors 71 */ 72 public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight, 73 ScaleType scaleType, Config decodeConfig, Response.ErrorListener errorListener) { 74 super(Method.GET, url, errorListener); 75 setRetryPolicy( 76 new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT)); 77 mListener = listener; 78 mDecodeConfig = decodeConfig; 79 mMaxWidth = maxWidth; 80 mMaxHeight = maxHeight; 81 mScaleType = scaleType; 82 } 83 84 @Override 85 public Priority getPriority() { 86 return Priority.LOW; 87 } 88 89 /** 90 * Scales one side of a rectangle to fit aspect ratio. 91 * 92 * @param maxPrimary Maximum size of the primary dimension (i.e. width for 93 * max width), or zero to maintain aspect ratio with secondary 94 * dimension 95 * @param maxSecondary Maximum size of the secondary dimension, or zero to 96 * maintain aspect ratio with primary dimension 97 * @param actualPrimary Actual size of the primary dimension 98 * @param actualSecondary Actual size of the secondary dimension 99 * @param scaleType The ScaleType used to calculate the needed image size. 100 */ 101 private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary, 102 int actualSecondary, ScaleType scaleType) { 103 104 // If no dominant value at all, just return the actual. 105 if ((maxPrimary == 0) && (maxSecondary == 0)) { 106 return actualPrimary; 107 } 108 109 // If ScaleType.FIT_XY fill the whole rectangle, ignore ratio. 110 if (scaleType == ScaleType.FIT_XY) { 111 if (maxPrimary == 0) { 112 return actualPrimary; 113 } 114 return maxPrimary; 115 } 116 117 // If primary is unspecified, scale primary to match secondary's scaling ratio. 118 if (maxPrimary == 0) { 119 double ratio = (double) maxSecondary / (double) actualSecondary; 120 return (int) (actualPrimary * ratio); 121 } 122 123 if (maxSecondary == 0) { 124 return maxPrimary; 125 } 126 127 double ratio = (double) actualSecondary / (double) actualPrimary; 128 int resized = maxPrimary; 129 130 // If ScaleType.CENTER_CROP fill the whole rectangle, preserve aspect ratio. 131 if (scaleType == ScaleType.CENTER_CROP) { 132 if ((resized * ratio) < maxSecondary) { 133 resized = (int) (maxSecondary / ratio); 134 } 135 return resized; 136 } 137 138 if ((resized * ratio) > maxSecondary) { 139 resized = (int) (maxSecondary / ratio); 140 } 141 return resized; 142 } 143 144 @Override 145 protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) { 146 // Serialize all decode on a global lock to reduce concurrent heap usage. 147 synchronized (sDecodeLock) { 148 try { 149 return doParse(response); 150 } catch (OutOfMemoryError e) { 151 VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl()); 152 return Response.error(new ParseError(e)); 153 } 154 } 155 } 156 157 /** 158 * The real guts of parseNetworkResponse. Broken out for readability. 159 */ 160 private Response<Bitmap> doParse(NetworkResponse response) { 161 byte[] data = response.data; 162 BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); 163 Bitmap bitmap = null; 164 if (mMaxWidth == 0 && mMaxHeight == 0) { 165 decodeOptions.inPreferredConfig = mDecodeConfig; 166 bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); 167 } else { 168 // If we have to resize this image, first get the natural bounds. 169 decodeOptions.inJustDecodeBounds = true; 170 BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); 171 int actualWidth = decodeOptions.outWidth; 172 int actualHeight = decodeOptions.outHeight; 173 174 // Then compute the dimensions we would ideally like to decode to. 175 int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight, 176 actualWidth, actualHeight, mScaleType); 177 int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, 178 actualHeight, actualWidth, mScaleType); 179 180 // Decode to the nearest power of two scaling factor. 181 decodeOptions.inJustDecodeBounds = false; 182 // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it? 183 // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED; 184 decodeOptions.inSampleSize = 185 findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight); 186 Bitmap tempBitmap = 187 BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions); 188 189 // If necessary, scale down to the maximal acceptable size. 190 if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth || 191 tempBitmap.getHeight() > desiredHeight)) { 192 bitmap = Bitmap.createScaledBitmap(tempBitmap, 193 desiredWidth, desiredHeight, true); 194 tempBitmap.recycle(); 195 } else { 196 bitmap = tempBitmap; 197 } 198 } 199 200 if (bitmap == null) { 201 return Response.error(new ParseError(response)); 202 } else { 203 return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response)); 204 } 205 } 206 207 @Override 208 protected void deliverResponse(Bitmap response) { 209 mListener.onResponse(response); 210 } 211 212 /** 213 * Returns the largest power-of-two divisor for use in downscaling a bitmap 214 * that will not result in the scaling past the desired dimensions. 215 * 216 * @param actualWidth Actual width of the bitmap 217 * @param actualHeight Actual height of the bitmap 218 * @param desiredWidth Desired width of the bitmap 219 * @param desiredHeight Desired height of the bitmap 220 */ 221 // Visible for testing. 222 static int findBestSampleSize( 223 int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) { 224 double wr = (double) actualWidth / desiredWidth; 225 double hr = (double) actualHeight / desiredHeight; 226 double ratio = Math.min(wr, hr); 227 float n = 1.0f; 228 while ((n * 2) <= ratio) { 229 n *= 2; 230 } 231 232 return (int) n; 233 } 234} 235