【问题标题】:Android: SQLite transactions when using ContentResolverAndroid:使用 ContentResolver 时的 SQLite 事务
【发布时间】:2011-01-14 22:28:50
【问题描述】:

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

过程:

  • 开始交易
  • 删除表格中的所有现有行
  • 将解析后的 XML 插入行的每个主要元素插入主表并获取 PK
  • 每个主元素的每个子元素插入记录到第二个表中,提供上一步的 FK
  • 提交事务

就数据库操作而言,相当标准的东西。问题是 CRUD 操作不是在 ContentProvider 内完成,而是使用 ContentResolver,因此插入示例看起来像 resolver.insert(CONTENT_URI, contentValues)。 ContentResolver API 似乎与事务无关,我不能使用bulkInsert,因为我间歇性地插入 2 个表(另外我也希望在事务中包含delete)。

我正在考虑使用registerContentObserver 将我的自定义ContentProvider 注册为侦听器,但由于ContentResolver#acquireProvider 方法被隐藏,我如何获得正确的参考?

我运气不好?

【问题讨论】:

标签: android sqlite transactions android-contentprovider android-contentresolver


【解决方案1】:

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

我打算使用这种方法来看看它是如何工作的。我很好奇是否有人尝试过。

【讨论】:

  • 我试过这种方法,效果很好。但是批处理中的每个 ContentProviderOperation 都是原子操作。我的意思是,没有办法正确处理主从关系的依赖操作,其中需要由第一个操作创建的身份密钥作为后续操作的输入。我之前问过这个问题,但得到的回复为零 (stackoverflow.com/questions/3224857/…)。
  • 我也试过了,我注意到性能提升超过 1000%。只需将 IOShed 项目中的代码复制到我的 Provider 即可。
  • 这个答案不正确。 applyBatch() 的默认 impl 仅迭代操作并单独应用它们。这仅提供了一种实现事务的方法,方法是在您的ContentProvider 中覆盖applyBatch()。它本身不提供事务行为。如果你不控制ContentProvider 的实现,那你就不走运了。
【解决方案2】:

从 Android 2.1 开始,可以使用 ContentProviderOperation 相当干净地执行基于事务的多表插入,正如 kaciula 所提到的。

在构建 ContentProviderOperation 对象时,可以调用 .withValueBackReference(fieldName, refNr)。当使用 applyBatch 应用操作时,结果是 insert() 调用提供的 ContentValues 对象将注入一个整数。该整数将使用 fieldName 字符串作为键,其值从先前应用的 ContentProviderOperation 的 ContentProviderResult 中检索,由 refNr 索引。

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

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
}

【讨论】:

    【解决方案3】:

    好吧 - 所以这不会漫无目的:我能想到的唯一方法是将 startTransaction 和 endTransaction 编码为基于 URL 的查询请求。像ContentResolver.query(START_TRANSACTION, null, null, null, null) 这样的东西。然后在ContentProvider#query基于注册的URL调用开始或结束事务

    【讨论】:

      【解决方案4】:

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

      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 ());
      }
      

      然后:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-06-24
        • 1970-01-01
        • 2015-11-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-01
        • 1970-01-01
        相关资源
        最近更新 更多