【问题标题】:SQLite DB with Content Provider race conditions具有内容提供程序竞争条件的 SQLite DB
【发布时间】:2016-04-09 15:31:59
【问题描述】:

我实现了使用 SQLiteDatabase 作为其支持数据源的 Content Provider。

通过调用getContentResolver().applyBatch(operations) 写入数据库的一个活动,应该是原子的。

protected void onPause() {
    new Thread(){
        @Override
        public void run() {
            ArrayList<ContentProviderOperation> ops = new ArrayList<>();
            ContentProviderOperation.Builder builder;
            for (Tag tag: mTopicAdapter.getTags()) {
                builder = ContentProviderOperation.newUpdate(QuizProvider.TAG_URI);
                builder.withValue(Tag.Table.SELECTED, tag.getSelectionStatus());
                builder.withSelection(Tag.Table._ID + " = " + tag.getId(), null);
                ops.add(builder.build());
            }
            try {
                ContentProviderResult[] res = getContentResolver().applyBatch(QuizProvider.AUTHORITY, ops);
                Timber.d("Update result: %d", res.length);
                getContentResolver().notifyChange(QuizProvider.TAG_URI, null);
                getContentResolver().notifyChange(QuizProvider.QUESTION_URI, null);
            } catch (RemoteException e) {
                e.printStackTrace();
            } catch (OperationApplicationException e) {
                e.printStackTrace();
            }
        }
    }.start();
    super.onPause();
}

在游标加载器的帮助下从数据库读取第二个活动,有时会获取旧数据(竞争条件)。

@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    Uri randQuestionUri = QuizProvider.QUESTION_URI
            .buildUpon()
            .appendPath("rand").appendPath(Integer.toString(QUIZ_SIZE))
            .build();
    return new CursorLoader(this, randQuestionUri, null, null, null, null);
}

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    if (DEBUG) Timber.d("load finished: %d", data.hashCode());
    mPagerAdapter.swapCursor(data);
}

@Override
public void onLoaderReset(Loader<Cursor> loader) {
    mPagerAdapter.swapCursor(null);
}

完整项目here.

日志输出:

