热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SharedPreferences跨进程共享数据研究

最近因工作需求,需要Androidapp中的SharedPreferences的数据共享到其他进程,研究很两天,终于得到了解决方案,感动不已,分享一下。刚开始在网上找解决方案,似乎

最近因工作需求,需要Android app中的SharedPreferences的数据共享到其他进程,研究很两天,终于得到了解决方案,感动不已,分享一下。

刚开始在网上找解决方案,似乎有这个需求的人太少,Google了半天,只看到一个方案。

通过MODE_MULTI_PROCESS属性使用SharedPreferences就可以解决不同进程之间不能共享数据的问题了,但SQA总是反馈一些随机但出现频率比较大的bug,比如在使用过程中没有清除程序数据的前提下,会出现欢迎界面和操作指引,这是通过保存在SharedPreferences的标志来判断用户是否是第一次启动程序的,分析发现保存在SharedPreferences中的数据丢失了,但代码中并没有去清除这些数据,所以推测可能是不同进程同一时间对SharedPreferences操作导致的,经验证确实如此,去掉多进程就不会再出现这个问题了。

最后团队分析讨论抛弃了这个方案,转而考虑使用ContentProvider去实现跨进程共享。然而问题接踵而至,即绝大部分使用ContentProvider的情况都是结合了Sqlite。ContentProvider使用要重写几个方法。

《SharedPreferences跨进程共享数据研究》

特别是query方法,因为你要查询数据,那么基本上必须要重写它。这里就是做了一件事,把你的数据从某个地方取出来,通过Cursor抛到另一个进程。所以,Sqlite就很方便啊,它提供了许多的api最后都可以去到Cursor。然而,SharedPreferences并没有这么方便。于是要共享SharedPreferences,要怎么做呢?简单的思路就是读取里面的数据写到Cursor里面。因此我们要去自定义一个Cursor。

一、如何处理SharedPreferences的数据

我在这两天的研究中,尝试了几种自定义Cursor的办法,从最顶层的实现Cursor,到参考SqliteCursor之后,决定继承AbstractWindowedCursor这个类去自定义一个Cursor。接下来我们看下代码。

《SharedPreferences跨进程共享数据研究》

以上是必须要重写的方法,来分别介绍一下。

1、构造方法

public SharedPreferencesCursor(Context context, String sharedFileName, String[] keys) {
mSharedFileName = sharedFileName;
sharedPreferences = context.getSharedPreferences(sharedFileName,
Context.MODE_PRIVATE);
Set sets = sharedPreferences.getAll().keySet();
if (keys == null) {//如果没有指定要查询的key值列表,就查询所有
keys = new String[sets.size()];
int i = 0;
for (String key : sets) {
keys[i++] = key;
}
}
mColumns = keys;
}

此处第三个入参模拟数据库表中指定列的查询,对应你要查询的key。因为我们知道一个SharedPreferences里面有多个key,因此通过这种方式可以指定要查询的key。

2、游标移动操作

public boolean onMove(int oldPosition, int newPosition) {
if (mWindow == null
|| newPosition || newPosition >= (mWindow.getStartPosition() + mWindow
.getNumRows())) {
fillWindow(newPosition);
}
return true;
}

3、填充window

private void fillWindow(int requiredPos) {
clearOrCreateWindow(mSharedFileName);

try {
if (mCount == NO_COUNT) {
mCount = fillWindow(mWindow);
} else {
fillWindow(mWindow);
}
} catch (RuntimeException e) {
closeWindow();
throw e;
}
}


private int fillWindow(CursorWindow window) {
final int numColumns = getColumnCount();
window.clear();
window.setStartPosition(0);
window.setNumColumns(numColumns);
if (!window.allocRow()) {
return -1;
}
int i = 0;
for (String key : mColumns) {
boolean success = false;
final int type = getType(key);
switch (type) {
case Cursor.FIELD_TYPE_NULL:
success = window.putNull(0, i);
break;

case Cursor.FIELD_TYPE_INTEGER:
success = window
.putLong(sharedPreferences.getInt(key, 0), 0, i);
break;

case Cursor.FIELD_TYPE_FLOAT:
success = window.putDouble(sharedPreferences.getFloat(key, 0),
0, i);
break;

case Cursor.FIELD_TYPE_BLOB:
break;

default: // assume value is convertible to String
case Cursor.FIELD_TYPE_STRING: 
final String value = sharedPreferences.getString(key, “”);
success = value != null ? window.putString(value, 0, i)
: window.putNull(0, i);
break;
}
if (!success) {
window.freeLastRow();
}
i++;
}
return i;
}

这两步是实现数据共享的关键。什么是CursorWindow?当你了解了SqliteCursor的源码就会发现,我们通过Uri查询数据库所得到的数据集,保存在native层的CursorWindow中。CursorWindow的实质是共享内存的抽象,以实现跨进程数据共享。共享内存所采用的实现方式是文件映射。同理,我们通过Url获取SharedPreferences里面的数据,也要保存到CursorWindow中。

4、获取key对应的index

public int getColumnIndex(String columnName) {
if (mColumnNameMap == null) {
String[] columns = mColumns;
int columnCount = columns.length;
HashMap map = new HashMap(
columnCount, 1);
for (int i = 0; i map.put(columns[i], i);
}
mColumnNameMap = map;
}

Integer integer = mColumnNameMap.get(columnName);
if (integer != null) {
return integer.intValue();
}
return -1;
}

5、创建或者关闭window

