com.facebook.react.modules.image.ImageLoaderModule Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of react-native Show documentation
Show all versions of react-native Show documentation
A framework for building native apps with React
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
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;
import com.facebook.datasource.BaseDataSubscriber;
import com.facebook.datasource.DataSource;
import com.facebook.datasource.DataSubscriber;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline;
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.GuardedAsyncTask;
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.ReadableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = "ImageLoader")
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);
mCallerContext = this;
}
public ImageLoaderModule(ReactApplicationContext reactContext, Object callerContext) {
super(reactContext);
mCallerContext = callerContext;
}
@Override
public String getName() {
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(
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");
return;
}
Uri uri = Uri.parse(uriString);
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).build();
DataSource> dataSource =
Fresco.getImagePipeline().fetchDecodedImage(request, mCallerContext);
DataSubscriber> dataSubscriber =
new BaseDataSubscriber>() {
@Override
protected void onNewResultImpl(
DataSource> dataSource) {
if (!dataSource.isFinished()) {
return;
}
CloseableReference ref = dataSource.getResult();
if (ref != null) {
try {
CloseableImage image = ref.get();
WritableMap sizes = Arguments.createMap();
sizes.putInt("width", image.getWidth());
sizes.putInt("height", image.getHeight());
promise.resolve(sizes);
} catch (Exception e) {
promise.reject(ERROR_GET_SIZE_FAILURE, e);
} finally {
CloseableReference.closeSafely(ref);
}
} else {
promise.reject(ERROR_GET_SIZE_FAILURE);
}
}
@Override
protected void onFailureImpl(DataSource> dataSource) {
promise.reject(ERROR_GET_SIZE_FAILURE, dataSource.getFailureCause());
}
};
dataSource.subscribe(dataSubscriber, CallerThreadExecutor.getInstance());
}
/**
* 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(
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;
}
Uri uri = Uri.parse(uriString);
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).build();
DataSource prefetchSource =
Fresco.getImagePipeline().prefetchToDiskCache(request, mCallerContext);
DataSubscriber prefetchSubscriber = new BaseDataSubscriber() {
@Override
protected void onNewResultImpl(DataSource dataSource) {
if (!dataSource.isFinished()) {
return;
}
try {
removeRequest(requestId);
promise.resolve(true);
} finally {
dataSource.close();
}
}
@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();
}
}
@ReactMethod
public void queryCache(final ReadableArray uris, final Promise promise) {
// perform cache interrogation in async task as disk cache checks are expensive
new GuardedAsyncTask(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
WritableMap result = Arguments.createMap();
ImagePipeline imagePipeline = Fresco.getImagePipeline();
for (int i = 0; i < uris.size(); i++) {
String uriString = uris.getString(i);
final Uri uri = Uri.parse(uriString);
if (imagePipeline.isInBitmapMemoryCache(uri)) {
result.putString(uriString, "memory");
} else if (imagePipeline.isInDiskCacheSync(uri)) {
result.putString(uriString, "disk");
}
}
promise.resolve(result);
}
}.executeOnExecutor(GuardedAsyncTask.THREAD_POOL_EXECUTOR);
}
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();
}
}
}