1. 程式人生 > >React Native的緩存和下載

React Native的緩存和下載

-c 返回 end 就會 .so 刷新 modules 有時 方法

一般我們有3種數據需要緩存和下載:純文本(比如API返回,狀態標記等),圖片緩存和其他靜態文件。

純文本

純文本還是比較簡單的,RN官方模塊AsyncStorage就夠了。它就跟HTML5裏面的LocalStorage一樣,你可以直接調setItemgetItem去操作數據,這兩個方法都會返回一個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, ImageCacheProviderImageCacheManager,這是一個官方例子:

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‘);
  });
}

React Native的緩存和下載