React Native AsyncStorage | 行太大,无法适应CursorWindow

5

我在ReactNative中使用AsyncStorage存储一些数据(大于2MB),然后使用以下代码读取:

try {
   const value = await AsyncStorage.getItem('date_stored_copy');
} catch (e) {
   console.log(e);
}

我遇到了以下错误:

行太大,无法适应 CursorWindow requiredPos=0, totalRows=1...

有没有办法增加 CursorWindow 的大小,或者使用其他替代方案来代替 AsyncStorage?

5个回答

3
一种替代方案是将数据分成块,然后再写入。
我编写了一个使用AsyncStorage的包装器,正是这样做的。
请注意,它最初是为与 apollo-cache-persist(用于 apollo-client 的持久化库)一起使用而编写的。由于 GraphQL 将数据存储在非常扁平的结构中,因此这个解决方案可以很好地直接使用。
对于您的情况,如果您存储的对象看起来像这样:
{
    data: { a lot of data here }
}

如果您的对象长这样:

那么问题就不大了,包装器也不会起作用。

但是,如果您的对象长这样:

{
    someData: { partial data },
    someMoreData: { more partial data },
    ....
}

理论上来说它应该可以工作。
完全透明地说,我还没有进行彻底的测试,只是在使用 apollo-cache-persist 时尝试了一下。

2
我也遇到了这个问题,以下是我解决此问题的方法:
算法的基本描述:
  • "key"保存数据将被分成几部分的数量。(例如:key是"MyElementToStore",则其值为7,表示需要将数据分成7个部分才能适合存储在AsyncStorage的每一行中)
  • 然后,每个部分将作为单独的一行存储在AsyncStorage中,名称为键名加上该部分的索引。(例如:["MyElementToStore0", "MyElementToStore1", ...]
  • 检索数据的方式相反,每行都被检索并聚合到结果中返回
  • 最后需要注意的是,在删除键之前清除存储时,重要的是要移除每个部分(使用最后一个函数“clearStore”确保正确释放内存)

AsyncStorage 文档

import AsyncStorage from "@react-native-async-storage/async-storage";

const getStore = async (key) =>
{
  try
  {
    let store = "";
    let numberOfParts = await AsyncStorage.getItem(key);
    if(typeof(numberOfParts) === 'undefined' || numberOfParts === null)
      return null;
    else
      numberOfParts = parseInt(numberOfParts);
    for (let i = 0; i < numberOfParts; i++) { store += await AsyncStorage.getItem(key + i); }
    if(store === "")
      return null;
    return JSON.parse(store);
  }
  catch (error)
  {
    console.log("Could not get [" + key + "] from store.");
    console.log(error);
    return null;
  }
};

const saveStore = async (key, data) =>
{
  try
  {
    const store = JSON.stringify(data).match(/.{1,1000000}/g);
    store.forEach((part, index) => { AsyncStorage.setItem((key + index), part); });
    AsyncStorage.setItem(key, ("" + store.length));
  }
  catch (error)
  {
    console.log("Could not save store : ");
    console.log(error.message);
  }
};

const clearStore = async (key) =>
{
  try
  {
    console.log("Clearing store for [" + key + "]");
    let numberOfParts = await AsyncStorage.getItem(key);
    if(typeof(numberOfParts) !== 'undefined' && numberOfParts !== null)
    {
      numberOfParts = parseInt(numberOfParts);
      for (let i = 0; i < numberOfParts; i++) { AsyncStorage.removeItem(key + i); }
      AsyncStorage.removeItem(key);
    }
  }
  catch (error)
  {
    console.log("Could not clear store : ");
    console.log(error.message);
  }
};


2

android/app/src/main/java/com/tamotam/mainApp/MainApplication.java

import android.database.CursorWindow;
import java.lang.reflect.Field;
...
@Override
public void onCreate() {
...
  try {
    Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");
    field.setAccessible(true);
    field.set(null, 100 * 1024 * 1024); // the 100MB is the new size
   } catch (Exception e) {
      e.printStackTrace();
   }
}

这个问题对我来说已经解决了,记得要包含这两个导入!

根据https://github.com/andpor/react-native-sqlite-storage/issues/364#issuecomment-665800433的说法,在某些解决方案中可能会有一个额外的检查if (DEBUG_MODE)...,但在我的情况下,它导致Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.


1
AsyncStorage for Android使用SQLite作为存储后端。虽然它有自己的大小限制,但Android系统也有两个已知的限制:总存储大小和每个条目的大小限制。
默认情况下,总存储大小限制为6 MB。您可以通过使用功能标志指定新的大小来增加此大小。
每个条目受WindowCursor的大小限制,WindowCursor是用于从SQLite读取数据的缓冲区。目前它的大小约为2 MB。这意味着一次只能读取的单个项目不能超过2 MB。AsyncStorage没有支持的解决方法。我们建议将数据保持在较低的水平,将其分割成多个条目,而不是一个大的条目。这就是multiGet和multiSet API的优势所在。

0

我发现另一个替代方案在这里提到

只需安装react-native-fs-store

npm i react-native-fs react-native-fs-store

react-native link react-native-fs

然后像这样使用:

import Store from "react-native-fs-store";
const AsyncStorage = new Store('store1');

它与AsyncStorage具有完全相同的API,因此无需进行任何代码更改

** 请注意,react-native-fs-storeAsyncStorage慢,因为每个操作都会同步到文件。因此,在读取/写入数据时可能会出现延迟(不响应屏幕)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接