diff --git a/Libraries/Image/Image.android.js b/Libraries/Image/Image.android.js index dfcfe7317e3..cafd1f22a50 100644 --- a/Libraries/Image/Image.android.js +++ b/Libraries/Image/Image.android.js @@ -34,6 +34,11 @@ var { ImageLoader, } = NativeModules; +let _requestId = 1; +function generateRequestId() { + return _requestId++; +} + /** * - A react component for displaying different types of images, * including network images, static resources, temporary local images, and @@ -148,8 +153,17 @@ var Image = React.createClass({ * Prefetches a remote image for later use by downloading it to the disk * cache */ - prefetch(url: string) { - return ImageLoader.prefetchImage(url); + prefetch(url: string, callback: ?Function) { + const requestId = generateRequestId(); + callback && callback(requestId); + return ImageLoader.prefetchImage(url, requestId); + }, + + /** + * Abort prefetch request + */ + abortPrefetch(requestId: number) { + ImageLoader.abortRequest(requestId); }, }, diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.java index 34648f1f164..54730146d2d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.java @@ -9,7 +9,10 @@ package com.facebook.react.modules.image; +import javax.annotation.Nullable; + import android.net.Uri; +import android.util.SparseArray; import com.facebook.common.executors.CallerThreadExecutor; import com.facebook.common.references.CloseableReference; @@ -21,19 +24,23 @@ import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.WritableMap; -public class ImageLoaderModule extends ReactContextBaseJavaModule { +public class ImageLoaderModule extends ReactContextBaseJavaModule implements + LifecycleEventListener { private static final String ERROR_INVALID_URI = "E_INVALID_URI"; private static final String ERROR_PREFETCH_FAILURE = "E_PREFETCH_FAILURE"; private static final String ERROR_GET_SIZE_FAILURE = "E_GET_SIZE_FAILURE"; private final Object mCallerContext; + private final Object mEnqueuedRequestMonitor = new Object(); + private final SparseArray> mEnqueuedRequests = new SparseArray<>(); public ImageLoaderModule(ReactApplicationContext reactContext) { super(reactContext); @@ -50,9 +57,16 @@ public class ImageLoaderModule extends ReactContextBaseJavaModule { return "ImageLoader"; } + /** + * Fetch the width and height of the given image. + * + * @param uriString the URI of the remote image to prefetch + * @param promise the promise that is fulfilled when the image is successfully prefetched + * or rejected when there is an error + */ @ReactMethod public void getSize( - String uriString, + final String uriString, final Promise promise) { if (uriString == null || uriString.isEmpty()) { promise.reject(ERROR_INVALID_URI, "Cannot get the size of an image for an empty URI"); @@ -105,11 +119,16 @@ public class ImageLoaderModule extends ReactContextBaseJavaModule { * Prefetches the given image to the Fresco image disk cache. * * @param uriString the URI of the remote image to prefetch + * @param requestId the client-supplied request ID used to identify this request * @param promise the promise that is fulfilled when the image is successfully prefetched * or rejected when there is an error */ @ReactMethod - public void prefetchImage(String uriString, final Promise promise) { + public void prefetchImage( + final String uriString, + final int requestId, + final Promise promise) + { if (uriString == null || uriString.isEmpty()) { promise.reject(ERROR_INVALID_URI, "Cannot prefetch an image for an empty URI"); return; @@ -127,6 +146,7 @@ public class ImageLoaderModule extends ReactContextBaseJavaModule { return; } try { + removeRequest(requestId); promise.resolve(true); } finally { dataSource.close(); @@ -136,12 +156,58 @@ public class ImageLoaderModule extends ReactContextBaseJavaModule { @Override protected void onFailureImpl(DataSource dataSource) { try { + removeRequest(requestId); promise.reject(ERROR_PREFETCH_FAILURE, dataSource.getFailureCause()); } finally { dataSource.close(); } } }; + registerRequest(requestId, prefetchSource); prefetchSource.subscribe(prefetchSubscriber, CallerThreadExecutor.getInstance()); } + + @ReactMethod + public void abortRequest(final int requestId) { + DataSource request = removeRequest(requestId); + if (request != null) { + request.close(); + } + } + + private void registerRequest(int requestId, DataSource request) { + synchronized (mEnqueuedRequestMonitor) { + mEnqueuedRequests.put(requestId, request); + } + } + + private @Nullable DataSource removeRequest(int requestId) { + synchronized (mEnqueuedRequestMonitor) { + DataSource request = mEnqueuedRequests.get(requestId); + mEnqueuedRequests.remove(requestId); + return request; + } + } + + @Override + public void onHostResume() { + } + + @Override + public void onHostPause() { + } + + @Override + public void onHostDestroy() { + // cancel all requests + synchronized (mEnqueuedRequestMonitor) { + for (int i = 0, size = mEnqueuedRequests.size(); i < size; i++) { + @Nullable DataSource enqueuedRequest = mEnqueuedRequests.valueAt(i); + if (enqueuedRequest != null) { + enqueuedRequest.close(); + } + } + mEnqueuedRequests.clear(); + } + } }