Android:扩展用户的通讯录。性能 ContentProvider vs Sqlite vs 内存中的列表
Posted
技术标签:
【中文标题】Android:扩展用户的通讯录。性能 ContentProvider vs Sqlite vs 内存中的列表【英文标题】:Android: Extending user's contact book. Performance ContentProvider vs Sqlite vs List in memory 【发布时间】:2013-01-10 20:23:21 【问题描述】:我和我的 android 团队遇到了问题。我们有一个应用程序可以显示用户的通讯录,以及扩展信息。
当前设置
我们的应用会读取 Android 操作系统的联系人提供程序。将此信息发送到我们的服务器,该服务器为我们计算几个必要的字段。我们的应用程序稍后会获取此信息,并将此信息保存在 SQLite 数据库中。我们最终在数据库中得到的是两个表。一个包含所有数字和服务器为我们计算的所有额外信息。另一张表包含所有联系人(一个联系人可以有多个号码)。此联系人表仅为性能而创建;当为用户显示通讯录时,我们可以在 CursorAdapter 中使用 Cursor 选择此 Contacts 表中的所有行。 因此,当向用户展示通讯录时,我们只需要从我们自己的 SQLite 数据库和一张表中读取(例如,没有 JOIN)。
主要问题
正在进行大量同步。由于数据是重复的,我们需要检查添加/更改/删除,并且需要同步所有的时间。此外,当我们现在要更改表示层中的特定内容时,我们需要更改联系人表以包含此特定信息。
我们的优先级
1st:向用户展示通讯录时的表现。
第二:代码可维护性。
因此,不要评论“不要重复数据——这是所有问题的根源”。对我们来说,用户没有性能问题比我们作为开发人员必须花费一些额外的时间编写好的同步算法更重要。
解决方案?
我不知道为什么,但我一直认为 CursorAdapter(从一个表中读取所有行)比带有对象列表(保存在内存中)的 ArrayAdapter 性能要好得多。有谁知道这是不是真的?因为一个至少可以帮助我们一半的解决方案是,在启动时,加入 Contacts Provider(本机通讯录)和我们的扩展信息,将其保存在内存中的 List 中,并使用 ArrayAdapter 呈现。
创建自己的内容提供者?我对创建自己的内容提供商知之甚少。任何人都试图创建一个内容提供者来扩展本地通讯录的信息并加入这些信息。也许通过这个接口的实现:ContactsContract.DataColumnsWithJoins?有人尝试过类似的东西吗?在 CursorAdapter 中呈现此信息时的性能如何?
请询问我可能忘记的更多信息,我会更新问题!
非常感谢您提供所有有用的提示和解决方案!
【问题讨论】:
一个设计良好的正常形式的数据库(例如,bkent.net/Doc/simple5.htm)不仅有助于维护您的代码,还可以显着加快处理速度。也就是说,如果明智地使用索引。此外,您不必花费计算时间来同步数据(因为您必须为此类操作锁定数据库以避免不一致的数据)。它还会炸毁您的数据库,在移动环境中您应该不惜一切代价避免这种情况。在我的实际工作中,我找不到任何不使用规范化数据库设计(没有重复数据)的理由。 现在您一般都在谈论数据库,是的,我同意。然而,由于 Android 使用了他们称之为 Cursors 的东西。这些游标是您在发出查询时返回的对象。但它们没有填充数据。当游标直接链接到数据库时,您向前一步并一次获取一行 - 这有助于内存消耗。但是,它们在使用连接时表现非常糟糕。 据我所知,游标缓冲从查询中检索到的数据。否则就没有多大意义了。因此,连接性能应该是 SQLite DB 的连接性能。 我认为(但不确定)你错了。例如,您不能在打开游标时关闭数据库。 【参考方案1】:我已经得出结论(在我的应用程序 JReader 上工作,它非常依赖于快速数据库操作)Android 中的 SQLite 与其他平台一样快,但存在一些 Android 特定问题。 关于数据库性能和您提出的问题的一些建议:
如果您不打算通过它们共享您的数据,那么内容提供者大多是无用的。但它们至少提供 2 个优势。首先,您会收到数据更改通知,并且光标会自动更新,第二个也是重要的:CursorLoaders 需要一个内容提供程序,如果性能对您很重要,我强烈建议您使用它们来加载您的光标; 访问集合比访问数据库要快得多,但这是一项双重工作,因为无论如何您都必须持久化数据,而且数据库访问速度非常快,即使是为超快速滚动列表获取数据,尤其是从单个列表中获取数据时也是如此表,应该没问题; 正确设计您的数据库(使用 JOINS、索引等):) 但不要在 CURSOR QUERIES 中使用 JOIN QUERIES!我在多个 Android 平台(包括 4.0+)上遇到了很多性能问题,但并非总是如此。我访问联表的方式是先获取外键,然后查询子表。根据我的经验,我遇到过数据库性能很差的情况,但最后我总是设法调整代码,结果将获得 10-100 倍的性能提升。所以继续分析你的数据库代码,你肯定会达到预期的性能。
【讨论】:
我完全同意丹的第一条评论,但不同意第二条 您的第三个项目符号正是我的问题所在。如何在不使用游标查询中的连接的情况下正确设计数据库(使用连接索引等)?您的意思是不使用 CursorAdapter 并在启动时使用连接查询数据库并将结果保存在内存中的列表中? "我访问联合表的方式是先获取外键,然后查询子表。"你是什么意思?首先使用一些外键 X 查询表 A,然后为每个 X 查询表 B?这样您就可以进行 X + 1 个查询的大小?或者你是什么意思? 这个大多数时候都非常慢:SELECT * FROM table_b INNER JOIN .... WHERE table_a.somevalue == 1 这个总是很快:SELECT * FROM table_b WHERE table_b.foreign_key IN (SELECT table_a.id FROM table_a WHERE table_a.somevalue == 1) 同样适用于更新、删除等,我使用:DELETE ... WHERE id IN ();【参考方案2】:Dan 的第二条评论是正确的 .. Android 使用屏幕下方的光标窗口来缓存来自光标的数据(大约 1M)。请参阅Android docs on Cursor Window,这就是光标适配器更快的方式。
如果您不喜欢单独加入两个表,您可以考虑更快的 CursorJoiner,这可以转到您的自定义 contentProvider ,其中一个提供者指向 Contacts 并返回一个 Cursor ,类似地第二个指向您的自己的表并返回一个游标。 cursorjoiner 可以连接这两个游标。
(虽然过程很复杂)
【讨论】:
如果是这样,那么为什么 cursor.getCount() 必须线性地遍历整个结果集呢?我的意思是如果结果集少于一百万,那么大小应该是可用的,而不必一个一个地计算它们。以上是关于Android:扩展用户的通讯录。性能 ContentProvider vs Sqlite vs 内存中的列表的主要内容,如果未能解决你的问题,请参考以下文章