一般我們有3種數(shù)據(jù)需要緩存和下載:純文本(比如API返回囤捻,狀態(tài)標(biāo)記等),圖片緩存和其他靜態(tài)文件视哑。
純文本
純文本還是比較簡單的挡毅,RN官方模塊AsyncStorage
就夠了跪呈。它就跟HTML5里面的LocalStorage
一樣段磨,你可以直接調(diào)setItem
和getItem
去操作數(shù)據(jù)苹支,這兩個方法都會返回一個promise债蜜。下面是官方的例子:
緩存數(shù)據(jù)
_storeData = async () => {
try {
await AsyncStorage.setItem('@MySuperStore:key', 'I like to save it.');
} catch (error) {
// Error saving data
}
};
獲取數(shù)據(jù)
_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代碼實現(xiàn)的特姐,如果是小數(shù)據(jù)唐含,就存在一個序列化字典里面沫浆,如果數(shù)據(jù)量太大,就單獨存一個文件淮捆。在Android上攀痊,AsyncStorage
使用的是RocksDB 或者 SQLite苟径,取決于當(dāng)前設(shè)備支持哪個躬审。需要注意的是,Android上有大小限制遭殉,最大只能存6MB恩沽。這個是RN官方故意的翔始,可以看這里的源碼城瞎。如果需要的話,可以覆蓋這個限制:
- 找到
/android/app/src/main/java/MainApplication.java
并且導(dǎo)入ReactDatabaseSupplier
飒箭。
導(dǎo)入后這個文件看起來像這樣: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
并設(shè)置新的maximumSize
弦蹂,我這里設(shè)置為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變得很大很丑咙崎,如果存儲失敗,雖然會拋錯网杆,但是數(shù)據(jù)并不會回滾碳却,數(shù)據(jù)會更丑追城。如果你需要存儲大的數(shù)據(jù)燥撞,你可以把它存為文件物舒,我們后面會講到
圖片
如果一個圖片我們已經(jīng)加載過一次了冠胯,下次再用的時候我就不想再加載一次了,最好是直接能從緩存讀出來置蜀。官方組件Image
有一個屬性 cache
可以支持一些緩存,但是他只支持iOS馋吗。我這里找了兩個比較流行的庫react-native-cached-image 和 react-native-fast-image
react-native-cached-image
你可以跟著官方指引來安裝宏粤,我就不多說了绍哎。但是要注意一點崇堰,這個庫依賴 react-native-fetch-blob灿巧。這是一個native模塊抠藕,在執(zhí)行yarn add
或者 npm install
后,你需要把它鏈接到你的項目盾似,最簡單的是執(zhí)行react-native link react-native-fetch-blob
自動鏈接零院。如果你的項目結(jié)構(gòu)跟自動鏈接的不一樣,你需要手動鏈接告抄,可以參考這里撰茎。
這個庫有三個比較有用的組件,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
是用來控制緩存的龄糊,你可以用它下載和刪除圖片,甚至你可以獲取到下載圖片的物理地址募疮。它并沒有緩存優(yōu)先炫惩,強制刷新,強制使用緩存這種預(yù)設(shè)規(guī)則可以用阿浓,具體怎么用需要你自己定義他嚷。
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}
/>
它預(yù)設(shè)了三種模式來控制緩存,其中一個是FastImage.cacheControl.web
,這個策略就是網(wǎng)頁是一樣的了,他會采用HTTP的緩存控制頭來控制右莱,前端開發(fā)者應(yīng)該很熟悉胀瞪。這個庫官方有很多例子可以看圆雁,看這里汛蝙。做圖片緩存的話,推薦用這個。
其他靜態(tài)文件
有時候我們需要下載或者緩存一些靜態(tài)文件到設(shè)備上跳昼,比如pdf, mp3, mp4等住拭。rn-fetch-blob
是一個可以將你的HTTP返回作為文件存在設(shè)備上的native庫。他其實就是react-native-fetch-blob
摊求,但是react-native-fetch-blob
沒有繼續(xù)維護(hù)了硫惕,所以就fork過來改了個名字繼續(xù)維護(hù)曼氛。
你只要在請求的配置里面設(shè)置fileCache : true
气破,他就會將返回值作為文件存起來,同時返回給你物理路徑,默認(rèn)存的文件是沒有后綴名的佩迟,你可以加參數(shù)設(shè)定后綴,比如: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 ..
})
在下載和上傳過程中,還可以拿到他的進(jìn)度:
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
并不會記錄你的下載歷史,就是說你關(guān)掉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,
});
});
檢查是不是已經(jīng)下過這個文件了:
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');
});
}