Add a way to prefetch remote images to cache with Image.prefetch
Summary:Adds `Image.prefetch` to prefetch remote images before they are used in an actual `Image` component. This is based off of #4420 by sospartan and skevy's work. Closes https://github.com/facebook/react-native/pull/6774 Differential Revision: D3153729 Pulled By: bestander fb-gh-sync-id: ef61412e051a49b42ae885edce7905a8ca0da23f fbshipit-source-id: ef61412e051a49b42ae885edce7905a8ca0da23f
This commit is contained in:
parent
4450d789e0
commit
f7bcb3e98d
|
@ -36,11 +36,14 @@ var {
|
|||
var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
|
||||
|
||||
var ImageCapInsetsExample = require('./ImageCapInsetsExample');
|
||||
const IMAGE_PREFETCH_URL = 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now();
|
||||
var prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
|
||||
|
||||
var NetworkImageCallbackExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
events: [],
|
||||
startLoadPrefetched: false,
|
||||
mountTime: new Date(),
|
||||
};
|
||||
},
|
||||
|
@ -59,9 +62,26 @@ var NetworkImageCallbackExample = React.createClass({
|
|||
style={[styles.base, {overflow: 'visible'}]}
|
||||
onLoadStart={() => this._loadEventFired(`✔ onLoadStart (+${new Date() - mountTime}ms)`)}
|
||||
onLoad={() => this._loadEventFired(`✔ onLoad (+${new Date() - mountTime}ms)`)}
|
||||
onLoadEnd={() => this._loadEventFired(`✔ onLoadEnd (+${new Date() - mountTime}ms)`)}
|
||||
onLoadEnd={() => {
|
||||
this._loadEventFired(`✔ onLoadEnd (+${new Date() - mountTime}ms)`);
|
||||
this.setState({startLoadPrefetched: true}, () => {
|
||||
prefetchTask.then(() => {
|
||||
this._loadEventFired(`✔ Prefetch OK (+${new Date() - mountTime}ms)`);
|
||||
}, error => {
|
||||
this._loadEventFired(`✘ Prefetch failed (+${new Date() - mountTime}ms)`);
|
||||
});
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{this.state.startLoadPrefetched ?
|
||||
<Image
|
||||
source={this.props.prefetchedSource}
|
||||
style={[styles.base, {overflow: 'visible'}]}
|
||||
onLoadStart={() => this._loadEventFired(`✔ (prefetched) onLoadStart (+${new Date() - mountTime}ms)`)}
|
||||
onLoad={() => this._loadEventFired(`✔ (prefetched) onLoad (+${new Date() - mountTime}ms)`)}
|
||||
onLoadEnd={() => this._loadEventFired(`✔ (prefetched) onLoadEnd (+${new Date() - mountTime}ms)`)}
|
||||
/>
|
||||
: null}
|
||||
<Text style={{marginTop: 20}}>
|
||||
{this.state.events.join('\n')}
|
||||
</Text>
|
||||
|
@ -174,7 +194,8 @@ exports.examples = [
|
|||
title: 'Image Loading Events',
|
||||
render: function() {
|
||||
return (
|
||||
<NetworkImageCallbackExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1'}}/>
|
||||
<NetworkImageCallbackExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now()}}
|
||||
prefetchedSource={{uri: IMAGE_PREFETCH_URL}}/>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
|
|
@ -23,11 +23,14 @@ var StyleSheetPropType = require('StyleSheetPropType');
|
|||
var View = require('View');
|
||||
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var merge = require('merge');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var resolveAssetSource = require('resolveAssetSource');
|
||||
|
||||
var {
|
||||
ImageLoader,
|
||||
} = NativeModules;
|
||||
|
||||
/**
|
||||
* <Image> - A react component for displaying different types of images,
|
||||
* including network images, static resources, temporary local images, and
|
||||
|
@ -110,6 +113,13 @@ var Image = React.createClass({
|
|||
|
||||
statics: {
|
||||
resizeMode: ImageResizeMode,
|
||||
/**
|
||||
* Prefetches a remote image for later use by downloading it to the disk
|
||||
* cache
|
||||
*/
|
||||
prefetch(url: string) {
|
||||
return ImageLoader.prefetchImage(url);
|
||||
},
|
||||
},
|
||||
|
||||
mixins: [NativeMethodsMixin],
|
||||
|
|
|
@ -15,23 +15,22 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType');
|
|||
var ImageResizeMode = require('ImageResizeMode');
|
||||
var ImageStylePropTypes = require('ImageStylePropTypes');
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var NativeModules = require('NativeModules');
|
||||
var PropTypes = require('ReactPropTypes');
|
||||
var React = require('React');
|
||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
var View = require('View');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var StyleSheetPropType = require('StyleSheetPropType');
|
||||
|
||||
var flattenStyle = require('flattenStyle');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var resolveAssetSource = require('resolveAssetSource');
|
||||
var warning = require('fbjs/lib/warning');
|
||||
|
||||
var {
|
||||
ImageLoader,
|
||||
ImageViewManager,
|
||||
NetworkImageViewManager,
|
||||
} = require('NativeModules');
|
||||
} = NativeModules;
|
||||
|
||||
/**
|
||||
* A React component for displaying different types of images,
|
||||
|
@ -181,7 +180,14 @@ var Image = React.createClass({
|
|||
ImageViewManager.getSize(uri, success, failure || function() {
|
||||
console.warn('Failed to get size for image: ' + uri);
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Prefetches a remote image for later use by downloading it to the disk
|
||||
* cache
|
||||
*/
|
||||
prefetch(url: string) {
|
||||
return ImageLoader.prefetchImage(url);
|
||||
},
|
||||
},
|
||||
|
||||
mixins: [NativeMethodsMixin],
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
#import "RCTNetworking.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
static NSString *const RCTErrorInvalidURI = @"E_INVALID_URI";
|
||||
static NSString *const RCTErrorPrefetchFailure = @"E_PREFETCH_FAILURE";
|
||||
|
||||
@implementation UIImage (React)
|
||||
|
||||
- (CAKeyframeAnimation *)reactKeyframeAnimation
|
||||
|
@ -634,6 +637,27 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
|||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Bridged methods
|
||||
|
||||
RCT_EXPORT_METHOD(prefetchImage:(NSString *)uri
|
||||
resolve:(RCTPromiseResolveBlock)resolve
|
||||
reject:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
if (!uri.length) {
|
||||
reject(RCTErrorInvalidURI, @"Cannot prefetch an image for an empty URI", nil);
|
||||
return;
|
||||
}
|
||||
|
||||
[_bridge.imageLoader loadImageWithTag:uri callback:^(NSError *error, UIImage *image) {
|
||||
if (error) {
|
||||
reject(RCTErrorPrefetchFailure, nil, error);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(@YES);
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - RCTURLRequestHandler
|
||||
|
||||
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
include_defs('//ReactAndroid/DEFS')
|
||||
|
||||
android_library(
|
||||
name = 'image',
|
||||
srcs = glob(['*.java']),
|
||||
deps = [
|
||||
react_native_dep('libraries/fresco/fresco-react-native:fbcore'),
|
||||
react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'),
|
||||
react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'),
|
||||
react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'),
|
||||
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
react_native_target('java/com/facebook/react/bridge:bridge'),
|
||||
react_native_target('java/com/facebook/react/common:common'),
|
||||
],
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
],
|
||||
)
|
||||
|
||||
project_config(
|
||||
src_target = ':image',
|
||||
)
|
|
@ -0,0 +1,82 @@
|
|||
/**
|
||||
* 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 android.net.Uri;
|
||||
|
||||
import com.facebook.common.executors.CallerThreadExecutor;
|
||||
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.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
|
||||
public class ImageLoaderModule extends ReactContextBaseJavaModule {
|
||||
|
||||
private static final String ERROR_INVALID_URI = "E_INVALID_URI";
|
||||
private static final String ERROR_PREFETCH_FAILURE = "E_PREFETCH_FAILURE";
|
||||
|
||||
public ImageLoaderModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ImageLoader";
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetches the given image to the Fresco image disk cache.
|
||||
*
|
||||
* @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 prefetchImage(String uriString, 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<Void> prefetchSource = Fresco.getImagePipeline().prefetchToDiskCache(request, this);
|
||||
DataSubscriber<Void> prefetchSubscriber = new BaseDataSubscriber<Void>() {
|
||||
@Override
|
||||
protected void onNewResultImpl(DataSource<Void> dataSource) {
|
||||
if (!dataSource.isFinished()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
promise.resolve(true);
|
||||
} finally {
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFailureImpl(DataSource<Void> dataSource) {
|
||||
try {
|
||||
promise.reject(ERROR_PREFETCH_FAILURE, dataSource.getFailureCause());
|
||||
} finally {
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
prefetchSource.subscribe(prefetchSubscriber, CallerThreadExecutor.getInstance());
|
||||
}
|
||||
}
|
|
@ -4,11 +4,33 @@ android_library(
|
|||
name = 'shell',
|
||||
srcs = glob(['**/*.java']),
|
||||
deps = [
|
||||
react_native_target('res:shell'),
|
||||
react_native_target('java/com/facebook/react:react'),
|
||||
react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
|
||||
react_native_dep('third-party/android/support/v4:lib-support-v4'),
|
||||
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
react_native_target('java/com/facebook/react/bridge:bridge'),
|
||||
react_native_target('java/com/facebook/react/common:common'),
|
||||
react_native_target('java/com/facebook/react/devsupport:devsupport'),
|
||||
react_native_target('java/com/facebook/react/modules/appstate:appstate'),
|
||||
react_native_target('java/com/facebook/react/modules/camera:camera'),
|
||||
react_native_target('java/com/facebook/react/modules/clipboard:clipboard'),
|
||||
react_native_target('java/com/facebook/react/modules/core:core'),
|
||||
react_native_target('java/com/facebook/react/modules/datepicker:datepicker'),
|
||||
react_native_target('java/com/facebook/react/modules/debug:debug'),
|
||||
react_native_target('java/com/facebook/react/modules/dialog:dialog'),
|
||||
react_native_target('java/com/facebook/react/modules/fresco:fresco'),
|
||||
react_native_target('java/com/facebook/react/modules/image:image'),
|
||||
react_native_target('java/com/facebook/react/modules/intent:intent'),
|
||||
react_native_target('java/com/facebook/react/modules/location:location'),
|
||||
react_native_target('java/com/facebook/react/modules/netinfo:netinfo'),
|
||||
react_native_target('java/com/facebook/react/modules/network:network'),
|
||||
react_native_target('java/com/facebook/react/modules/statusbar:statusbar'),
|
||||
react_native_target('java/com/facebook/react/modules/storage:storage'),
|
||||
react_native_target('java/com/facebook/react/modules/timepicker:timepicker'),
|
||||
react_native_target('java/com/facebook/react/modules/toast:toast'),
|
||||
react_native_target('java/com/facebook/react/modules/vibration:vibration'),
|
||||
react_native_target('java/com/facebook/react/modules/websocket:websocket'),
|
||||
react_native_target('java/com/facebook/react/uimanager:uimanager'),
|
||||
react_native_target('java/com/facebook/react/views/art:art'),
|
||||
react_native_target('java/com/facebook/react/views/drawer:drawer'),
|
||||
react_native_target('java/com/facebook/react/views/image:image'),
|
||||
|
@ -20,36 +42,15 @@ android_library(
|
|||
react_native_target('java/com/facebook/react/views/slider:slider'),
|
||||
react_native_target('java/com/facebook/react/views/swiperefresh:swiperefresh'),
|
||||
react_native_target('java/com/facebook/react/views/switchview:switchview'),
|
||||
react_native_target('java/com/facebook/react/views/text:text'),
|
||||
react_native_target('java/com/facebook/react/views/text/frescosupport:frescosupport'),
|
||||
react_native_target('java/com/facebook/react/views/text:text'),
|
||||
react_native_target('java/com/facebook/react/views/textinput:textinput'),
|
||||
react_native_target('java/com/facebook/react/views/toolbar:toolbar'),
|
||||
react_native_target('java/com/facebook/react/views/view:view'),
|
||||
react_native_target('java/com/facebook/react/views/viewpager:viewpager'),
|
||||
react_native_target('java/com/facebook/react/views/webview:webview'),
|
||||
react_native_target('java/com/facebook/react/modules/appstate:appstate'),
|
||||
react_native_target('java/com/facebook/react/modules/vibration:vibration'),
|
||||
react_native_target('java/com/facebook/react/modules/camera:camera'),
|
||||
react_native_target('java/com/facebook/react/modules/clipboard:clipboard'),
|
||||
react_native_target('java/com/facebook/react/modules/core:core'),
|
||||
react_native_target('java/com/facebook/react/modules/datepicker:datepicker'),
|
||||
react_native_target('java/com/facebook/react/modules/debug:debug'),
|
||||
react_native_target('java/com/facebook/react/modules/dialog:dialog'),
|
||||
react_native_target('java/com/facebook/react/modules/fresco:fresco'),
|
||||
react_native_target('java/com/facebook/react/modules/intent:intent'),
|
||||
react_native_target('java/com/facebook/react/modules/location:location'),
|
||||
react_native_target('java/com/facebook/react/modules/netinfo:netinfo'),
|
||||
react_native_target('java/com/facebook/react/modules/network:network'),
|
||||
react_native_target('java/com/facebook/react/modules/statusbar:statusbar'),
|
||||
react_native_target('java/com/facebook/react/modules/storage:storage'),
|
||||
react_native_target('java/com/facebook/react/modules/timepicker:timepicker'),
|
||||
react_native_target('java/com/facebook/react/modules/toast:toast'),
|
||||
react_native_target('java/com/facebook/react/uimanager:uimanager'),
|
||||
react_native_target('java/com/facebook/react/modules/websocket:websocket'),
|
||||
react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
|
||||
react_native_dep('third-party/android/support/v4:lib-support-v4'),
|
||||
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
|
||||
react_native_dep('third-party/java/jsr-305:jsr-305'),
|
||||
react_native_target('java/com/facebook/react:react'),
|
||||
react_native_target('res:shell'),
|
||||
],
|
||||
visibility = [
|
||||
'PUBLIC',
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.facebook.react.modules.clipboard.ClipboardModule;
|
|||
import com.facebook.react.modules.datepicker.DatePickerDialogModule;
|
||||
import com.facebook.react.modules.dialog.DialogModule;
|
||||
import com.facebook.react.modules.fresco.FrescoModule;
|
||||
import com.facebook.react.modules.image.ImageLoaderModule;
|
||||
import com.facebook.react.modules.intent.IntentModule;
|
||||
import com.facebook.react.modules.location.LocationModule;
|
||||
import com.facebook.react.modules.netinfo.NetInfoModule;
|
||||
|
@ -76,6 +77,7 @@ public class MainReactPackage implements ReactPackage {
|
|||
new DialogModule(reactContext),
|
||||
new FrescoModule(reactContext),
|
||||
new ImageEditingManager(reactContext),
|
||||
new ImageLoaderModule(reactContext),
|
||||
new ImageStoreManager(reactContext),
|
||||
new IntentModule(reactContext),
|
||||
new LocationModule(reactContext),
|
||||
|
|
Loading…
Reference in New Issue