【问题标题】:Extended ContentProvider not behaving properly扩展的 ContentProvider 行为不正常
【发布时间】:2010-08-25 15:29:54
【问题描述】:

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

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

我想测试我所拥有的;所以我试图实例化我的新 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() 的引用为空。

注意:其他一切似乎都运行良好。当我调用查询时,它会触发我的查询处理程序,识别 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);

【问题讨论】:

  • 您注册了您的提供商吗?
  • 是的,我在 标记中的 标记之前放置了以下内容:
  • 我还尝试将提供程序标签移动到应用程序标签内。当我尝试启动应用程序时,应用程序崩溃(在到达实例化提供程序的我的 Activity 之前。日志说这里存在语法错误:创建表消息(_id 整数主键),delivery_id 整数,user_id 整数不为空,created_on 整数非空,主题文本非空,摘要文本非空,消息文本非空,读取整数非空,状态文本非空默认'STATE_OK';
  • 哎呀,发现语法错误。错位)
  • 无论如何。提供商现在正在正确注册。我的扩展 ContentProvider 的 onCreate 方法正在执行,但我的数据库助手实例化失败

标签: android sqlite cursor android-contentprovider


【解决方案1】:

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

在研究了那个例子之后,我会坚持他们在那里编写 ContentProviders 的方式,以及他们如何从 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" />

//仅作为旁注 由于我更喜欢​​进行自动化单元测试,而您提到您只想测试您的 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 实现后重新运行测试,看看您是否没有破坏任何东西。

【讨论】:

  • 感谢您的回复。这基本上就是我现在所拥有的(我已经更新了我的帖子)。但是,managedQuery 返回 null,我不知道为什么。我在清单中的应用程序标记中添加了以下标记,并且日志似乎显示了提供程序的正确注册:
  • 啊哈!好的,我做得更好,但我的查询处理程序由于某种原因无法识别 Uri。让我确保我没有输入任何错误
  • 从您发布的示例来看,似乎 ContentProvider 中的 URI 与 Activity 中使用的 URI 不同。可能您只是没有正确更新帖子。
  • 求主怜悯。它正在工作! ……嗯。我的 ListView 中的第一行自动获得焦点,我不太喜欢这样。
  • Fyi,我在看一个 Google I/O 演示,我相信是关于 REST 服务的演示,我相信演示者说数据库事务应该由服务调用,而 Web 服务应该被调用来自服务启动的线程。
【解决方案2】:

是的,我在标签之前的标签内放置了以下内容:“"

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

【讨论】:

  • 我的供应商标签在我的应用标签内
  • 好的,谢谢验证!我可以请你看看我的问题吗?当你解决了你的问题时,你也许可以修复它:stackoverflow.com/questions/4404689/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-29
  • 1970-01-01
  • 2015-04-19
  • 2017-05-03
  • 1970-01-01
  • 2013-08-10
相关资源
最近更新 更多