Skip to content

Commit

Permalink
Plumb Okio stream types into request handlers.
Browse files Browse the repository at this point in the history
This simplifies a ton of byte-management operations like checking for webp or even converting to a byte[].
  • Loading branch information
JakeWharton committed Feb 24, 2017
1 parent ee87368 commit 202ac6b
Show file tree
Hide file tree
Showing 13 changed files with 85 additions and 76 deletions.
2 changes: 1 addition & 1 deletion dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ ext {
targetSdkVersion = 25
sourceCompatibilityVersion = JavaVersion.VERSION_1_7
targetCompatibilityVersion = JavaVersion.VERSION_1_7
okhttpVersion = '3.0.1'
okhttpVersion = '3.6.0'
supportLibrariesVersion = '25.1.0'

dep = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import android.content.res.AssetManager;
import android.net.Uri;
import java.io.IOException;
import java.io.InputStream;
import okio.Okio;
import okio.Source;

import static android.content.ContentResolver.SCHEME_FILE;
import static com.squareup.picasso.Picasso.LoadedFrom.DISK;
Expand Down Expand Up @@ -51,8 +52,8 @@ public AssetRequestHandler(Context context) {
}
}
}
InputStream is = assetManager.open(getFilePath(request));
return new Result(is, DISK);
Source source = Okio.source(assetManager.open(getFilePath(request)));
return new Result(source, DISK);
}

