一般我们有3种数据需要缓存和下载:纯文本(比如API返回,状态标记等),图片缓存和其他静态文件。
纯文本
纯文本还是比较简单的,RN官方模块AsyncStorage
就够了。它就跟HTML5里面的LocalStorage
一样,你可以直接调setItem
和getItem
去操作数据,这两个方法都会返回一个promise。下面是官方的例子:
缓存数据
- _storeData = async () => {
- try {
- await AsyncStorage.setItem('@MySuperStore:key', 'I like to save it.');
- } catch (error) {
- // Error saving data
- }
- };
获取数据
- _retrieveData = async () => {
- try {
- const value = await AsyncStorage.getItem('TASKS');
- if (value !== null) {
- // We have data!!
- console.log(value);
- }
- } catch (error) {
- // Error retrieving data
- }
- };
在iOS上,AsyncStorage
是native代码实现的,如果是小数据,就存在一个序列化字典里面,如果数据量太大,就单独存一个文件。在Android上,AsyncStorage
使用的是RocksDB 或者 SQLite,取决于当前设备支持哪个。需要注意的是,Android上有大小限制,最大只能存6MB。这个是RN官方故意的,可以看这里的源码。如果需要的话,可以覆盖这个限制:
找到
/android/app/src/main/java/MainApplication.java
并且导入ReactDatabaseSupplier
。import com.facebook.react.modules.storage.ReactDatabaseSupplier;
导入后这个文件看起来像这样:
- import com.facebook.react.shell.MainReactPackage;
- import com.facebook.soloader.SoLoader;
- import com.facebook.react.modules.storage.ReactDatabaseSupplier;
找到
onCreate
并设置新的maximumSize
,我这里设置为50MB- long size = 50L * 1024L * 1024L; // 50 MB
- ReactDatabaseSupplier.getInstance(getApplicationContext()).setMaximumSize(size);
改好后的
onCreate
看起来是这样:- @Override
- public void onCreate() {
- super.onCreate();
- SoLoader.init(this, /* native exopackage */ false);
- long size = 50L * 1024L * 1024L; // 50 MB
- ReactDatabaseSupplier.getInstance(getApplicationContext()).setMaximumSize(size);
- }
虽然可以覆盖这个大小,但是不推荐这么做,这会让DB变得很大很丑,如果存储失败,虽然会抛错,但是数据并不会回滚,数据会更丑。如果你需要存储大的数据,你可以把它存为文件,我们后面会讲到
图片
如果一个图片我们已经加载过一次了,下次再用的时候我就不想再加载一次了,最好是直接能从缓存读出来。官方组件Image
有一个属性 cache
可以支持一些缓存,但是他只支持iOS。我这里找了两个比较流行的库react-native-cached-image 和 react-native-fast-image
react-native-cached-image
你可以跟着官方指引来安装,我就不多说了。但是要注意一点,这个库依赖 react-native-fetch-blob。这是一个native模块,在执行yarn add
或者 npm install
后,你需要把它链接到你的项目,最简单的是执行react-native link react-native-fetch-blob
自动链接。如果你的项目结构跟自动链接的不一样,你需要手动链接,可以参考这里。
这个库有三个比较有用的组件,CachedImage
, ImageCacheProvider
和 ImageCacheManager
,这是一个官方例子:
- import React from 'react';
- import {
- CachedImage,
- ImageCacheProvider
- } from 'react-native-cached-image';
-
- const images = [
- 'https://example.com/images/1.jpg',
- 'https://example.com/images/2.jpg',
- 'https://example.com/images/3.jpg',
- // ...
- ];
-
- export default class Example extends React.Component {
- render() {
- return (
- <ImageCacheProvider
- urlsToPreload={images}
- onPreloadComplete={() => console.log('hey there')}>
-
- <CachedImage source={{uri: images[0]}}/>
-
- <CachedImage source={{uri: images[1]}}/>
-
- <CachedImage source={{uri: images[2]}}/>
-
- </ImageCacheProvider>
- );
- }
- }
ImageCacheManager
是用来控制缓存的,你可以用它下载和删除图片,甚至你可以获取到下载图片的物理地址。它并没有缓存优先,强制刷新,强制使用缓存这种预设规则可以用,具体怎么用需要你自己定义。
react-native-fast-image
react-native-fast-image
用起来更简单一点,在GitHub上的星星也多一点。这是一个native库,在iOS上是包装的 SDWebImage,Android上是包装的Glide (Android),这两个都是原生上万星星的库。因为是native库,所以安装后也需要链接,具体方法跟上面一样。这是一个使用例子:
- import FastImage from 'react-native-fast-image'
-
- const YourImage = () =>
- <FastImage
- style={styles.image}
- source={{
- uri: 'https://unsplash.it/400/400?image=1',
- headers:{ Authorization: 'someAuthToken' },
- priority: FastImage.priority.normal,
- cache: FastImage.cacheControl.web
- }}
- resizeMode={FastImage.resizeMode.contain}
- />
它预设了三种模式来控制缓存,其中一个是FastImage.cacheControl.web
,这个策略就是网页是一样的了,他会采用HTTP的缓存控制头来控制,前端开发者应该很熟悉。这个库官方有很多例子可以看,看这里。做图片缓存的话,推荐用这个。
其他静态文件
有时候我们需要下载或者缓存一些静态文件到设备上,比如pdf, mp3, mp4等。rn-fetch-blob
是一个可以将你的HTTP返回作为文件存在设备上的native库。他其实就是react-native-fetch-blob
,但是react-native-fetch-blob
没有继续维护了,所以就fork过来改了个名字继续维护。
你只要在请求的配置里面设置fileCache : true
,他就会将返回值作为文件存起来,同时返回给你物理路径,默认存的文件是没有后缀名的,你可以加参数设定后缀,比如:appendExt : 'zip'
- RNFetchBlob
- .config({
- // add this option that makes response data to be stored as a file,
- // this is much more performant.
- fileCache : true,
- appendExt : 'zip'
- })
- .fetch('GET', 'http://www.example.com/file/example.zip', {
- Authorization : 'Bearer access-token...',
- //some headers ..
- })
- .then((res) => {
- // the temp file path
- console.log('The file saved to ', res.path())
- })
拿到这个路径可以直接用
imageView = <Image source={{ uri : Platform.OS === 'android' ? 'file://' + res.path() : '' + res.path() }}/>
这个库还可以支持文件上传
- RNFetchBlob.fetch('POST', 'https://content.dropboxapi.com/2/files/upload', {
- // dropbox upload headers
- Authorization : "Bearer access-token...",
- 'Dropbox-API-Arg': JSON.stringify({
- path : '/img-from-react-native.png',
- mode : 'add',
- autorename : true,
- mute : false
- }),
- 'Content-Type' : 'application/octet-stream',
- // Change BASE64 encoded data to a file path with prefix `RNFetchBlob-file://`.
- // Or simply wrap the file path with RNFetchBlob.wrap().
- }, RNFetchBlob.wrap(PATH_TO_THE_FILE))
- .then((res) => {
- console.log(res.text())
- })
- .catch((err) => {
- // error handling ..
- })
在下载和上传过程中,还可以拿到他的进度:
- RNFetchBlob.fetch('POST', 'http://www.example.com/upload', {
- //... some headers,
- 'Content-Type' : 'octet-stream'
- }, base64DataString)
- // listen to upload progress event
- .uploadProgress((written, total) => {
- console.log('uploaded', written / total)
- })
- .then((resp) => {
- // ...
- })
- .catch((err) => {
- // ...
- })
-
- RNFetchBlob
- .config({
- // add this option that makes response data to be stored as a file,
- // this is much more performant.
- fileCache : true,
- appendExt : 'zip'
- })
- .fetch('GET', 'http://www.example.com/file/example.zip', {
- Authorization : 'Bearer access-token...',
- //some headers ..
- })
- // listen to download progress event
- .progress((received, total) => {
- console.log('progress', received / total)
- })
- .then((res) => {
- // the temp file path
- console.log('The file saved to ', res.path())
- })
要注意点的是,rn-fetch-blob
并不会记录你的下载历史,就是说你关掉APP再打开,你就不知道你下载的文件哪儿去了。我们可以用AsyncStorage
配合着记录下载的历史。
下载完成后记录地址到AsyncStorage
:
- RNFetchBlob
- .config({
- fileCache: true,
- appendExt: 'pdf',
- })
- .fetch('GET', 'http://pdf.dfcfw.com/pdf/H3_AP201901271289150621_1.pdf')
- .then((response) => {
- const path = response.path();
-
- this.setState({
- cachedFile: path,
- });
-
- AsyncStorage.setItem('fileCache', path);
- })
- .catch((error) => {
- this.setState({
- error,
- });
- });
检查是不是已经下过这个文件了:
- componentDidMount() {
- AsyncStorage.getItem('fileCache').then((data) => {
- this.setState({
- cachedFile: data,
- });
- });
- }
用完了也可以删除这个文件,同时删除记录
- clearCache() {
- const { cachedFile } = this.state;
- RNFetchBlob.fs.unlink(cachedFile).then(() => {
- this.setState({
- cachedFile: null,
- });
- AsyncStorage. removeItem('fileCache');
- });
- }