扩展 ContentProvider 行为不正常

发布于 2024-09-15 15:09:13 字数 8554 浏览 2 评论 0原文

我正在尝试实现一个包含 SQLite 数据库的 ContentProvider。

我按照此处的教程构建了我的 ContentProvider: 教程

我想测试一下我所拥有的;所以我尝试实例化我的新 ContentProvider,从查询处理程序检索 Cursor,并将其附加到我的 CursorAdapter。目前,我正在 Activity 的 onCreate 中执行此操作(我知道这是不好的做法,我只是在测试,我最终会将其移动到服务中)。

Uri uri = Uri.parse("content://com.test.db.providers.Messages/messages");
String s[] = {"_id", "delivery_id", "user_id", "created_on", "subject", "summary", "messagetext", "read", "status"};
MessagesProvider p = new MessagesProvider();
if (p.open()) {
   Cursor messages = p.query(uri, s, null, null, null);
   startManagingCursor(messages);
}

当我启动应用程序时,扩展 ContentProvider 的 onCreate 方法将被执行。创建数据库辅助对象,创建数据库,然后 onCreate 方法返回 true。但是,当我尝试使用 ContentProvider(使用上面的代码)时,在 open() 方法中创建数据库帮助程序对象,但 getWritableDatabase() 返回 null。另外,当我调用 open() 时,对 getContext() 的引用为 null。

注意:其他一切似乎都工作正常。当我调用查询时,它会命中我的查询处理程序,识别 Uri 并尝试运行我的查询代码(这显然会崩溃,因为数据库对象为空)。

这是我的扩展 ContentProvider 和数据库助手:

package com.test.db.providers;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

import com.test.db.DbDefinitions;
import com.test.db.DbHelper;

public class MessagesProvider extends ContentProvider {

 private DbHelper mDbHelper;
    private SQLiteDatabase mDb;
    private static final UriMatcher sUriMatcher;

    private static final String PROVIDER_NAME = "com.test.db.providers.Messages";
    private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/messages");

 public static final String id = "_id";
 public static final String delivery_id = "delivery_id";
 public static final String user_id = "user_id";
 public static final String created_on = "created_on";
 public static final String subject = "subject";
 public static final String summary = "summary";
 public static final String messagetext = "messagetext";
 public static final String status = "status";

    private static final int MESSAGES = 1;
    private static final int MESSAGES_ID = 2;

    static {
     sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
     sUriMatcher.addURI(PROVIDER_NAME, "messages", MESSAGES);
     sUriMatcher.addURI(PROVIDER_NAME, "messages/#", MESSAGES_ID);
    }

    public boolean open() {
        mDbHelper = new DbHelper(getContext());
        mDb = mDbHelper.getWritableDatabase();
        return (mDb == null) ? false : true;
    }
    public void close() {
     mDbHelper.close();
    }


    @Override
    public boolean onCreate () {
     mDbHelper = new DbHelper(getContext());
     mDb = mDbHelper.getWritableDatabase();
     return (mDb == null) ? false : true;
    }

    @Override
    public String getType (Uri uri) {
     switch (sUriMatcher.match(uri)) {
      case MESSAGES:
       return "vnd.android.cursor.dir/com.test.messages";
      case MESSAGES_ID:
       return "vnd.android.cursor.item/com.test.messages";
      default:
       throw new IllegalArgumentException("Unknown URI " + uri);
     }
    }

    @Override
    public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
     switch (sUriMatcher.match(uri)) {
      case MESSAGES:
       return queryMessages(uri, projection, selection, selectionArgs, sortOrder);
      default:
       throw new IllegalArgumentException("Unknown Uri " + uri);
     }
    }

    @Override
    public Uri insert (Uri uri, ContentValues initialValues) {
     switch (sUriMatcher.match(uri)) {
   case MESSAGES:
    return insertMessages(uri, initialValues);
   default:
    throw new IllegalArgumentException("Unknown URI " + uri);
     }
    }

    @Override
    public int update (Uri uri, ContentValues values, String selection, String[] selectionArgs) {
     switch (sUriMatcher.match(uri)) {
      case MESSAGES:
       return updateMessages(uri, values, selection, selectionArgs);
      default:
       throw new IllegalArgumentException("Unknown URI " + uri);
     }
    }

    @Override
    public int delete (Uri uri, String selection, String[] selectionArgs) {
     switch (sUriMatcher.match(uri)) {
   case MESSAGES:
    return deleteMessages(uri, selection, selectionArgs);
   default:
    throw new IllegalArgumentException("Unknown URI " + uri);
     }
    }


    /*
     * Messages
     */
    private Cursor queryMessages(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
     Cursor c = mDb.query(DbDefinitions.TABLE_MESSAGES, projection, selection, selectionArgs, null, null, sortOrder);
     if (c != null) {
      c.moveToFirst();
     }
     return c;
    }

    private Uri insertMessages(Uri uri, ContentValues initialValues) {
  ContentValues values;
  if (initialValues != null)
   values = new ContentValues(initialValues);
  else
   values = new ContentValues();
  long rowId = mDb.insert(DbDefinitions.TABLE_MESSAGES, summary, values);
  if (rowId > 0) {
   Uri messageUri = ContentUris.withAppendedId(CONTENT_URI, rowId);
   getContext().getContentResolver().notifyChange(messageUri, null);
   return messageUri;
  }
  throw new SQLException("Failed to insert new message " + uri);
    }

    private int updateMessages(Uri uri, ContentValues values, String where, String[] whereArgs) {
     int result = mDb.update(DbDefinitions.TABLE_MESSAGES, values, where, whereArgs);
     getContext().getContentResolver().notifyChange(uri, null);
     return result;
    }

    public int deleteMessages(Uri uri, String where, String[] whereArgs) {
     // TODO flag message as deleted
     return 0;
    }
}