NSA:QuizProvider:138: update db: selected=false _id = 4
NSA:QuizProvider:138: update db: selected=false _id = 5
NSA:QuizProvider:138: update db: selected=false _id = 26
NSA:QuizProvider:138: update db: selected=false _id = 19
NSA:QuizProvider:138: update db: selected=false _id = 28
NSA:QuizProvider:138: update db: selected=false _id = 10
NSA:QuizProvider:138: update db: selected=false _id = 12
NSA:QuizProvider:138: update db: selected=false _id = 15
NSA:QuizProvider:138: update db: selected=false _id = 18
NSA:QuizProvider:138: update db: selected=false _id = 25
NSA:QuizProvider:138: update db: selected=false _id = 16
NSA:QuizProvider:138: update db: selected=false _id = 17
NSA:QuizProvider:138: update db: selected=false _id = 8
NSA:QuizProvider:138: update db: selected=false _id = 3
NSA:QuizProvider:138: update db: selected=false _id = 20
NSA:QuizProvider:138: update db: selected=false _id = 29
NSA:QuizProvider:138: update db: selected=false _id = 24
NSA:QuizProvider:138: update db: selected=false _id = 23
NSA:QuizProvider:138: update db: selected=false _id = 30
NSA:QuestionsPagerAdapter:42: counter: 0
NSA:QuizProvider:103: query db: content://doit.study.droi    question/ran    280 null null
NSA:QuizProvider:138: update db: selected=false _id = 6
NSA:QuestionsPagerAdapter:42: counter: 0
NSA:QuestionsPagerAdapter:42: counter: 0
NSA:QuizProvider:138: update db: selected=false _id = 1
NSA:QuizProvider:138: update db: selected=false _id = 14
NSA:QuestionsPagerAdapter:42: counter: 0
NSA:QuizProvider:138: update db: selected=false _id = 7
NSA:QuizProvider:138: update db: selected=false _id = 27
NSA:QuestionsPagerAdapter:42: counter: 0
NSA:QuestionsActivity:84: load finished: 154982045
NSA:QuestionsPagerAdapter:66: swap cursor, cnt: 104
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuestionsPagerAdapter:35: instantiateItem, pos=0
NSA:QuestionsPagerAdapter:25: getItem, pos=0
NSA:QuestionsPagerAdapter:35: instantiateItem, pos=1
NSA:QuestionsPagerAdapter:25: getItem, pos=1
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuizProvider:138: update db: selected=false _id = 2
NSA:QuizProvider:138: update db: selected=false _id = 11
NSA:QuizProvider:138: update db: selected=false _id = 22
NSA:QuizProvider:138: update db: selected=false _id = 9
NSA:QuizProvider:138: update db: selected=false _id = 31
NSA:QuizProvider:138: update db: selected=false _id = 21
NSA:QuizProvider:138: update db: selected=false _id = 32
NSA:QuizProvider:138: update db: selected=false _id = 13
NSA:QuizProvider:138: update db: selected=false _id = 4
W/FragmentManager: moveToState: Fragment state for QuestionFragment{5c25e01 #0 id=0x7f0f00e5} not updated inline; expected state 3 found 2
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuestionsPagerAdapter:52: title pos: 0, questions: tags: [User Interfaces]
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuestionsPagerAdapter:52: title pos: 1, questions: tags: [User Interfaces]
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuizProvider:103: query db: content://doit.study.droi    tag null null
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuestionsPagerAdapter:42: counter: 104
NSA:QuizProvider:138: update db: selected=false _id = 5
NSA:QuizProvider:138: update db: selected=false _id = 26
NSA:QuizProvider:103: query db: content://doit.study.droi    tag null null
NSA:QuizProvider:138: update db: selected=false _id = 19
NSA:QuizProvider:138: update db: selected=false _id = 28
NSA:QuizProvider:138: update db: selected=false _id = 10
NSA:QuizProvider:138: update db: selected=false _id = 12
NSA:QuizProvider:138: update db: selected=false _id = 15
NSA:QuizProvider:138: update db: selected=false _id = 18
NSA:QuizProvider:138: update db: selected=false _id = 25
NSA:QuizProvider:138: update db: selected=false _id = 16
NSA:QuizProvider:138: update db: selected=false _id = 17
NSA:QuizProvider:138: update db: selected=false _id = 8
NSA:QuizProvider:138: update db: selected=false _id = 3
NSA:QuizProvider:138: update db: selected=false _id = 20
NSA:QuizProvider:138: update db: selected=false _id = 29
NSA:QuizProvider:138: update db: selected=false _id = 24
NSA:QuizProvider:138: update db: selected=false _id = 23
NSA:QuizProvider:138: update db: selected=false _id = 30
NSA:QuizProvider:138: update db: selected=false _id = 6
NSA:QuizProvider:138: update db: selected=false _id = 1
NSA:QuizProvider:138: update db: selected=false _id = 14
NSA:QuizProvider:138: update db: selected=false _id = 7
NSA:QuizProvider:138: update db: selected=false _id = 27
NSA:QuizProvider:138: update db: selected=false _id = 2
NSA:QuizProvider:138: update db: selected=false _id = 11
NSA:QuizProvider:138: update db: selected=false _id = 22
NSA:QuizProvider:138: update db: selected=false _id = 9
NSA:QuizProvider:138: update db: selected=false _id = 31
NSA:QuizProvider:138: update db: selected=false _id = 21
NSA:QuizProvider:138: update db: selected=false _id = 32
NSA:QuizProvider:138: update db: selected=false _id = 13
NSA:QuizProvider:103: query db: content://doit.study.droi    question/ran    280 null null
NSA:QuestionsActivity:84: load finished: 19434496
NSA:QuestionsPagerAdapter:66: swap cursor, cnt: 0
NSA:QuestionsPagerAdapter:42: counter: 0
NSA:QuestionsPagerAdapter:42: counter: 0

日志显示 applyBatch 未完成,Cursor Loader 获取部分修改的数据(游标计数器=104,应为 0 或 280)。

一些资源(抱歉,不能添加超过两个链接):

_http://developer.android.com/guide/topics/providers/content-provider-basics.html#Batch

_http://www.androiddesignpatterns.com/2012/10/sqlite-contentprovider-thread-safety.html

_http://stackoverflow.com/questions/8104832/sqlite-simultaneous-reading-and-writing

_http://www.grokkingandroid.com/better-performance-with-contentprovideroperation/

你有什么想法吗?

【问题讨论】:

  • 您的代码有两个问题:您面临的“竞争条件”是由于每次提交更改时使用新线程而不是使用队列(例如,具有默认执行程序的 AsyncTask)。您的代码也不会调用 ContentResolver#notifyChange,因此当发生更改时不会重新加载 CursorLoader。
  • 感谢您的回复。我在方法 onPause() 中离开活动一时提交了一次更改。添加了 notifyChange() (根据您的建议)。仍然无法获得 applyBatch(operations) 的原子含义。正如日志输出所示,它不是原子的。
  • 您应该在 ContentProvider 的方法中调用 notifyChange。您还应该同时在创建的 SQLiteCursor 上设置 observable Uri。通过研究现有 ContentProviders 的代码来学习正确的做事方式(我建议查看由 AnnotatedSQL 生成的代码)。并确保使用 AsyncTask 而不是 new Thread
  • 我明白了,您建议重用 AsyncTasks 的顺序执行顺序,但令我不安的是非原子 applyBatch。我使用事务和原始 java 线程创建了小测试,它按我的预期工作(操作是 mutually exclusive
  • 哦,对了,所以有一个 elepha…TL; DR:Base ContentProvider 完全与存储无关,事务的概念与实际使用的数据库引擎/存储 API 密切相关。您应该覆盖applyBatch 并自己用事务包装它。这就是为什么我建议您阅读一些 ContentProviders 的代码以了解一切应该如何完成的原因。

标签: android sqlite android-contentprovider


【解决方案1】:

这里基本上需要考虑三件事:

  • 将这些 notifyChange() 调用移至您的 ContentProvider。这样您就可以确保在您进行更改时调用它们。或者换句话说:您可能会忘记在其他地方这样做。客户不应为此负责——它不属于这里。
  • 确保您的applyBatch() 方法实际使用事务。只有这样您才能获得所需的性能优势,然后才能以您需要的方式使用锁。
  • 确保在applyBatch() 运行时,不会发出任何通知。否则你的 Loader 会被太频繁地调用。你肯定想避免这种情况。

您可以查看我的cpsample project,了解遵守这些规则的内容提供商。

【讨论】:

  • 没问题。只要记得在可以的时候回来:-)
猜你喜欢
  • 2023-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多