Tray 轻量级数据存储 sharepreference的替代实现方案

前言:

使用SharePreferences是不支持在多个进程中操作数据的(不同进程之间的存取和读取,不同进程同时存储相同的数据都会出现问题),所以我们需要自己去实现跨进程的数据存储,但是很多人会指出,我们在创建SharePreferences的时候,官方明明提供了多线程操作的MODE_MULTI_PROCESS,难道不支持么?带着这个疑问我把官方对这部分的介绍贴出来:

int MODE_MULTI_PROCESS
This constant was deprecated in API level 23.
MODE_MULTI_PROCESS does not work reliably in some versions of Android, and furthermore does not provide any mechanism for reconciling concurrent modifications across processes. Applications should not attempt to use it. Instead, they should use an explicit cross-process data management approach such as ContentProvider.

上面已经明确的指出这个常数在API23的时候已经被弃用,并且推荐使用ContentProvider去实现此功能。
Tray是SharePrefrence的一个替代实现方案,提供了更加简单的方法去存储和读取,以及进程间数据的存储和读取。Tray明确的指出,跨进程的数据存储特性是基于ContentProvider的多线程数据存储方案。

使用说明:

一、在gradle中配置:

compile 'net.grandcentrix.tray:tray:0.12.0'

二、使用Tray存储和读取数据:

// 存储数据
AppPreferences appPreferences = new AppPreferences(this);
appPreferences.put(TRAY_KEY, "i am trayPreference param");
//读取数据
String param = appPreferences.getString(TRAY_KEY, "default");

从上面的使用中可以看到使用Tray后不需要再像SharePrefrence一样去获取Edior和apply(),一句话直接搞定,写法上相对简单好多。

三、如果你现在项目中接入的是SharePerefrence,但是突然有个要在不同进程中存储数据的功能需求,要使用Tray,那之前存储在sharepreference中的数据如何处理呢?

Tray还提供了一个功能就是合并SharePreference中的数据到Tray中。
使用方式,我们需要自己创建一个类,将其命名为ImportPreference,继承TrayPreferences,TrayPreferences提供了migrate()方法,
下面我们具体来看一下migrate()方法,这个方法主要是将SharePerference中的数据移动到Tray中存储,然后我们看一下方法中的参数值:migrate(Migration… migrations) 参数是Migration的实例值,那我们需要传入的就是Migration或其子类的实例,文档推荐使用SharedPreferencesImport:

SharedPreferencesImport importPref =new SharedPreferencesImport(getContext(), SharepreferenceName, prefKey, prefKey);
importPreference.migrate(importPref);

参数getContext():上下文对象
参数SharepreferenceName:SharePreference的name名称
参数prefKey:要移动的对象在SharePreference中的key名
参数prefKey:要移动的对象在TrayPreference中的key名

获取到SharedPreferencesImport的实例之后,使用我们自己创建的TrayPreferences的子类来调用migrate()方法,传入SharedPreferencesImport的实例值。

从Tray中取出被移动的数据:
这样我们就把SharePerefrence中的数据移动到Tray中,下次获取此数据的时候可以使用:

String param = appPreferences.getString(trayKey, “default”);

四、现在具体看一下Tray是如何对数据进行存储操作的,竟然so nb

项目中调用put方法存储value:AppPreferences.put(“key”,value) ,跟踪put进去,到Preferences类,我们看看处理的代码

@Override
public boolean put(@NonNull final String key, final float value) {
        if (!isVersionChangeChecked()) {
            return false;
        }
        v("put '" + key + "=" + value + "' into " + this);
        return putData(key, value);
}
private boolean putData(String key, Object value) {
        if (TextUtils.isEmpty(key)) {
            throw new IllegalArgumentException("Preference key value cannot be empty.");
        }
        return getStorage().put(key, value);
    }

那么这个getStorage()到底是什么? 一步步查找,最终发现是ContentProviderStorage这个类,这个类中的put方法又做了什么操作呢?

@Override
public boolean put(@NonNull final String key, @Nullable final String migrationKey,
            @Nullable final Object data) {
        if (getType() == Type.UNDEFINED) {
            throw new TrayRuntimeException(
                    "writing data into a storage with type UNDEFINED is forbidden. Only Read and delete is allowed.");
        }
final String value = data == null ? null : String.valueOf(data);
final Uri uri = mTrayUri.builder()
                .setType(getType())
                .setModule(getModuleName())
                .setKey(key)
                .build();
        return mProviderHelper.persist(uri, value, migrationKey);
    }

可以看到,重点代码是获取了一个uri,然后通过provuderHelper调用persist方法将uri和存储值value和替代key(migrationKey应该就是在将数据移动存储的情况下才会有值)。

public boolean persist(@NonNull final Uri uri, @Nullable String value,
            @Nullable final String previousKey) {
        ContentValues values = new ContentValues();
        values.put(TrayContract.Preferences.Columns.VALUE, value);
        values.put(TrayContract.Preferences.Columns.MIGRATED_KEY, previousKey);
        try {
            return mContext.getContentResolver().insert(uri, values) != null;
        } catch (Throwable e) {
            e.printStackTrace();
            return false;
        }
    }

重点在这里,Tray存储数据的时候果然是通过ContentProvider操作,机智啊。
再来看看数据读取时最后是如何操作的(TrayProviderHelper中):

@NonNull
public List<TrayItem> queryProvider(@NonNull final Uri uri) throws TrayException {
        final Cursor cursor;
        try {
            cursor = mContext.getContentResolver().query(uri, null, null, null, null);
        } catch (Throwable e) {
            throw new TrayException("Hard error accessing the ContentProvider", e);
        }

        // Return Preference if found
        if (cursor == null) {
            // When running in here, please check if your ContentProvider has the correct authority
            throw new TrayException("could not access stored data with uri " + uri);
        }
        final ArrayList<TrayItem> list = new ArrayList<>();
        for (boolean hasItem = cursor.moveToFirst(); hasItem; hasItem = cursor.moveToNext()) {
            final TrayItem trayItem = cursorToTrayItem(cursor);
            list.add(trayItem);
        }
        cursor.close();
        return list;
    }

在ContentProvider中取出cursor之后放到ArrayList集合中,然后如何处理这个集合呢?在ContentProviderStorage类中:

@Override
@Nullable
public TrayItem get(@NonNull final String key) {
        final Uri uri = mTrayUri.builder()
                .setType(getType())
                .setModule(getModuleName())
                .setKey(key)
                .build();
        final List<TrayItem> prefs = mProviderHelper.queryProviderSafe(uri);
        final int size = prefs.size();
        if (size > 1) {
            TrayLog.w("found more than one item for key '" + key
                    + "' in module " + getModuleName() + ". "
                    + "This can be caused by using the same name for a device and user specific preference.");
            for (int i = 0; i < prefs.size(); i++) {
                final TrayItem pref = prefs.get(i);
                TrayLog.d("item #" + i + " " + pref);
            }
        }
        return size > 0 ? prefs.get(0) : null;
    }

最后返回的是集合的第1个Item,这里的每个item是Tray自定义的TrayItem类,那有是如何处理TrayItem类的呢?我们拿getString为例:

@Override
public String getString(@NonNull final String key) throws ItemNotFoundException {
    final TrayItem pref = getPref(key);
    if (pref == null) {
        throw new ItemNotFoundException("Value for Key <%s> not found", key);
    }
    return pref.value();
}

bingo ,取到了值。
到这里Tray使用ContenProvider的存储机制核心代码差不多就这些,能力有限,欢迎补充。

参考链接:https://github.com/grandcentrix/tray

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值