package com.test.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DbHelper extends SQLiteOpenHelper {

 public DbHelper(Context context) {
  super(context, DbDefinitions.DATABASE_NAME, null, DbDefinitions.DATABASE_VERSION);
 }

 @Override
    public void onCreate(SQLiteDatabase db) {
     db.execSQL(DbDefinitions.DB_CREATE);
     db.execSQL(DbDefinitions.DB_TEST_DATA);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
     // TODO run upgrade string
     db.execSQL("DROP TABLE IF EXISTS " + DbDefinitions.TABLE_MESSAGES);
     onCreate(db);
    }
}

我想知道我是否应该以某种方式引用启动应用程序时创建的 MessagesProvider 实例,而不是声明一个新实例 (p) 并使用它?

我将 Activity 中的 onCreate 代码更新为以下内容,但 ManagedQuery 返回 null:

Uri uri = Uri.parse("content://com.test.db.providers.Messages/messages");
String s[] = {"_id", "delivery_id", "user_id", "created_on", "subject", "summary", "messagetext", "read", "status"};

Cursor messages = managedQuery(uri, s, null, null, null);
if (messages != null)
    startManagingCursor(messages);

ExampleCursorAdapter msg = 
    new ExampleCursorAdapter(this, messages);
setListAdapter(msg);

I'm trying to implement a ContentProvider wrapped around an SQLite database.

I've followed the tutorial here in building my ContentProvider:
tutorial