static String getFilePath(Request request) {
Expand Down
39 changes: 24 additions & 15 deletions picasso/src/main/java/com/squareup/picasso/BitmapHunter.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import okio.BufferedSource;
import okio.Okio;
import okio.Source;

import static android.media.ExifInterface.ORIENTATION_FLIP_HORIZONTAL;
import static android.media.ExifInterface.ORIENTATION_FLIP_VERTICAL;
Expand Down Expand Up @@ -119,37 +122,39 @@ class BitmapHunter implements Runnable {
* about the supplied request in order to do the decoding efficiently (such as through leveraging
* {@code inSampleSize}).
*/
static Bitmap decodeStream(InputStream stream, Request request) throws IOException {
MarkableInputStream markStream = new MarkableInputStream(stream);
stream = markStream;
markStream.allowMarksToExpire(false);
long mark = markStream.savePosition(1024);
static Bitmap decodeStream(Source source, Request request) throws IOException {
BufferedSource bufferedSource = Okio.buffer(source);

final BitmapFactory.Options options = RequestHandler.createBitmapOptions(request);
final boolean calculateSize = RequestHandler.requiresInSampleSize(options);

boolean isWebPFile = Utils.isWebPFile(stream);
boolean isWebPFile = Utils.isWebPFile(bufferedSource);
boolean isPurgeable = request.purgeable && Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP;
markStream.reset(mark);
BitmapFactory.Options options = RequestHandler.createBitmapOptions(request);
boolean calculateSize = RequestHandler.requiresInSampleSize(options);

// We decode from a byte array because, a) when decoding a WebP network stream, BitmapFactory
// throws a JNI Exception, so we workaround by decoding a byte array, or b) user requested
// purgeable, which only affects bitmaps decoded from byte arrays.
if (isWebPFile || isPurgeable) {
byte[] bytes = Utils.toByteArray(stream);
byte[] bytes = bufferedSource.readByteArray();
if (calculateSize) {
BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,
request);
}
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
} else {
InputStream stream = bufferedSource.inputStream();
if (calculateSize) {
// TODO use an InputStream that buffers with Okio...
MarkableInputStream markStream = new MarkableInputStream(stream);
stream = markStream;
markStream.allowMarksToExpire(false);
long mark = markStream.savePosition(1024);
BitmapFactory.decodeStream(stream, null, options);
RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options,
request);
markStream.reset(mark);
markStream.allowMarksToExpire(true);
}
markStream.allowMarksToExpire(true);
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
if (bitmap == null) {
// Treat null as an IO exception, we will eventually retry.
Expand Down Expand Up @@ -219,11 +224,15 @@ Bitmap hunt() throws IOException {

// If there was no Bitmap then we need to decode it from the stream.
if (bitmap == null) {
InputStream is = result.getStream();
Source source = result.getSource();
try {
bitmap = decodeStream(is, data);
bitmap = decodeStream(source, data);
} finally {
Utils.closeQuietly(is);
try {
//noinspection ConstantConditions If bitmap is null then source is guranteed non-null.
source.close();
} catch (IOException ignored) {
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import android.provider.ContactsContract;
import java.io.IOException;
import java.io.InputStream;
import okio.Okio;

import static android.content.ContentResolver.SCHEME_CONTENT;
import static android.provider.ContactsContract.Contacts.openContactPhotoInputStream;
Expand Down Expand Up @@ -66,7 +67,10 @@ class ContactsPhotoRequestHandler extends RequestHandler {

@Override public Result load(Request request, int networkPolicy) throws IOException {
InputStream is = getInputStream(request);
return is != null ? new Result(is, DISK) : null;
if (is == null) {
return null;
}
return new Result(Okio.source(is), DISK);
}

private InputStream getInputStream(Request data) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import okio.Okio;
import okio.Source;

import static android.content.ContentResolver.SCHEME_CONTENT;
import static com.squareup.picasso.Picasso.LoadedFrom.DISK;
Expand All @@ -36,7 +38,8 @@ class ContentStreamRequestHandler extends RequestHandler {
}

@Override public Result load(Request request, int networkPolicy) throws IOException {
return new Result(getInputStream(request), DISK);
Source source = Okio.source(getInputStream(request));
return new Result(source, DISK);
}

InputStream getInputStream(Request request) throws FileNotFoundException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import android.media.ExifInterface;
import android.net.Uri;
import java.io.IOException;
import okio.Okio;
import okio.Source;

import static android.content.ContentResolver.SCHEME_FILE;
import static android.media.ExifInterface.ORIENTATION_NORMAL;
Expand All @@ -36,7 +38,8 @@ class FileRequestHandler extends ContentStreamRequestHandler {
}

@Override public Result load(Request request, int networkPolicy) throws IOException {
return new Result(null, getInputStream(request), DISK, getFileExifRotation(request.uri));
Source source = Okio.source(getInputStream(request));
return new Result(null, source, DISK, getFileExifRotation(request.uri));
}

static int getFileExifRotation(Uri uri) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import android.net.Uri;
import android.provider.MediaStore;
import java.io.IOException;
import okio.Okio;
import okio.Source;

import static android.content.ContentResolver.SCHEME_CONTENT;
import static android.content.ContentUris.parseId;
Expand Down Expand Up @@ -61,7 +63,8 @@ class MediaStoreRequestHandler extends ContentStreamRequestHandler {
if (request.hasSize()) {
PicassoKind picassoKind = getPicassoKind(request.targetWidth, request.targetHeight);
if (!isVideo && picassoKind == FULL) {
return new Result(null, getInputStream(request), DISK, exifOrientation);
Source source = Okio.source(getInputStream(request));
return new Result(null, source, DISK, exifOrientation);
}

long id = parseId(request.uri);
Expand Down Expand Up @@ -89,7 +92,8 @@ class MediaStoreRequestHandler extends ContentStreamRequestHandler {
}
}

return new Result(null, getInputStream(request), DISK, exifOrientation);
Source source = Okio.source(getInputStream(request));
return new Result(null, source, DISK, exifOrientation);
}

static PicassoKind getPicassoKind(int targetWidth, int targetHeight) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import android.net.NetworkInfo;
import java.io.IOException;
import java.io.InputStream;
import okhttp3.CacheControl;
import okhttp3.Response;
import okhttp3.ResponseBody;
Expand Down Expand Up @@ -65,8 +64,7 @@ public NetworkRequestHandler(Downloader downloader, Stats stats) {
if (loadedFrom == NETWORK && body.contentLength() > 0) {
stats.dispatchDownloadFinished(body.contentLength());
}
InputStream is = body.byteStream();
return new Result(is, loadedFrom);
return new Result(body.source(), loadedFrom);
}

@Override int getRetryCount() {
Expand Down
21 changes: 10 additions & 11 deletions picasso/src/main/java/com/squareup/picasso/RequestHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
import android.net.NetworkInfo;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

import java.io.IOException;
import java.io.InputStream;
import okio.Source;

import static com.squareup.picasso.Utils.checkNotNull;

Expand Down Expand Up @@ -54,39 +53,39 @@ public abstract class RequestHandler {
public static final class Result {
private final Picasso.LoadedFrom loadedFrom;
private final Bitmap bitmap;
private final InputStream stream;
private final Source source;
private final int exifOrientation;

public Result(@NonNull Bitmap bitmap, @NonNull Picasso.LoadedFrom loadedFrom) {
this(checkNotNull(bitmap, "bitmap == null"), null, loadedFrom, 0);
}

public Result(@NonNull InputStream stream, @NonNull Picasso.LoadedFrom loadedFrom) {
this(null, checkNotNull(stream, "stream == null"), loadedFrom, 0);
public Result(@NonNull Source source, @NonNull Picasso.LoadedFrom loadedFrom) {
this(null, checkNotNull(source, "source == null"), loadedFrom, 0);
}

Result(
@Nullable Bitmap bitmap,
@Nullable InputStream stream,
@Nullable Source source,
@NonNull Picasso.LoadedFrom loadedFrom,
int exifOrientation) {
if ((bitmap != null) == (stream != null)) {
if ((bitmap != null) == (source != null)) {
throw new AssertionError();
}
this.bitmap = bitmap;
this.stream = stream;
this.source = source;
this.loadedFrom = checkNotNull(loadedFrom, "loadedFrom == null");
this.exifOrientation = exifOrientation;
}

/** The loaded {@link Bitmap}. Mutually exclusive with {@link #getStream()}. */
/** The loaded {@link Bitmap}. Mutually exclusive with {@link #getSource()}. */
@Nullable public Bitmap getBitmap() {
return bitmap;
}

/** A stream of image data. Mutually exclusive with {@link #getBitmap()}. */
@Nullable public InputStream getStream() {
return stream;
@Nullable public Source getSource() {
return source;
}

/**
Expand Down
43 changes: 13 additions & 30 deletions picasso/src/main/java/com/squareup/picasso/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@
import android.os.StatFs;
import android.provider.Settings;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import okio.Buffer;
import okio.BufferedSource;
import okio.ByteString;

import static android.content.Context.ACTIVITY_SERVICE;
import static android.content.pm.ApplicationInfo.FLAG_LARGE_HEAP;
Expand Down Expand Up @@ -95,8 +96,8 @@ final class Utils {
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
private static final int WEBP_FILE_HEADER_SIZE = 12;
private static final String WEBP_FILE_HEADER_RIFF = "RIFF";
private static final String WEBP_FILE_HEADER_WEBP = "WEBP";
private static final ByteString WEBP_FILE_HEADER_RIFF = ByteString.encodeUtf8("RIFF");
private static final ByteString WEBP_FILE_HEADER_WEBP = ByteString.encodeUtf8("WEBP");

private Utils() {
// No instances.
Expand Down Expand Up @@ -209,14 +210,6 @@ static String createKey(Request data, StringBuilder builder) {
return builder.toString();
}

static void closeQuietly(InputStream is) {
if (is == null) return;
try {
is.close();
} catch (IOException ignored) {
}
}

static File createDefaultCacheDir(Context context) {
File cache = new File(context.getApplicationContext().getCacheDir(), PICASSO_CACHE);
if (!cache.exists()) {
Expand Down Expand Up @@ -283,25 +276,15 @@ static boolean hasPermission(Context context, String permission) {
return context.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
}

static byte[] toByteArray(InputStream input) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024 * 4];
int n;
while (-1 != (n = input.read(buffer))) {
byteArrayOutputStream.write(buffer, 0, n);
}
return byteArrayOutputStream.toByteArray();
}

static boolean isWebPFile(InputStream stream) throws IOException {
byte[] fileHeaderBytes = new byte[WEBP_FILE_HEADER_SIZE];
boolean isWebPFile = false;
if (stream.read(fileHeaderBytes, 0, WEBP_FILE_HEADER_SIZE) == WEBP_FILE_HEADER_SIZE) {
// If a file's header starts with RIFF and end with WEBP, the file is a WebP file
isWebPFile = WEBP_FILE_HEADER_RIFF.equals(new String(fileHeaderBytes, 0, 4, "US-ASCII"))
&& WEBP_FILE_HEADER_WEBP.equals(new String(fileHeaderBytes, 8, 4, "US-ASCII"));
static boolean isWebPFile(BufferedSource source) throws IOException {
if (source.request(WEBP_FILE_HEADER_SIZE)) {
Buffer buffer = source.buffer();
if (buffer.rangeEquals(0, WEBP_FILE_HEADER_RIFF)
&& buffer.rangeEquals(8, WEBP_FILE_HEADER_WEBP)) {
return true;
}
}
return isWebPFile;
return false;
}

static int getResourceId(Resources resources, Request data) throws FileNotFoundException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@
import static org.fest.assertions.api.Assertions.assertThat;

@RunWith(RobolectricGradleTestRunner.class)
@Config(shadows = { Shadows.ShadowNetwork.class })
@Config(
sdk = 23, // Works around https://github.com/robolectric/robolectric/issues/2566.
shadows = { Shadows.ShadowNetwork.class }
)
public class OkHttp3DownloaderTest {
@Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule public MockWebServer server = new MockWebServer();
Expand Down
3 changes: 3 additions & 0 deletions picasso/src/test/java/com/squareup/picasso/PicassoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.mockito.Mock;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

import static android.graphics.Bitmap.Config.ARGB_8888;
import static com.squareup.picasso.Picasso.Listener;
Expand Down Expand Up @@ -57,6 +58,7 @@
import static org.mockito.MockitoAnnotations.initMocks;

@RunWith(RobolectricGradleTestRunner.class)
@Config(sdk = 23) // Works around https://github.com/robolectric/robolectric/issues/2566.
public class PicassoTest {

@Mock Context context;
Expand Down Expand Up @@ -280,6 +282,7 @@ public class PicassoTest {
}
}

@Config(sdk = 16) // This test fails on 23 so restore the default level.
@Test public void cancelExistingRequestWithRemoteViewTarget() {
int layoutId = 0;
int viewId = 1;
Expand Down
Loading

0 comments on commit 202ac6b

Please sign in to comment.