private void clearOrCreateWindow(String name) {
if (mWindow == null) {
mWindow = new CursorWindow(name);
} else {
mWindow.clear();
}
}

private void closeWindow() {
if (mWindow != null) {
mWindow.close();
mWindow = null;
}
}

到此,我们自定义的Cursor就可以使用了。SharedPreferences是可以理解为键值对映射的Map,它的形式和数据库的表是不一样的,所以我把每个key看作是表的列,因此这样的“表”其实只有一行数据,永远只有一行数据。因此在取数据的时候,只需要Cursor.moveToFirst(),就可以取数据了。

二、如何通过ContentProvider共享数据

接下来我们要去自定义一个ContentProvider。

《SharedPreferences跨进程共享数据研究》

根据实际情况去重写CRUD方法。这里解释下查询方法,正如前面解释的SharedPreferencesCursor的构造函数一样,我们把用在数据库中指定列的projection入参作为指定key的入参。如果要实现数据同步更新,动态加载,需要对cursor设置notification的URI。

《SharedPreferences跨进程共享数据研究》

三、如何同步更新数据并动态加载

跨进程动态更新加载数据需要使用到很厉害的一个类,LoadManager。

简单介绍一下android的Loaders机制,Loaders,装载机,适用于Android3.0以及更高的版本,它提供了一套在UI的主线程中异步加载数据的框架。使用Loaders可以非常简单的在Activity或者Fragment中异步加载数据,一般适用于大量的数据查询,或者需要经常修改并及时展示的数据显示到UI上,这样可以避免查询数据的时候,造成UI主线程的卡顿。

Loaders有以下特点:

  • 可以适用于Activity和Fragment。
  • 可以提供异步的方式加载数据。
  • 监听数据源,当数据改变的时候,将新的数据发布到UI上。
  • Loaders使用Cursor加载数据,在更改Cursor的时候,会自动重新连接到最后配置的Cursor中读取数据,因此不需要重新查询数据。

具体的机制读者请自行查看相关的文档。这边要实现SharedPreferences数据变更动态更新,只需要简单的一行代码。
《SharedPreferences跨进程共享数据研究》
第一行代码是保存SharedPreferences数据,第二行代码就是获取ContentResolver并传入SYNC_SIGNAL_URI通知刷新。这个URI一定要和ContentProvider里面配置的n notificationUri是一致的。到此,基本上你就可以实现SharedPreferences跨进程共享数据了。

接下来你需要在跨进程获取数据的类里面去实现LoaderCallbacks。重写三个方法。

《SharedPreferences跨进程共享数据研究》


onCreateLoader  顾名思义,就是要创建CursorLoader,配置你的URI,要查询的条件。

onLoadFinished  方法,当第一次获取到cursor或者cursror数据源有变化的时候,就会回调该方法,在这里可以实现更新数据,动态加载。

onLoaderReset   主要是用的你的loader变化的时候。
好了,以上就是SharedPreferences跨进程共享数据的步骤。回顾起来,发现其实不难,但是这两天在研究的时候,网上没有对应的解决方案,没有入手点,只能类比数据库去研究,着实有点心塞,不过功能总算实现了,也是有点开心。

四、异常解决方案

1、SimpleCursorAdapter SqliteException no such column: _id 异常解决方案:

我们项目的数据库使用的是开源库Litepal,在共享数据库的时候遇到了上面这个异常。这个异常主要由于我们使用Litepal导致的。为什么是这样呢?因为Litepal默认主键名是id,而非_id,并且不支持修改。而SimpleCursorAdapter的父类CursorAdapter里面,有几处代码通过getColumnIndexOrThrow(“_id”)去获取主键的id值,所以导致二者不匹配。那么要么修改Litepal,要么修改CursorAdapter,供君自由选择。这边提供的方案是修改SimpleCursorAdapter相关的几个类,拷贝源码,就修改了两处,非常方便。

《SharedPreferences跨进程共享数据研究》

通过

《SharedPreferences跨进程共享数据研究》

这个this是指LoaderCallbacks回调接口,必须实现,并重写以下方法

《SharedPreferences跨进程共享数据研究》


推荐阅读
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • flowable工作流 流程变量_信也科技工作流平台的技术实践
    1背景随着公司业务发展及内部业务流程诉求的增长,目前信息化系统不能够很好满足期望,主要体现如下:目前OA流程引擎无法满足企业特定业务流程需求,且移动端体 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • ScrollView嵌套Collectionview无痕衔接四向滚动,支持自定义TitleView
    本文介绍了如何实现ScrollView嵌套Collectionview无痕衔接四向滚动,并支持自定义TitleView。通过使用MainScrollView作为最底层,headView作为上部分,TitleView作为中间部分,Collectionview作为下面部分,实现了滚动效果。同时还介绍了使用runtime拦截_notifyDidScroll方法来实现滚动代理的方法。具体实现代码可以在github地址中找到。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 如何在php中将mysql查询结果赋值给变量
    本文介绍了在php中将mysql查询结果赋值给变量的方法,包括从mysql表中查询count(学号)并赋值给一个变量,以及如何将sql中查询单条结果赋值给php页面的一个变量。同时还讨论了php调用mysql查询结果到变量的方法,并提供了示例代码。 ... [详细]
  • 在使用dedecms过程中,添加自定义字段变量很有用,但删除并不容易。本文介绍了两种常用的删除方法:执行SQL语句和手动SQL删除。 ... [详细]
author-avatar
mobiledu2502915347
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有