使用Android中的内容提供程序公开多个表的最佳实践

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Android中的内容提供程序公开多个表的最佳实践相关的知识,希望对你有一定的参考价值。

我正在构建一个应用程序,我有一个事件表和一个场地表。我希望能够授予其他应用程序访问此数据的权限。我有几个与此类问题的最佳实践相关的问题。

  1. 我应该如何构建数据库类?我目前有EventsDbAdapter和VenuesDbAdapter的类,它们提供查询每个表的逻辑,同时有一个单独的DbManager(扩展SQLiteOpenHelper)来管理数据库版本,创建/升级数据库,提供对数据库的访问(getWriteable / ReadeableDatabase)。这是推荐的解决方案,还是我最好将所有内容整合到一个类(即DbManager)或分离所有内容并让每个适配器扩展SQLiteOpenHelper?
  2. 我应该如何为多个表设计内容提供程序?扩展上一个问题,我应该为整个应用程序使用一个内容提供程序,还是应该为事件和场地创建单独的提供程序?

我发现的大多数示例只涉及单表应用程序,所以我很感激这里的任何指针。

答案

对你来说可能有点晚了,但其他人可能觉得这很有用。

首先,您需要创建多个CONTENT_URI

public static final Uri CONTENT_URI1 = 
    Uri.parse("content://"+ PROVIDER_NAME + "/sampleuri1");
public static final Uri CONTENT_URI2 = 
    Uri.parse("content://"+ PROVIDER_NAME + "/sampleuri2");

然后展开URI匹配器

private static final UriMatcher uriMatcher;
static {
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI(PROVIDER_NAME, "sampleuri1", SAMPLE1);
    uriMatcher.addURI(PROVIDER_NAME, "sampleuri1/#", SAMPLE1_ID);      
    uriMatcher.addURI(PROVIDER_NAME, "sampleuri2", SAMPLE2);
    uriMatcher.addURI(PROVIDER_NAME, "sampleuri2/#", SAMPLE2_ID);      
}

然后创建表格

private static final String DATABASE_NAME = "sample.db";
private static final String DATABASE_TABLE1 = "sample1";
private static final String DATABASE_TABLE2 = "sample2";
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_CREATE1 =
    "CREATE TABLE IF NOT EXISTS " + DATABASE_TABLE1 + 
    " (" + _ID1 + " INTEGER PRIMARY KEY AUTOINCREMENT," + 
    "data text, stuff text);";
private static final String DATABASE_CREATE2 =
    "CREATE TABLE IF NOT EXISTS " + DATABASE_TABLE2 + 
    " (" + _ID2 + " INTEGER PRIMARY KEY AUTOINCREMENT," + 
    "data text, stuff text);";

不要忘记将第二个DATABASE_CREATE添加到onCreate()

您将使用switch-case块来确定使用哪个表。这是我的插入代码

@Override
public Uri insert(Uri uri, ContentValues values) {
    Uri _uri = null;
    switch (uriMatcher.match(uri)){
    case SAMPLE1:
        long _ID1 = db.insert(DATABASE_TABLE1, "", values);
        //---if added successfully---
        if (_ID1 > 0) {
            _uri = ContentUris.withAppendedId(CONTENT_URI1, _ID1);
            getContext().getContentResolver().notifyChange(_uri, null);    
        }
        break;
    case SAMPLE2:
        long _ID2 = db.insert(DATABASE_TABLE2, "", values);
        //---if added successfully---
        if (_ID2 > 0) {
            _uri = ContentUris.withAppendedId(CONTENT_URI2, _ID2);
            getContext().getContentResolver().notifyChange(_uri, null);    
        }
        break;
    default: throw new SQLException("Failed to insert row into " + uri);
    }
    return _uri;                
}

您将需要分配deleteupdategetType等。无论您的提供商是否需要DATABASE_TABLE或CONTENT_URI,您都会添加一个案例并在其中添加DATABASE_TABLE1或CONTENT_URI1,在下一个中使用#2,依此类推。

另一答案

我建议查看android 2.x ContactProvider的源代码。 (可以在网上找到)。它们通过提供专用视图来处理跨表查询,然后在后端运行查询。在前端,调用者可以通过单个内容提供者通过各种不同的URI访问它们。您可能还希望提供一个或两个类来保存表字段名称和URI字符串的常量。这些类可以作为API包含或作为类中的drop提供,并且将使消费应用程序更容易使用。

它有点复杂,所以你可能还想查看日历如何以及了解你做什么和不需要什么。

每个数据库(不是每个表)只需要一个数据库适配器和一个内容提供程序来完成大部分工作,但如果您真的想要,可以使用多个适配器/提供程序。它只是让事情变得复杂一些。

另一答案

一个ContentProvider可以服务多个表,但它们应该有些相关。如果您打算同步提供商,这将有所不同。如果你想要单独的同步,比如说联系人,邮件或日历,你需要为它们各自提供不同的提供者,即使它们最终在同一个数据库中或者与同一个服务同步,因为同步适配器直接绑定到特定的提供者。

