Skip to content

Commit d3a6b45

Browse files
committed
Merge pull request square#512 from lucasr/request-handler
Support custom bitmap loading with a new RequestHandler API
2 parents 28b27a9 + 4c6cf23 commit d3a6b45

24 files changed

+656
-364
lines changed

picasso/src/main/java/com/squareup/picasso/AssetBitmapHunter.java renamed to picasso/src/main/java/com/squareup/picasso/AssetRequestHandler.java

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,54 @@
1+
/*
2+
* Copyright (C) 2013 Square, Inc.
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+
*/
116
package com.squareup.picasso;
217

318
import android.content.Context;
419
import android.content.res.AssetManager;
520
import android.graphics.Bitmap;
621
import android.graphics.BitmapFactory;
22+
import android.net.Uri;
723
import java.io.IOException;
824
import java.io.InputStream;
925

1026
import static android.content.ContentResolver.SCHEME_FILE;
1127
import static com.squareup.picasso.Picasso.LoadedFrom.DISK;
1228

13-
class AssetBitmapHunter extends BitmapHunter {
29+
class AssetRequestHandler extends RequestHandler {
1430
protected static final String ANDROID_ASSET = "android_asset";
1531
private static final int ASSET_PREFIX_LENGTH =
1632
(SCHEME_FILE + ":///" + ANDROID_ASSET + "/").length();
1733

1834
private final AssetManager assetManager;
1935

20-
public AssetBitmapHunter(Context context, Picasso picasso, Dispatcher dispatcher, Cache cache,
21-
Stats stats, Action action) {
22-
super(picasso, dispatcher, cache, stats, action);
36+
public AssetRequestHandler(Context context) {
2337
assetManager = context.getAssets();
2438
}
2539

26-
@Override Bitmap decode(Request data) throws IOException {
27-
String filePath = data.uri.toString().substring(ASSET_PREFIX_LENGTH);
28-
return decodeAsset(filePath);
40+
@Override public boolean canHandleRequest(Request data) {
41+
Uri uri = data.uri;
42+
return (SCHEME_FILE.equals(uri.getScheme())
43+
&& !uri.getPathSegments().isEmpty() && ANDROID_ASSET.equals(uri.getPathSegments().get(0)));
2944
}
3045

31-
@Override Picasso.LoadedFrom getLoadedFrom() {
32-
return DISK;
46+
@Override public Result load(Request data) throws IOException {
47+
String filePath = data.uri.toString().substring(ASSET_PREFIX_LENGTH);
48+
return new Result(decodeAsset(data, filePath), DISK);
3349
}
3450

35-
Bitmap decodeAsset(String filePath) throws IOException {
51+
Bitmap decodeAsset(Request data, String filePath) throws IOException {
3652
final BitmapFactory.Options options = createBitmapOptions(data);
3753
if (requiresInSampleSize(options)) {
3854
InputStream is = null;

picasso/src/main/java/com/squareup/picasso/BitmapHunter.java

Lines changed: 32 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,16 @@
1515
*/
1616
package com.squareup.picasso;
1717

18-
import android.content.Context;
1918
import android.graphics.Bitmap;
20-
import android.graphics.BitmapFactory;
2119
import android.graphics.Matrix;
2220
import android.net.NetworkInfo;
23-
import android.net.Uri;
24-
import android.provider.MediaStore;
2521
import java.io.IOException;
2622
import java.io.PrintWriter;
2723
import java.io.StringWriter;
2824
import java.util.ArrayList;
2925
import java.util.List;
3026
import java.util.concurrent.Future;
3127

32-
import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;
33-
import static android.content.ContentResolver.SCHEME_CONTENT;
34-
import static android.content.ContentResolver.SCHEME_FILE;
35-
import static android.provider.ContactsContract.Contacts;
36-
import static com.squareup.picasso.AssetBitmapHunter.ANDROID_ASSET;
3728
import static com.squareup.picasso.Picasso.LoadedFrom.MEMORY;
3829
import static com.squareup.picasso.Utils.OWNER_HUNTER;
3930
import static com.squareup.picasso.Utils.VERB_DECODED;
@@ -44,8 +35,7 @@
4435
import static com.squareup.picasso.Utils.getLogIdsForHunter;
4536
import static com.squareup.picasso.Utils.log;
4637

47-
abstract class BitmapHunter implements Runnable {
48-
38+
class BitmapHunter implements Runnable {
4939
/**
5040
* Global lock for bitmap decoding to ensure that we are only are decoding one at a time. Since
5141
* this will only ever happen in background threads we help avoid excessive memory thrashing as
@@ -66,6 +56,7 @@ abstract class BitmapHunter implements Runnable {
6656
final String key;
6757
final Request data;
6858
final boolean skipMemoryCache;
59+
final RequestHandler requestHandler;
6960

7061
Action action;
7162
List<Action> actions;
@@ -74,22 +65,22 @@ abstract class BitmapHunter implements Runnable {
7465
Picasso.LoadedFrom loadedFrom;
7566
Exception exception;
7667
int exifRotation; // Determined during decoding of original resource.
68+
int retryCount;
7769

78-
BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action) {
70+
BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action,
71+
RequestHandler requestHandler) {
7972
this.picasso = picasso;
8073
this.dispatcher = dispatcher;
8174
this.cache = cache;
8275
this.stats = stats;
8376
this.key = action.getKey();
8477
this.data = action.getRequest();
8578
this.skipMemoryCache = action.skipCache;
79+
this.requestHandler = requestHandler;
80+
this.retryCount = requestHandler.getRetryCount();
8681
this.action = action;
8782
}
8883

89-
protected void setExifRotation(int exifRotation) {
90-
this.exifRotation = exifRotation;
91-
}
92-
9384
@Override public void run() {
9485
try {
9586
updateThreadName(data);
@@ -124,10 +115,8 @@ protected void setExifRotation(int exifRotation) {
124115
}
125116
}
126117

127-
abstract Bitmap decode(Request data) throws IOException;
128-
129118
Bitmap hunt() throws IOException {
130-
Bitmap bitmap;
119+
Bitmap bitmap = null;
131120

132121
if (!skipMemoryCache) {
133122
bitmap = cache.get(key);
@@ -141,7 +130,13 @@ Bitmap hunt() throws IOException {
141130
}
142131
}
143132

144-
bitmap = decode(data);
133+
data.loadFromLocalCacheOnly = (retryCount == 0);
134+
RequestHandler.Result result = requestHandler.load(data);
135+
if (result != null) {
136+
bitmap = result.getBitmap();
137+
loadedFrom = result.getLoadedFrom();
138+
exifRotation = result.getExifOrientation();
139+
}
145140

146141
if (bitmap != null) {
147142
if (picasso.loggingEnabled) {
@@ -227,11 +222,16 @@ boolean shouldSkipMemoryCache() {
227222
}
228223

229224
boolean shouldRetry(boolean airplaneMode, NetworkInfo info) {
230-
return false;
225+
boolean hasRetries = retryCount > 0;
226+
if (!hasRetries) {
227+
return false;
228+
}
229+
retryCount--;
230+
return requestHandler.shouldRetry(airplaneMode, info);
231231
}
232232

233233
boolean supportsReplay() {
234-
return false;
234+
return requestHandler.supportsReplay();
235235
}
236236

237237
Bitmap getResult() {
@@ -276,70 +276,20 @@ static void updateThreadName(Request data) {
276276
Thread.currentThread().setName(builder.toString());
277277
}
278278

279-
static BitmapHunter forRequest(Context context, Picasso picasso, Dispatcher dispatcher,
280-
Cache cache, Stats stats, Action action, Downloader downloader) {
281-
if (action.getRequest().resourceId != 0) {
282-
return new ResourceBitmapHunter(context, picasso, dispatcher, cache, stats, action);
283-
}
284-
Uri uri = action.getRequest().uri;
285-
String scheme = uri.getScheme();
286-
if (SCHEME_CONTENT.equals(scheme)) {
287-
if (Contacts.CONTENT_URI.getHost().equals(uri.getHost()) //
288-
&& !uri.getPathSegments().contains(Contacts.Photo.CONTENT_DIRECTORY)) {
289-
return new ContactsPhotoBitmapHunter(context, picasso, dispatcher, cache, stats, action);
290-
} else if (MediaStore.AUTHORITY.equals(uri.getAuthority())) {
291-
return new MediaStoreBitmapHunter(context, picasso, dispatcher, cache, stats, action);
292-
} else {
293-
return new ContentStreamBitmapHunter(context, picasso, dispatcher, cache, stats, action);
294-
}
295-
} else if (SCHEME_FILE.equals(scheme)) {
296-
if (!uri.getPathSegments().isEmpty() && ANDROID_ASSET.equals(uri.getPathSegments().get(0))) {
297-
return new AssetBitmapHunter(context, picasso, dispatcher, cache, stats, action);
298-
}
299-
return new FileBitmapHunter(context, picasso, dispatcher, cache, stats, action);
300-
} else if (SCHEME_ANDROID_RESOURCE.equals(scheme)) {
301-
return new ResourceBitmapHunter(context, picasso, dispatcher, cache, stats, action);
302-
} else {
303-
return new NetworkBitmapHunter(picasso, dispatcher, cache, stats, action, downloader);
304-
}
305-
}
279+
static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher,
280+
Cache cache, Stats stats, Action action) {
281+
Request request = action.getRequest();
306282

307-
/**
308-
* Lazily create {@link android.graphics.BitmapFactory.Options} based in given
309-
* {@link com.squareup.picasso.Request}, only instantiating them if needed.
310-
*/
311-
static BitmapFactory.Options createBitmapOptions(Request data) {
312-
final boolean justBounds = data.hasSize();
313-
final boolean hasConfig = data.config != null;
314-
BitmapFactory.Options options = null;
315-
if (justBounds || hasConfig) {
316-
options = new BitmapFactory.Options();
317-
options.inJustDecodeBounds = justBounds;
318-
if (hasConfig) {
319-
options.inPreferredConfig = data.config;
283+
List<RequestHandler> requestHandlers = picasso.getRequestHandlers();
284+
final int count = requestHandlers.size();
285+
for (int i = 0; i < count; i++) {
286+
RequestHandler requestHandler = requestHandlers.get(i);
287+
if (requestHandler.canHandleRequest(request)) {
288+
return new BitmapHunter(picasso, dispatcher, cache, stats, action, requestHandler);
320289
}
321290
}
322-
return options;
323-
}
324291

325-
static boolean requiresInSampleSize(BitmapFactory.Options options) {
326-
return options != null && options.inJustDecodeBounds;
327-
}
328-
329-
static void calculateInSampleSize(int reqWidth, int reqHeight, BitmapFactory.Options options) {
330-
calculateInSampleSize(reqWidth, reqHeight, options.outWidth, options.outHeight, options);
331-
}
332-
333-
static void calculateInSampleSize(int reqWidth, int reqHeight, int width, int height,
334-
BitmapFactory.Options options) {
335-
int sampleSize = 1;
336-
if (height > reqHeight || width > reqWidth) {
337-
final int heightRatio = (int) Math.floor((float) height / (float) reqHeight);
338-
final int widthRatio = (int) Math.floor((float) width / (float) reqWidth);
339-
sampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
340-
}
341-
options.inSampleSize = sampleSize;
342-
options.inJustDecodeBounds = false;
292+
throw new IllegalStateException("Unrecognized type of request: " + request);
343293
}
344294

345295
static Bitmap applyCustomTransformations(List<Transformation> transformations, Bitmap result) {

picasso/src/main/java/com/squareup/picasso/ContactsPhotoBitmapHunter.java renamed to picasso/src/main/java/com/squareup/picasso/ContactsPhotoRequestHandler.java

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@
2727
import java.io.IOException;
2828
import java.io.InputStream;
2929

30+
import static android.content.ContentResolver.SCHEME_CONTENT;
3031
import static android.os.Build.VERSION.SDK_INT;
3132
import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
3233
import static android.provider.ContactsContract.Contacts.openContactPhotoInputStream;
3334
import static com.squareup.picasso.Picasso.LoadedFrom.DISK;
3435

35-
class ContactsPhotoBitmapHunter extends BitmapHunter {
36+
class ContactsPhotoRequestHandler extends RequestHandler {
3637
/** A lookup uri (e.g. content://com.android.contacts/contacts/lookup/3570i61d948d30808e537) */
3738
private static final int ID_LOOKUP = 1;
3839
/** A contact thumbnail uri (e.g. content://com.android.contacts/contacts/38/photo) */
@@ -58,29 +59,30 @@ class ContactsPhotoBitmapHunter extends BitmapHunter {
5859

5960
final Context context;
6061

61-
ContactsPhotoBitmapHunter(Context context, Picasso picasso, Dispatcher dispatcher, Cache cache,
62-
Stats stats, Action action) {
63-
super(picasso, dispatcher, cache, stats, action);
62+
ContactsPhotoRequestHandler(Context context) {
6463
this.context = context;
6564
}
6665

67-
@Override Bitmap decode(Request data) throws IOException {
66+
@Override public boolean canHandleRequest(Request data) {
67+
final Uri uri = data.uri;
68+
return (SCHEME_CONTENT.equals(uri.getScheme())
69+
&& ContactsContract.Contacts.CONTENT_URI.getHost().equals(uri.getHost())
70+
&& !uri.getPathSegments().contains(ContactsContract.Contacts.Photo.CONTENT_DIRECTORY));
71+
}
72+
73+
@Override public Result load(Request data) throws IOException {
6874
InputStream is = null;
6975
try {
70-
is = getInputStream();
71-
return decodeStream(is, data);
76+
is = getInputStream(data);
77+
return new Result(decodeStream(is, data), DISK);
7278
} finally {
7379
Utils.closeQuietly(is);
7480
}
7581
}
7682

77-
@Override Picasso.LoadedFrom getLoadedFrom() {
78-
return DISK;
79-
}
80-
81-
private InputStream getInputStream() throws IOException {
83+
private InputStream getInputStream(Request data) throws IOException {
8284
ContentResolver contentResolver = context.getContentResolver();
83-
Uri uri = getData().uri;
85+
Uri uri = data.uri;
8486
switch (matcher.match(uri)) {
8587
case ID_LOOKUP:
8688
uri = ContactsContract.Contacts.lookupContact(contentResolver, uri);
@@ -108,7 +110,7 @@ private Bitmap decodeStream(InputStream stream, Request data) throws IOException
108110
}
109111
final BitmapFactory.Options options = createBitmapOptions(data);
110112
if (requiresInSampleSize(options)) {
111-
InputStream is = getInputStream();
113+
InputStream is = getInputStream(data);
112114
try {
113115
BitmapFactory.decodeStream(is, null, options);
114116
} finally {

picasso/src/main/java/com/squareup/picasso/ContentStreamBitmapHunter.java renamed to picasso/src/main/java/com/squareup/picasso/ContentStreamRequestHandler.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,22 @@
2222
import java.io.IOException;
2323
import java.io.InputStream;
2424

25+
import static android.content.ContentResolver.SCHEME_CONTENT;
2526
import static com.squareup.picasso.Picasso.LoadedFrom.DISK;
2627

27-
class ContentStreamBitmapHunter extends BitmapHunter {
28+
class ContentStreamRequestHandler extends RequestHandler {
2829
final Context context;
2930

30-
ContentStreamBitmapHunter(Context context, Picasso picasso, Dispatcher dispatcher, Cache cache,
31-
Stats stats, Action action) {
32-
super(picasso, dispatcher, cache, stats, action);
31+
ContentStreamRequestHandler(Context context) {
3332
this.context = context;
3433
}
3534

36-
@Override Bitmap decode(Request data)
37-
throws IOException {
38-
return decodeContentStream(data);
35+
@Override public boolean canHandleRequest(Request data) {
36+
return SCHEME_CONTENT.equals(data.uri.getScheme());
3937
}
4038

41-
@Override Picasso.LoadedFrom getLoadedFrom() {
42-
return DISK;
39+
@Override public Result load(Request data) throws IOException {
40+
return new Result(decodeContentStream(data), DISK);
4341
}
4442

4543
protected Bitmap decodeContentStream(Request data) throws IOException {

picasso/src/main/java/com/squareup/picasso/Dispatcher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ void performSubmit(Action action) {
156156
return;
157157
}
158158

159-
hunter = forRequest(context, action.getPicasso(), this, cache, stats, action, downloader);
159+
hunter = forRequest(action.getPicasso(), this, cache, stats, action);
160160
hunter.future = service.submit(hunter);
161161
hunterMap.put(action.getKey(), hunter);
162162
failedActions.remove(action.getTarget());

0 commit comments

Comments
 (0)