I want to test what I have; so I'm attempting to instantiate my new ContentProvider, retrieve a Cursor from the query handler, and attach it to my CursorAdapter. Currently, I'm doing this in the onCreate of my Activity (I know this is bad practice, I'm just testing, I will move it to a service eventually).

Uri uri = Uri.parse("content://com.test.db.providers.Messages/messages");
String s[] = {"_id", "delivery_id", "user_id", "created_on", "subject", "summary", "messagetext", "read", "status"};
MessagesProvider p = new MessagesProvider();
if (p.open()) {
   Cursor messages = p.query(uri, s, null, null, null);
   startManagingCursor(messages);
}

When I launch my application, the onCreate method of my extended ContentProvider gets executed. The database helper object gets created, the database gets created, and the onCreate method returns true. However, when I try to use my ContentProvider (with the code above), in the open() method the database helper object gets created, but getWritableDatabase() returns null. Also, when I call open(), the reference to getContext() is null.

Note: everything else seems to be working fine. When I call query, it hits my query handler, recognizes the Uri and attempts to run my query code (which obviously blows up because the database object is null).

Here are my extended ContentProvider and database helper:

package com.test.db.providers;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

import com.test.db.DbDefinitions;
import com.test.db.DbHelper;

public class MessagesProvider extends ContentProvider {

 private DbHelper mDbHelper;
    private SQLiteDatabase mDb;
    private static final UriMatcher sUriMatcher;

    private static final String PROVIDER_NAME = "com.test.db.providers.Messages";
    private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/messages");

 public static final String id = "_id";
 public static final String delivery_id = "delivery_id";
 public static final String user_id = "user_id";
 public static final String created_on = "created_on";
 public static final String subject = "subject";
 public static final String summary = "summary";
 public static final String messagetext = "messagetext";
 public static final String status = "status";

    private static final int MESSAGES = 1;
    private static final int MESSAGES_ID = 2;

    static {
     sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
     sUriMatcher.addURI(PROVIDER_NAME, "messages", MESSAGES);
     sUriMatcher.addURI(PROVIDER_NAME, "messages/#", MESSAGES_ID);
    }

    public boolean open() {
        mDbHelper = new DbHelper(getContext());
        mDb = mDbHelper.getWritableDatabase();
        return (mDb == null) ? false : true;
    }
    public void close() {
     mDbHelper.close();
    }


    @Override
    public boolean onCreate () {
     mDbHelper = new DbHelper(getContext());
     mDb = mDbHelper.getWritableDatabase();
     return (mDb == null) ? false : true;
    }

    @Override
    public String getType (Uri uri) {
     switch (sUriMatcher.match(uri)) {
      case MESSAGES:
       return "vnd.android.cursor.dir/com.test.messages";
      case MESSAGES_ID:
       return "vnd.android.cursor.item/com.test.messages";
      default:
       throw new IllegalArgumentException("Unknown URI " + uri);
     }
    }

    @Override
    public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
     switch (sUriMatcher.match(uri)) {
      case MESSAGES:
       return queryMessages(uri, projection, selection, selectionArgs, sortOrder);
      default:
       throw new IllegalArgumentException("Unknown Uri " + uri);
     }
    }

    @Override
    public Uri insert (Uri uri, ContentValues initialValues) {
     switch (sUriMatcher.match(uri)) {
   case MESSAGES:
    return insertMessages(uri, initialValues);
   default:
    throw new IllegalArgumentException("Unknown URI " + uri);
     }
    }

    @Override
    public int update (Uri uri, ContentValues values, String selection, String[] selectionArgs) {
     switch (sUriMatcher.match(uri)) {
      case MESSAGES:
       return updateMessages(uri, values, selection, selectionArgs);
      default:
       throw new IllegalArgumentException("Unknown URI " + uri);
     }
    }

    @Override
    public int delete (Uri uri, String selection, String[] selectionArgs) {
     switch (sUriMatcher.match(uri)) {
   case MESSAGES:
    return deleteMessages(uri, selection, selectionArgs);
   default:
    throw new IllegalArgumentException("Unknown URI " + uri);
     }
    }


    /*
     * Messages
     */
    private Cursor queryMessages(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
     Cursor c = mDb.query(DbDefinitions.TABLE_MESSAGES, projection, selection, selectionArgs, null, null, sortOrder);
     if (c != null) {
      c.moveToFirst();
     }
     return c;
    }

    private Uri insertMessages(Uri uri, ContentValues initialValues) {
  ContentValues values;
  if (initialValues != null)
   values = new ContentValues(initialValues);
  else
   values = new ContentValues();
  long rowId = mDb.insert(DbDefinitions.TABLE_MESSAGES, summary, values);
  if (rowId > 0) {
   Uri messageUri = ContentUris.withAppendedId(CONTENT_URI, rowId);
   getContext().getContentResolver().notifyChange(messageUri, null);
   return messageUri;
  }
  throw new SQLException("Failed to insert new message " + uri);
    }

    private int updateMessages(Uri uri, ContentValues values, String where, String[] whereArgs) {
     int result = mDb.update(DbDefinitions.TABLE_MESSAGES, values, where, whereArgs);
     getContext().getContentResolver().notifyChange(uri, null);
     return result;
    }

    public int deleteMessages(Uri uri, String where, String[] whereArgs) {
     // TODO flag message as deleted
     return 0;
    }
}



package com.test.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DbHelper extends SQLiteOpenHelper {

 public DbHelper(Context context) {
  super(context, DbDefinitions.DATABASE_NAME, null, DbDefinitions.DATABASE_VERSION);
 }

 @Override
    public void onCreate(SQLiteDatabase db) {
     db.execSQL(DbDefinitions.DB_CREATE);
     db.execSQL(DbDefinitions.DB_TEST_DATA);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
     // TODO run upgrade string
     db.execSQL("DROP TABLE IF EXISTS " + DbDefinitions.TABLE_MESSAGES);
     onCreate(db);
    }
}

I'm wondering if I should somehow be referencing whatever instance of MessagesProvider was created when I launched the application, instead of declaring a new one (p) and using it?

I updated the onCreate code in my Activity to the following, but managedQuery returned null:

Uri uri = Uri.parse("content://com.test.db.providers.Messages/messages");
String s[] = {"_id", "delivery_id", "user_id", "created_on", "subject", "summary", "messagetext", "read", "status"};

Cursor messages = managedQuery(uri, s, null, null, null);
if (messages != null)
    startManagingCursor(messages);

ExampleCursorAdapter msg = 
    new ExampleCursorAdapter(this, messages);
setListAdapter(msg);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

锦欢 2024-09-22 15:09:13