据我所知,每个数据库只能使用一个SQLiteOpenHelper,因为它将元信息存储在数据库的表中。因此,如果你的ContentProviders访问同一个数据库,你将不得不分享Helper。

另一答案

注意:这是对Opy提供的答案的澄清/修改。

这种方法使用switch语句细分insertdeleteupdategetType方法中的每一个,以便处理每个表。您将使用CASE来标识要引用的每个表(或uri)。然后每个CASE映射到您的一个表或URI。例如,对于您的应用程序使用的所有表,在CASE#1等中选择TABLE1或URI1。

这是该方法的一个例子。这适用于插入方法。它与Opy的实现略有不同,但执行相同的功能。您可以选择您喜欢的样式。我还想确保即使表插入失败,insert也会返回一个值。在这种情况下,它返回一个-1

  @Override
  public Uri insert(Uri uri, ContentValues values) {
    int uriType = sURIMatcher.match(uri);
    SQLiteDatabase sqlDB; 

    long id = 0;
    switch (uriType){ 
        case TABLE1: 
            sqlDB = Table1Database.getWritableDatabase();
            id = sqlDB.insert(Table1.TABLE_NAME, null, values); 
            getContext().getContentResolver().notifyChange(uri, null);
            return Uri.parse(BASE_PATH1 + "/" + id);
        case TABLE2: 
            sqlDB = Table2Database.getWritableDatabase();
            id = sqlDB.insert(Table2.TABLE_NAME, null, values); 
            getContext().getContentResolver().notifyChange(uri, null);
            return Uri.parse(BASE_PATH2 + "/" + id);
        default: 
            throw new SQLException("Failed to insert row into " + uri); 
            return -1;
    }       
  }  // [END insert]
另一答案

我找到了ContentProvider的最佳演示和解释,我认为它遵循Android标准。

合同类

 /**
   * The Content Authority is a name for the entire content provider, similar to the relationship
   * between a domain name and its website. A convenient string to use for content authority is
   * the package name for the app, since it is guaranteed to be unique on the device.
   */
  public static final String CONTENT_AUTHORITY = "com.androidessence.moviedatabase";

  /**
   * The content authority is used to create the base of all URIs which apps will use to
   * contact this content provider.
   */
  private static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

  /**
   * A list of possible paths that will be appended to the base URI for each of the different
   * tables.
   */
  public static final String PATH_MOVIE = "movie";
  public static final String PATH_GENRE = "genre";

和内在类:

 /**
   * Create one class for each table that handles all information regarding the table schema and
   * the URIs related to it.
   */
  public static final class MovieEntry implements BaseColumns {
      // Content URI represents the base location for the table
      public static final Uri CONTENT_URI =
              BASE_CONTENT_URI.buildUpon().appendPath(PATH_MOVIE).build();

      // These are special type prefixes that specify if a URI returns a list or a specific item
      public static final String CONTENT_TYPE =
              "vnd.android.cursor.dir/" + CONTENT_URI  + "/" + PATH_MOVIE;
      public static final String CONTENT_ITEM_TYPE =
              "vnd.android.cursor.item/" + CONTENT_URI + "/" + PATH_MOVIE;

      // Define the table schema
      public static final String TABLE_NAME = "movieTable";
      public static final String COLUMN_NAME = "movieName";
      public static final String COLUMN_RELEASE_DATE = "movieReleaseDate";
      public static final String COLUMN_GENRE = "movieGenre";

      // Define a function to build a URI to find a specific movie by it's identifier
      public static Uri buildMovieUri(long id){
          return ContentUris.withAppendedId(CONTENT_URI, id);
      }
  }

  public static final class GenreEntry implements BaseColumns{
      public static final Uri CONTENT_URI =
              BASE_CONTENT_URI.buildUpon().appendPath(PATH_GENRE).build();

      public static final String CONTENT_TYPE =
              "vnd.android.cursor.dir/" + CONTENT_URI + "/" + PATH_GENRE;
      public static final String CONTENT_ITEM_TYPE =
              "vnd.android.cursor.item/" + CONTENT_URI + "/" + PATH_GENRE;

      public static final String TABLE_NAME = "genreTable";
      public static final String COLUMN_NAME = "genreName";

      public static Uri buildGenreUri(long id){
          return ContentUris.withAppendedId(CONTENT_URI, id);
      }
  }

现在使用SQLiteOpenHelper创建数据库:

public class MovieDBHelper extends SQLiteOpenHelper{
    /**
     * Defines the database version. This variable must be incremented in order for onUpdate to
     * be called when necessary.
     */
    private static final int DATABASE_VERSION = 1;
    /**
     * The name of the database on the device.
     */
    private static final String DATABASE_NAME = "movieList.db";

    /**
     * Default constructor.
     * @param context The application context using this database.
     */
    public MovieDBHelper(Context conte

以上是关于使用Android中的内容提供程序公开多个表的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

如何在 android 内容提供程序中存储大 blob?

处理多个表的最佳实践

Android内容提供程序中多个进程的多次写入

NPM 包:最佳实践和公开多个导入路径

最佳实践:sql 视图真的值得吗? [复制]

具有远程数据的 android 应用程序的最佳方法是啥?