X Tutup
Skip to content

Commit f7bcb3e

Browse files
ideFacebook Github Bot 1
authored andcommitted
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 facebook/react-native#6774 Differential Revision: D3153729 Pulled By: bestander fb-gh-sync-id: ef61412e051a49b42ae885edce7905a8ca0da23f fbshipit-source-id: ef61412e051a49b42ae885edce7905a8ca0da23f
1 parent 4450d78 commit f7bcb3e

File tree

8 files changed

+204
-35
lines changed

8 files changed

+204
-35
lines changed

Examples/UIExplorer/ImageExample.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,14 @@ var {
3636
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==';
3737

3838
var ImageCapInsetsExample = require('./ImageCapInsetsExample');
39+
const IMAGE_PREFETCH_URL = 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now();
40+
var prefetchTask = Image.prefetch(IMAGE_PREFETCH_URL);
3941

4042
var NetworkImageCallbackExample = React.createClass({
4143
getInitialState: function() {
4244
return {
4345
events: [],
46+
startLoadPrefetched: false,
4447
mountTime: new Date(),
4548
};
4649
},
@@ -59,9 +62,26 @@ var NetworkImageCallbackExample = React.createClass({
5962
style={[styles.base, {overflow: 'visible'}]}
6063
onLoadStart={() => this._loadEventFired(`✔ onLoadStart (+${new Date() - mountTime}ms)`)}
6164
onLoad={() => this._loadEventFired(`✔ onLoad (+${new Date() - mountTime}ms)`)}
62-
onLoadEnd={() => this._loadEventFired(`✔ onLoadEnd (+${new Date() - mountTime}ms)`)}
65+
onLoadEnd={() => {
66+
this._loadEventFired(`✔ onLoadEnd (+${new Date() - mountTime}ms)`);
67+
this.setState({startLoadPrefetched: true}, () => {
68+
prefetchTask.then(() => {
69+
this._loadEventFired(`✔ Prefetch OK (+${new Date() - mountTime}ms)`);
70+
}, error => {
71+
this._loadEventFired(`✘ Prefetch failed (+${new Date() - mountTime}ms)`);
72+
});
73+
});
74+
}}
6375
/>
64-
76+
{this.state.startLoadPrefetched ?
77+
<Image
78+
source={this.props.prefetchedSource}
79+
style={[styles.base, {overflow: 'visible'}]}
80+
onLoadStart={() => this._loadEventFired(`✔ (prefetched) onLoadStart (+${new Date() - mountTime}ms)`)}
81+
onLoad={() => this._loadEventFired(`✔ (prefetched) onLoad (+${new Date() - mountTime}ms)`)}
82+
onLoadEnd={() => this._loadEventFired(`✔ (prefetched) onLoadEnd (+${new Date() - mountTime}ms)`)}
83+
/>
84+
: null}
6585
<Text style={{marginTop: 20}}>
6686
{this.state.events.join('\n')}
6787
</Text>
@@ -174,7 +194,8 @@ exports.examples = [
174194
title: 'Image Loading Events',
175195
render: function() {
176196
return (
177-
<NetworkImageCallbackExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1'}}/>
197+
<NetworkImageCallbackExample source={{uri: 'http://facebook.github.io/origami/public/images/blog-hero.jpg?r=1&t=' + Date.now()}}
198+
prefetchedSource={{uri: IMAGE_PREFETCH_URL}}/>
178199
);
179200
},
180201
},

Libraries/Image/Image.android.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,14 @@ var StyleSheetPropType = require('StyleSheetPropType');
2323
var View = require('View');
2424

2525
var flattenStyle = require('flattenStyle');
26-
var invariant = require('fbjs/lib/invariant');
2726
var merge = require('merge');
2827
var requireNativeComponent = require('requireNativeComponent');
2928
var resolveAssetSource = require('resolveAssetSource');
3029

30+
var {
31+
ImageLoader,
32+
} = NativeModules;
33+
3134
/**
3235
* <Image> - A react component for displaying different types of images,
3336
* including network images, static resources, temporary local images, and
@@ -110,6 +113,13 @@ var Image = React.createClass({
110113

111114
statics: {
112115
resizeMode: ImageResizeMode,
116+
/**
117+
* Prefetches a remote image for later use by downloading it to the disk
118+
* cache
119+
*/
120+
prefetch(url: string) {
121+
return ImageLoader.prefetchImage(url);
122+
},
113123
},
114124

115125
mixins: [NativeMethodsMixin],

Libraries/Image/Image.ios.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,22 @@ var EdgeInsetsPropType = require('EdgeInsetsPropType');
1515
var ImageResizeMode = require('ImageResizeMode');
1616
var ImageStylePropTypes = require('ImageStylePropTypes');
1717
var NativeMethodsMixin = require('NativeMethodsMixin');
18+
var NativeModules = require('NativeModules');
1819
var PropTypes = require('ReactPropTypes');
1920
var React = require('React');
2021
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
21-
var View = require('View');
2222
var StyleSheet = require('StyleSheet');
2323
var StyleSheetPropType = require('StyleSheetPropType');
2424

2525
var flattenStyle = require('flattenStyle');
26-
var invariant = require('fbjs/lib/invariant');
2726
var requireNativeComponent = require('requireNativeComponent');
2827
var resolveAssetSource = require('resolveAssetSource');
29-
var warning = require('fbjs/lib/warning');
3028

3129
var {
30+
ImageLoader,
3231
ImageViewManager,
3332
NetworkImageViewManager,
34-
} = require('NativeModules');
33+
} = NativeModules;
3534

3635
/**
3736
* A React component for displaying different types of images,
@@ -181,7 +180,14 @@ var Image = React.createClass({
181180
ImageViewManager.getSize(uri, success, failure || function() {
182181
console.warn('Failed to get size for image: ' + uri);
183182
});
184-
}
183+
},
184+
/**
185+
* Prefetches a remote image for later use by downloading it to the disk
186+
* cache
187+
*/
188+
prefetch(url: string) {
189+
return ImageLoader.prefetchImage(url);
190+
},
185191
},
186192

187193
mixins: [NativeMethodsMixin],

Libraries/Image/RCTImageLoader.m

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
#import "RCTNetworking.h"
2121
#import "RCTUtils.h"
2222

23+
static NSString *const RCTErrorInvalidURI = @"E_INVALID_URI";
24+
static NSString *const RCTErrorPrefetchFailure = @"E_PREFETCH_FAILURE";
25+
2326
@implementation UIImage (React)
2427

2528
- (CAKeyframeAnimation *)reactKeyframeAnimation
@@ -634,6 +637,27 @@ - (RCTImageLoaderCancellationBlock)getImageSize:(NSString *)imageTag
634637
}];
635638
}
636639