在编写其他内容之前:请查看记事本示例 Android 开发者网站。在我看来,这是了解 ContentProviders 是如何实现的一个很好的例子。

研究完该示例后,我将坚持他们在那里编写 ContentProvider 的方式,以及他们如何从 UI 调用它来检索数据。

例如,您不需要“open()”方法。你可以在你的 Activity 上做的就是

@Override
public void onCreate(Bundle savedInstanceState){
   ...

   if (getIntent().getData() == null) {
       getIntent().setData(MyMetaData.CONTENT_URI);
   }

   Cursor cursor = managedQuery(getIntent().getData(), null, null, null, null);

   //create an appropriate adapter and bind it to the UI
   ...
}

自动调用能够处理给定内容 uri 的 ContentProvider,前提是你在 manifest.xml 文件中注册了它,例如

<provider android:name=".provider.MyContentProvider" android:authorities="com.mycompany.contentprovider.MyContentProvider" />

//Just as a sidenote
由于我更喜欢​​进行自动化单元测试,并且您提到您只想测试您的 ContentProvider 是否实际工作,您也可以考虑针对它编写一个单元测试,更具体地说是 ProviderTestCase2:

public class MessagesProviderTest extends ProviderTestCase2<MessagesProvider> {
    private MockContentResolver mockResolver;

    public MessagesProviderTest() {
        super(MessagesProvider.class, MessagesMetaData.AUTHORITY);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mockResolver = getMockContentResolver();
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
        mockResolver = null;

            //clean the old db
        getContext().getDatabasePath("test.messages.db").delete();
    }

    public void testRetrieveMessages() {
        //TODO insert some using ContentValues

        //try to retrieve them
        Cursor readMessagesCursor = mockResolver.query(....);
        assertTrue("The cursor should contain some entries", readMessagesCursor.moveToFirst());


           ...
    }
 }

这只是作为旁注,但我确实推荐这样做,因为通过这种方式,您可以

  1. 测试您的 ContentProvider 是否工作,而无需实现一些虚拟的 Activity、Service 或其他内容
  2. 。您可以在修改 ContentProvider 实现后随时重新运行测试,看看是否没有破坏任何内容。

Before writing anything else: take a look at the Notepad example from the Android Developer website. In my opinion it is a great example for seing how ContentProviders are being implemented.

After having studied that example, I would stick on the way they write ContentProviders there and also on how they invoke it from the UI for retrieving data.

For instance you won't need the "open()" method. What you can do on your Activity is simply

@Override
public void onCreate(Bundle savedInstanceState){
   ...

   if (getIntent().getData() == null) {
       getIntent().setData(MyMetaData.CONTENT_URI);
   }

   Cursor cursor = managedQuery(getIntent().getData(), null, null, null, null);

   //create an appropriate adapter and bind it to the UI
   ...
}

this will automatically call the ContentProvider that is able to handle the given content uri, given you registered it in the manifest.xml file like

<provider android:name=".provider.MyContentProvider" android:authorities="com.mycompany.contentprovider.MyContentProvider" />

//Just as a sidenote
Since I prefer having automated unit tests in place and you mention you'd just like to test whether your ContentProvider actually works, you may also consider to write a unit test against it, more specifically a ProviderTestCase2:

public class MessagesProviderTest extends ProviderTestCase2<MessagesProvider> {
    private MockContentResolver mockResolver;

    public MessagesProviderTest() {
        super(MessagesProvider.class, MessagesMetaData.AUTHORITY);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mockResolver = getMockContentResolver();
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
        mockResolver = null;

            //clean the old db
        getContext().getDatabasePath("test.messages.db").delete();
    }

    public void testRetrieveMessages() {
        //TODO insert some using ContentValues

        //try to retrieve them
        Cursor readMessagesCursor = mockResolver.query(....);
        assertTrue("The cursor should contain some entries", readMessagesCursor.moveToFirst());


           ...
    }
 }

This just as a sidenote, but I do really recommend this, because in this way you can

  1. Test whether your ContentProvider works without having to implement some dummy Activity, Service or whatever
  2. You can always rerun the test after you modify your ContentProvider implementation and see whether you didn't break anything.
待天淡蓝洁白时 2024-09-22 15:09:13

是的,我在标签内的标签之前放置了以下内容:“<”provider android:name="com.test.db.providers.MessagesProvider" android:authorities="com.test.db.providers .MessagesProvider"/>"

你还这样做吗?通常,提供商标签必须放置在应用程序标签内。

Yes, I have placed the following inside the tag just before the tag: "<"provider android:name="com.test.db.providers.MessagesProvider" android:authorities="com.test.db.providers.MessagesProvider"/>"

Do you still do this? As usually the provider-tag has to be placed inside the application-tag.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文