mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-07 17:37:34 +08:00
Use fresco cache to show low res image if available
Summary: ImageView will now interrogate fresco cache for images that can be shown before the one with the best fitting size is downloaded. Cache interrogation does not take into account that the images from cache are smaller or bigger than the best fit. Most of the cases, the smaller one will be displayed. It is also possible that a bigger image is available for being displayed, but ideally we'd still want the best fit to be shown, so as to not decode and resize images that are too big. I've added a ImageSource class to simplify things. This makes it easier to lazy-parse the Uri's when necessary, and cache data related to that uri and wether the image is local. This also gets rid of the Map, which makes parsing the source a bit more elegant. Reviewed By: foghina Differential Revision: D3392751 fbshipit-source-id: f6b803fb7ae2aa1c787aa51f66297a14903e4040
This commit is contained in:
committed by
Facebook Github Bot 1
parent
675c55e8ad
commit
477cc52945
@@ -15,6 +15,7 @@ android_library(
|
||||
react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'),
|
||||
react_native_dep('third-party/android/support/v4:lib-support-v4'),
|
||||
react_native_dep('third-party/android/support-annotations:android-support-annotations'),
|
||||
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
],
|
||||
visibility = [
|
||||
|
||||
@@ -12,8 +12,8 @@ package com.facebook.react.views.image;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
@@ -44,11 +44,14 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
import com.facebook.drawee.generic.RoundingParams;
|
||||
import com.facebook.drawee.view.GenericDraweeView;
|
||||
import com.facebook.imagepipeline.common.ResizeOptions;
|
||||
import com.facebook.imagepipeline.core.ImagePipeline;
|
||||
import com.facebook.imagepipeline.core.ImagePipelineFactory;
|
||||
import com.facebook.imagepipeline.image.ImageInfo;
|
||||
import com.facebook.imagepipeline.request.BasePostprocessor;
|
||||
import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
import com.facebook.imagepipeline.request.Postprocessor;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
@@ -139,10 +142,67 @@ public class ReactImageView extends GenericDraweeView {
|
||||
}
|
||||
}
|
||||
|
||||
private final ResourceDrawableIdHelper mResourceDrawableIdHelper;
|
||||
private final Map<String, Double> mSources;
|
||||
private class ImageSource {
|
||||
private @Nullable Uri mUri;
|
||||
private String mSource;
|
||||
private double mSize;
|
||||
private boolean mIsLocalImage;
|
||||
|
||||
private @Nullable Uri mUri;
|
||||
public ImageSource(String source, double width, double height) {
|
||||
mSource = source;
|
||||
mSize = width * height;
|
||||
}
|
||||
|
||||
public ImageSource(String source) {
|
||||
this(source, 0.0d, 0.0d);
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return mSource;
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
if (mUri == null) {
|
||||
computeUri();
|
||||
}
|
||||
return Assertions.assertNotNull(mUri);
|
||||
}
|
||||
|
||||
public double getSize() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
public boolean isLocalImage() {
|
||||
if (mUri == null) {
|
||||
computeUri();
|
||||
}
|
||||
return mIsLocalImage;
|
||||
}
|
||||
|
||||
private void computeUri() {
|
||||
try {
|
||||
mUri = Uri.parse(mSource);
|
||||
// Verify scheme is set, so that relative uri (used by static resources) are not handled.
|
||||
if (mUri.getScheme() == null) {
|
||||
mUri = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore malformed uri, then attempt to extract resource ID.
|
||||
}
|
||||
if (mUri == null) {
|
||||
mUri = mResourceDrawableIdHelper.getResourceDrawableUri(getContext(), mSource);
|
||||
mIsLocalImage = true;
|
||||
} else {
|
||||
mIsLocalImage = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final ResourceDrawableIdHelper mResourceDrawableIdHelper;
|
||||
private final List<ImageSource> mSources;
|
||||
|
||||
private @Nullable ImageSource mImageSource;
|
||||
private @Nullable ImageSource mCachedImageSource;
|
||||
private @Nullable Drawable mLoadingImageDrawable;
|
||||
private int mBorderColor;
|
||||
private int mOverlayColor;
|
||||
@@ -151,7 +211,6 @@ public class ReactImageView extends GenericDraweeView {
|
||||
private @Nullable float[] mBorderCornerRadii;
|
||||
private ScalingUtils.ScaleType mScaleType;
|
||||
private boolean mIsDirty;
|
||||
private boolean mIsLocalImage;
|
||||
private final AbstractDraweeControllerBuilder mDraweeControllerBuilder;
|
||||
private final RoundedCornerPostprocessor mRoundedCornerPostprocessor;
|
||||
private @Nullable ControllerListener mControllerListener;
|
||||
@@ -178,7 +237,7 @@ public class ReactImageView extends GenericDraweeView {
|
||||
mRoundedCornerPostprocessor = new RoundedCornerPostprocessor();
|
||||
mCallerContext = callerContext;
|
||||
mResourceDrawableIdHelper = resourceDrawableIdHelper;
|
||||
mSources = new HashMap<>();
|
||||
mSources = new LinkedList<>();
|
||||
}
|
||||
|
||||
public void setShouldNotifyLoadEvents(boolean shouldNotify) {
|
||||
@@ -270,13 +329,14 @@ public class ReactImageView extends GenericDraweeView {
|
||||
if (sources != null && sources.size() != 0) {
|
||||
// Optimize for the case where we have just one uri, case in which we don't need the sizes
|
||||
if (sources.size() == 1) {
|
||||
mSources.put(sources.getMap(0).getString("uri"), 0.0);
|
||||
mSources.add(new ImageSource(sources.getMap(0).getString("uri")));
|
||||
} else {
|
||||
for (int idx = 0; idx < sources.size(); idx++) {
|
||||
ReadableMap source = sources.getMap(idx);
|
||||
mSources.put(
|
||||
mSources.add(new ImageSource(
|
||||
source.getString("uri"),
|
||||
source.getDouble("width") * source.getDouble("height"));
|
||||
source.getDouble("width"),
|
||||
source.getDouble("height")));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -319,12 +379,12 @@ public class ReactImageView extends GenericDraweeView {
|
||||
return;
|
||||
}
|
||||
|
||||
computeSourceUri();
|
||||
if (mUri == null) {
|
||||
setSourceImage();
|
||||
if (mImageSource == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean doResize = shouldResize(mUri);
|
||||
boolean doResize = shouldResize(mImageSource);
|
||||
if (doResize && (getWidth() <= 0 || getHeight() <= 0)) {
|
||||
// If need a resize and the size is not yet set, wait until the layout pass provides one
|
||||
return;
|
||||
@@ -362,13 +422,13 @@ public class ReactImageView extends GenericDraweeView {
|
||||
hierarchy.setFadeDuration(
|
||||
mFadeDurationMs >= 0
|
||||
? mFadeDurationMs
|
||||
: mIsLocalImage ? 0 : REMOTE_IMAGE_FADE_DURATION_MS);
|
||||
: mImageSource.isLocalImage() ? 0 : REMOTE_IMAGE_FADE_DURATION_MS);
|
||||
|
||||
Postprocessor postprocessor = usePostprocessorScaling ? mRoundedCornerPostprocessor : null;
|
||||
|
||||
ResizeOptions resizeOptions = doResize ? new ResizeOptions(getWidth(), getHeight()) : null;
|
||||
|
||||
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(mUri)
|
||||
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(mImageSource.getUri())
|
||||
.setPostprocessor(postprocessor)
|
||||
.setResizeOptions(resizeOptions)
|
||||
.setAutoRotateEnabled(true)
|
||||
@@ -384,6 +444,17 @@ public class ReactImageView extends GenericDraweeView {
|
||||
.setOldController(getController())
|
||||
.setImageRequest(imageRequest);
|
||||
|
||||
if (mCachedImageSource != null) {
|
||||
ImageRequest cachedImageRequest =
|
||||
ImageRequestBuilder.newBuilderWithSource(mCachedImageSource.getUri())
|
||||
.setPostprocessor(postprocessor)
|
||||
.setResizeOptions(resizeOptions)
|
||||
.setAutoRotateEnabled(true)
|
||||
.setProgressiveRenderingEnabled(mProgressiveRenderingEnabled)
|
||||
.build();
|
||||
mDraweeControllerBuilder.setLowResImageRequest(cachedImageRequest);
|
||||
}
|
||||
|
||||
if (mControllerListener != null && mControllerForTesting != null) {
|
||||
ForwardingControllerListener combinedListener = new ForwardingControllerListener();
|
||||
combinedListener.addListener(mControllerListener);
|
||||
@@ -427,61 +498,57 @@ public class ReactImageView extends GenericDraweeView {
|
||||
return mSources.size() > 1;
|
||||
}
|
||||
|
||||
private void computeSourceUri() {
|
||||
mUri = null;
|
||||
private void setSourceImage() {
|
||||
mImageSource = null;
|
||||
if (mSources.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (hasMultipleSources()) {
|
||||
setUriFromMultipleSources();
|
||||
setImageSourceFromMultipleSources();
|
||||
return;
|
||||
}
|
||||
|
||||
final String singleSource = mSources.keySet().iterator().next();
|
||||
setUriFromSingleSource(singleSource);
|
||||
}
|
||||
|
||||
private void setUriFromSingleSource(String source) {
|
||||
try {
|
||||
mUri = Uri.parse(source);
|
||||
// Verify scheme is set, so that relative uri (used by static resources) are not handled.
|
||||
if (mUri.getScheme() == null) {
|
||||
mUri = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore malformed uri, then attempt to extract resource ID.
|
||||
}
|
||||
if (mUri == null) {
|
||||
mUri = mResourceDrawableIdHelper.getResourceDrawableUri(getContext(), source);
|
||||
mIsLocalImage = true;
|
||||
} else {
|
||||
mIsLocalImage = false;
|
||||
}
|
||||
mImageSource = mSources.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Chooses the uri with the size closest to the target image size. Must be called only after the
|
||||
* layout pass when the sizes of the target image have been computed, and when there are at least
|
||||
* two sources to choose from.
|
||||
* Chooses the image source with the size closest to the target image size. Must be called only
|
||||
* after the layout pass when the sizes of the target image have been computed, and when there
|
||||
* are at least two sources to choose from.
|
||||
*/
|
||||
private void setUriFromMultipleSources() {
|
||||
private void setImageSourceFromMultipleSources() {
|
||||
ImagePipeline imagePipeline = ImagePipelineFactory.getInstance().getImagePipeline();
|
||||
final double targetImageSize = getWidth() * getHeight();
|
||||
double bestPrecision = Double.MAX_VALUE;
|
||||
String bestUri = null;
|
||||
for (Map.Entry<String, Double> source : mSources.entrySet()) {
|
||||
final double precision = Math.abs(1.0 - (source.getValue()) / targetImageSize);
|
||||
double bestCachePrecision = Double.MAX_VALUE;
|
||||
for (ImageSource source : mSources) {
|
||||
final double precision = Math.abs(1.0 - (source.getSize()) / targetImageSize);
|
||||
if (precision < bestPrecision) {
|
||||
bestPrecision = precision;
|
||||
bestUri = source.getKey();
|
||||
mImageSource = source;
|
||||
}
|
||||
|
||||
if (precision < bestCachePrecision &&
|
||||
(imagePipeline.isInBitmapMemoryCache(source.getUri()) ||
|
||||
imagePipeline.isInDiskCacheSync(source.getUri()))) {
|
||||
bestCachePrecision = precision;
|
||||
mCachedImageSource = source;
|
||||
}
|
||||
}
|
||||
setUriFromSingleSource(bestUri);
|
||||
|
||||
// don't use cached image source if it's the same as the image source
|
||||
if (mCachedImageSource != null &&
|
||||
mImageSource.getSource().equals(mCachedImageSource.getSource())) {
|
||||
mCachedImageSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldResize(Uri uri) {
|
||||
private static boolean shouldResize(ImageSource imageSource) {
|
||||
// Resizing is inferior to scaling. See http://frescolib.org/docs/resizing-rotating.html#_
|
||||
// We resize here only for images likely to be from the device's camera, where the app developer
|
||||
// has no control over the original size
|
||||
return UriUtil.isLocalContentUri(uri) || UriUtil.isLocalFileUri(uri);
|
||||
return
|
||||
UriUtil.isLocalContentUri(imageSource.getUri()) ||
|
||||
UriUtil.isLocalFileUri(imageSource.getUri());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user