640+
#pragma mark - Bridged methods
641+
642+
RCT_EXPORT_METHOD(prefetchImage:(NSString *)uri
643+
resolve:(RCTPromiseResolveBlock)resolve
644+
reject:(RCTPromiseRejectBlock)reject)
645+
{
646+
if (!uri.length) {
647+
reject(RCTErrorInvalidURI, @"Cannot prefetch an image for an empty URI", nil);
648+
return;
649+
}
650+
651+
[_bridge.imageLoader loadImageWithTag:uri callback:^(NSError *error, UIImage *image) {
652+
if (error) {
653+
reject(RCTErrorPrefetchFailure, nil, error);
654+
return;
655+
}
656+
657+
resolve(@YES);
658+
}];
659+
}
660+
637661
#pragma mark - RCTURLRequestHandler
638662

639663
- (BOOL)canHandleRequest:(NSURLRequest *)request
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
include_defs('//ReactAndroid/DEFS')
2+
3+
android_library(
4+
name = 'image',
5+
srcs = glob(['*.java']),
6+
deps = [
7+
react_native_dep('libraries/fresco/fresco-react-native:fbcore'),
8+
react_native_dep('libraries/fresco/fresco-react-native:fresco-drawee'),
9+
react_native_dep('libraries/fresco/fresco-react-native:fresco-react-native'),
10+
react_native_dep('libraries/fresco/fresco-react-native:imagepipeline'),
11+
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
12+
react_native_dep('third-party/java/jsr-305:jsr-305'),
13+
react_native_target('java/com/facebook/react/bridge:bridge'),
14+
react_native_target('java/com/facebook/react/common:common'),
15+
],
16+
visibility = [
17+
'PUBLIC',
18+
],
19+
)
20+
21+
project_config(
22+
src_target = ':image',
23+
)
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
package com.facebook.react.modules.image;
11+
12+
import android.net.Uri;
13+
14+
import com.facebook.common.executors.CallerThreadExecutor;
15+
import com.facebook.datasource.BaseDataSubscriber;
16+
import com.facebook.datasource.DataSource;
17+
import com.facebook.datasource.DataSubscriber;
18+
import com.facebook.drawee.backends.pipeline.Fresco;
19+
import com.facebook.imagepipeline.request.ImageRequest;
20+
import com.facebook.imagepipeline.request.ImageRequestBuilder;
21+
import com.facebook.react.bridge.Promise;
22+
import com.facebook.react.bridge.ReactApplicationContext;
23+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
24+
import com.facebook.react.bridge.ReactMethod;
25+
26+
public class ImageLoaderModule extends ReactContextBaseJavaModule {
27+
28+
private static final String ERROR_INVALID_URI = "E_INVALID_URI";
29+
private static final String ERROR_PREFETCH_FAILURE = "E_PREFETCH_FAILURE";
30+
31+
public ImageLoaderModule(ReactApplicationContext reactContext) {
32+
super(reactContext);
33+
}
34+
35+
@Override
36+
public String getName() {
37+
return "ImageLoader";
38+
}
39+
40+
/**
41+
* Prefetches the given image to the Fresco image disk cache.
42+
*
43+
* @param uriString the URI of the remote image to prefetch
44+
* @param promise the promise that is fulfilled when the image is successfully prefetched
45+
* or rejected when there is an error
46+
*/
47+
@ReactMethod
48+
public void prefetchImage(String uriString, final Promise promise) {
49+
if (uriString == null || uriString.isEmpty()) {
50+
promise.reject(ERROR_INVALID_URI, "Cannot prefetch an image for an empty URI");
51+
return;
52+
}
53+
54+
Uri uri = Uri.parse(uriString);
55+
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).build();
56+
57+
DataSource<Void> prefetchSource = Fresco.getImagePipeline().prefetchToDiskCache(request, this);
58+
DataSubscriber<Void> prefetchSubscriber = new BaseDataSubscriber<Void>() {
59+
@Override
60+
protected void onNewResultImpl(DataSource<Void> dataSource) {
61+
if (!dataSource.isFinished()) {
62+
return;
63+
}
64+
try {
65+
promise.resolve(true);
66+
} finally {
67+
dataSource.close();
68+
}
69+
}
70+
71+
@Override
72+
protected void onFailureImpl(DataSource<Void> dataSource) {
73+
try {
74+
promise.reject(ERROR_PREFETCH_FAILURE, dataSource.getFailureCause());
75+
} finally {
76+
dataSource.close();
77+
}
78+
}
79+
};
80+
prefetchSource.subscribe(prefetchSubscriber, CallerThreadExecutor.getInstance());
81+
}
82+
}

