Android:使用ContentResolver时的SQLite事务

Android: SQLite transactions when using ContentResolver

目标:从XML数据刷新数据库

过程:

  • 开始交易
  • 从表中删除所有现有行
  • 对于已解析的XML的每个主要元素,将行插入到主表中并获得PK
  • 将主元素的每个子元素插入记录到第二个表中,以提供上一步中的FK
  • 提交交易

关于db操作的相当标准的东西。问题在于CRUD操作不是在ContentProvider内完成,而是使用ContentResolver进行,因此例如插入看起来像resolver.insert(CONTENT_URI, contentValues)。 ContentResolver API似乎没有任何与事务有关的内容,并且我不能使用bulkInsert,因为我要间歇性地插入2个表中(此外,我也希望事务中包含delete)。

我当时想通过使用registerContentObserver将自定义的ContentProvider注册为侦听器,但是由于ContentResolver#acquireProvider方法是隐藏的,我如何获得正确的引用?

我不走运吗?


我已经看到,在Google I / O应用程序的源代码中,它们会覆盖ContentProviderapplyBatch()方法并在其中使用事务。因此,创建一批ContentProviderOperation,然后调用getContentResolver().applyBatch(uri_authority, batch)

我正计划使用这种方法来查看其工作原理。我很好奇是否有人尝试过。


从Android 2.1开始,可以通过使用ContentProviderOperation相当干净地进行基于事务的多表插入,如kaciula所述。

构建ContentProviderOperation对象时,可以调用.withValueBackReference(fieldName,refNr)。当使用applyBatch应用该操作时,结果是insert()调用随附的ContentValues对象将注入一个整数。该整数将使用fieldName String进行键控,并且其值将从先前应用的ContentProviderOperation的ContentProviderResult中检索,该内容由refNr索引。

请参考下面的代码示例。在该示例中,在表1中插入一行,然后在将表2中插入该行时,将所得的ID(在本例中为" 1")用作值。为简便起见,ContentProvider未连接到数据库。在ContentProvider中,有一些适合添加事务处理的打印输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class BatchTestActivity extends Activity {
    /** Called when the activity is first created. */
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        ArrayList<ContentProviderOperation> list = new
            ArrayList<ContentProviderOperation>();

        list.add(ContentProviderOperation.
            newInsert(BatchContentProvider.FIRST_URI).build());
        ContentValues cv = new ContentValues();
        cv.put("name","second_name");
        cv.put("refId", 23);

        // In this example,"refId" in the contentValues will be overwritten by
        // the result from the first insert operation, indexed by 0
        list.add(ContentProviderOperation.
            newInsert(BatchContentProvider.SECOND_URI).
            withValues(cv).withValueBackReference("refId", 0).build());

        try {
            getContentResolver().applyBatch(
                BatchContentProvider.AUTHORITY, list);
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        }
    }
}

public class BatchContentProvider extends ContentProvider {

    private static final String SCHEME ="content://";
    public static final String AUTHORITY ="com.test.batch";

    public static final Uri FIRST_URI =
        Uri.parse(SCHEME + AUTHORITY +"/" +"table1");
    public static final Uri SECOND_URI =
        Uri.parse(SCHEME + AUTHORITY +"/" +"table2");


    public ContentProviderResult[] applyBatch(
        ArrayList<ContentProviderOperation> operations)
            throws OperationApplicationException {
        System.out.println("starting transaction");
        ContentProviderResult[] result;
        try {
            result = super.applyBatch(operations);
        } catch (OperationApplicationException e) {
            System.out.println("aborting transaction");
            throw e;
        }
        System.out.println("ending transaction");
        return result;
    }

    public Uri insert(Uri uri, ContentValues values) {
        // this printout will have a proper value when
        // the second operation is applied
        System.out.println("" + values);

        return ContentUris.withAppendedId(uri, 1);
    }

    // other overrides omitted for brevity
}

好吧-因此这并非毫无目的:我可以想到的唯一方法是将startTransaction和endTransaction编码为基于URL的查询请求。 ContentResolver.query(START_TRANSACTION, null, null, null, null)之类的东西。然后在ContentProvider#query中根据已注册的URL调用开始或结束事务


您可以获取内容提供者对象本身的实现(如果在同一过程中,则提示:您可以使用multiprocess =" true"或process =" http://developer.android.com/guide来控制提供者的过程/topics/manifest/provider-element.html),可以使用ContentProviderClient.getLocalContentProvider()强制转换为提供程序实现,该实现可以提供额外的功能,例如reset(),用于关闭和删除数据库,还可以返回自定义Transaction类具有save()和close()方法的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Transaction {
    protected Transaction (SQLiteDatabase database) {
        this.database = database;
        database.beginTransaction ();
    }

    public void save () {
        this.database.setTransactionSuccessful ();
    }

    public void close () {
        this.database.endTransaction ();
    }

    private SQLiteDatabase database;
}

public Transaction createTransaction () {
    return new Transaction (this.dbHelper.getWritableDatabase ());
}

然后:

1
2
ContentProviderClient client = getContentResolver ().acquireContentProviderClient (Contract.authorityLocal);
Transaction tx = ((LocalContentProvider) client.getLocalContentProvider ()).createTransaction ();