ReactAndroid/src/main/java/com/facebook/react/shell/BUCK

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,22 @@ android_library(
44
name = 'shell',
55
srcs = glob(['**/*.java']),
66
deps = [
7-
react_native_target('res:shell'),
8-
react_native_target('java/com/facebook/react:react'),
7+
react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
8+
react_native_dep('third-party/android/support/v4:lib-support-v4'),
9+
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
10+
react_native_dep('third-party/java/jsr-305:jsr-305'),
911
react_native_target('java/com/facebook/react/bridge:bridge'),
1012
react_native_target('java/com/facebook/react/common:common'),
1113
react_native_target('java/com/facebook/react/devsupport:devsupport'),
12-
react_native_target('java/com/facebook/react/views/art:art'),
13-
react_native_target('java/com/facebook/react/views/drawer:drawer'),
14-
react_native_target('java/com/facebook/react/views/image:image'),
15-
react_native_target('java/com/facebook/react/views/modal:modal'),
16-
react_native_target('java/com/facebook/react/views/picker:picker'),
17-
react_native_target('java/com/facebook/react/views/progressbar:progressbar'),
18-
react_native_target('java/com/facebook/react/views/recyclerview:recyclerview'),
19-
react_native_target('java/com/facebook/react/views/scroll:scroll'),
20-
react_native_target('java/com/facebook/react/views/slider:slider'),
21-
react_native_target('java/com/facebook/react/views/swiperefresh:swiperefresh'),
22-
react_native_target('java/com/facebook/react/views/switchview:switchview'),
23-
react_native_target('java/com/facebook/react/views/text:text'),
24-
react_native_target('java/com/facebook/react/views/text/frescosupport:frescosupport'),
25-
react_native_target('java/com/facebook/react/views/textinput:textinput'),
26-
react_native_target('java/com/facebook/react/views/toolbar:toolbar'),
27-
react_native_target('java/com/facebook/react/views/view:view'),
28-
react_native_target('java/com/facebook/react/views/viewpager:viewpager'),
29-
react_native_target('java/com/facebook/react/views/webview:webview'),
3014
react_native_target('java/com/facebook/react/modules/appstate:appstate'),
31-
react_native_target('java/com/facebook/react/modules/vibration:vibration'),
3215
react_native_target('java/com/facebook/react/modules/camera:camera'),
3316
react_native_target('java/com/facebook/react/modules/clipboard:clipboard'),
3417
react_native_target('java/com/facebook/react/modules/core:core'),
3518
react_native_target('java/com/facebook/react/modules/datepicker:datepicker'),
3619
react_native_target('java/com/facebook/react/modules/debug:debug'),
3720
react_native_target('java/com/facebook/react/modules/dialog:dialog'),
3821
react_native_target('java/com/facebook/react/modules/fresco:fresco'),
22+
react_native_target('java/com/facebook/react/modules/image:image'),
3923
react_native_target('java/com/facebook/react/modules/intent:intent'),
4024
react_native_target('java/com/facebook/react/modules/location:location'),
4125
react_native_target('java/com/facebook/react/modules/netinfo:netinfo'),
@@ -44,12 +28,29 @@ android_library(
4428
react_native_target('java/com/facebook/react/modules/storage:storage'),
4529
react_native_target('java/com/facebook/react/modules/timepicker:timepicker'),
4630
react_native_target('java/com/facebook/react/modules/toast:toast'),
47-
react_native_target('java/com/facebook/react/uimanager:uimanager'),
31+
react_native_target('java/com/facebook/react/modules/vibration:vibration'),
4832
react_native_target('java/com/facebook/react/modules/websocket:websocket'),
49-
react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
50-
react_native_dep('third-party/android/support/v4:lib-support-v4'),
51-
react_native_dep('third-party/java/infer-annotations:infer-annotations'),
52-
react_native_dep('third-party/java/jsr-305:jsr-305'),
33+
react_native_target('java/com/facebook/react/uimanager:uimanager'),
34+
react_native_target('java/com/facebook/react/views/art:art'),
35+
react_native_target('java/com/facebook/react/views/drawer:drawer'),
36+
react_native_target('java/com/facebook/react/views/image:image'),
37+
react_native_target('java/com/facebook/react/views/modal:modal'),
38+
react_native_target('java/com/facebook/react/views/picker:picker'),
39+
react_native_target('java/com/facebook/react/views/progressbar:progressbar'),
40+
react_native_target('java/com/facebook/react/views/recyclerview:recyclerview'),
41+
react_native_target('java/com/facebook/react/views/scroll:scroll'),
42+
react_native_target('java/com/facebook/react/views/slider:slider'),
43+
react_native_target('java/com/facebook/react/views/swiperefresh:swiperefresh'),
44+
react_native_target('java/com/facebook/react/views/switchview:switchview'),
45+
react_native_target('java/com/facebook/react/views/text/frescosupport:frescosupport'),
46+
react_native_target('java/com/facebook/react/views/text:text'),
47+
react_native_target('java/com/facebook/react/views/textinput:textinput'),
48+
react_native_target('java/com/facebook/react/views/toolbar:toolbar'),
49+
react_native_target('java/com/facebook/react/views/view:view'),
50+
react_native_target('java/com/facebook/react/views/viewpager:viewpager'),
51+
react_native_target('java/com/facebook/react/views/webview:webview'),
52+
react_native_target('java/com/facebook/react:react'),
53+
react_native_target('res:shell'),
5354
],
5455
visibility = [
5556
'PUBLIC',

ReactAndroid/src/main/java/com/facebook/react/shell/MainReactPackage.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.facebook.react.modules.datepicker.DatePickerDialogModule;
2626
import com.facebook.react.modules.dialog.DialogModule;
2727
import com.facebook.react.modules.fresco.FrescoModule;
28+
import com.facebook.react.modules.image.ImageLoaderModule;
2829
import com.facebook.react.modules.intent.IntentModule;
2930
import com.facebook.react.modules.location.LocationModule;
3031
import com.facebook.react.modules.netinfo.NetInfoModule;
@@ -76,6 +77,7 @@ public List<NativeModule> createNativeModules(ReactApplicationContext reactConte
7677
new DialogModule(reactContext),
7778
new FrescoModule(reactContext),
7879
new ImageEditingManager(reactContext),
80+
new ImageLoaderModule(reactContext),
7981
new ImageStoreManager(reactContext),
8082
new IntentModule(reactContext),
8183
new LocationModule(reactContext),

0 commit comments

Comments
 (0)
X Tutup