我正在构建一个应用程序,我有一个活动表和一个场地表。我希望能够授予其他应用程序访问此数据的权限。我有几个与此类问题的最佳实践相关的问题。
我应该如何构建数据库类? 我目前有EventsDbAdapter和VenuesDbAdapter的类,它们提供查询每个表的逻辑,同时有一个单独的DbManager(扩展SQLiteOpenHelper)来管理数据库版本,创建/升级数据库,提供对数据库的访问(getWriteable / ReadeableDatabase)。这是推荐的解决方案,还是我最好将所有内容合并到一个类(即DbManager)或分离所有内容并让每个适配器扩展SQLiteOpenHelper?
我应该如何为多个表设计内容提供商? 扩展上一个问题,我应该为整个应用程序使用一个内容提供程序,还是应该为事件和场地创建单独的提供程序?
我发现的大多数示例只涉及单表应用程序,所以我很感激这里有任何指示。
答案 0 :(得分:114)
对你来说可能有点晚了,但其他人可能觉得这很有用。
首先,您需要创建多个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;
}
您需要设置delete
,update
,getType
等。只要您的提供商要求DATABASE_TABLE或CONTENT_URI,您就会添加一个案例并将DATABASE_TABLE1或CONTENT_URI1合二为一和#2在下一个等等,你可以随心所欲。
答案 1 :(得分:10)
我建议查看Android 2.x ContactProvider的源代码。 (可以在网上找到)。它们通过提供专用视图来处理跨表查询,然后在后端运行查询。在前端,调用者可以通过单个内容提供者通过各种不同的URI访问它们。您可能还希望提供一个或两个类来保存表字段名称和URI字符串的常量。这些类可以作为API包含或作为类中的drop提供,并且使消费应用程序更容易使用。
它有点复杂,所以你可能还想查看日历,以及了解你做什么和不需要什么。
每个数据库(不是每个表)只需要一个数据库适配器和一个内容提供程序来完成大部分工作,但如果您真的需要,可以使用多个适配器/提供程序。它只是让事情变得更复杂。
答案 2 :(得分:7)
一个ContentProvider
可以提供多个表,但它们应该有些相关。如果您打算同步提供商,这将有所不同。如果你想要单独的同步,比如说联系人,邮件或日历,你需要为它们各自提供不同的提供者,即使它们最终在同一个数据库中或者与同一个服务同步,因为同步适配器直接绑定到特定的提供者。
据我所知,每个数据库只能使用一个SQLiteOpenHelper,因为它将元信息存储在数据库的表中。因此,如果您的ContentProviders
访问同一个数据库,则必须分享Helper。
答案 3 :(得分:7)
注意:这是对Opy提供的答案的澄清/修改。
此方法使用switch语句细分每个insert
,delete
,update
和getType
方法,以便处理每个表。您将使用CASE来标识要引用的每个表(或uri)。然后每个CASE映射到您的一个表或URI。例如,对于您的应用所使用的所有表格,在案例#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]
答案 4 :(得分:2)
我找到了 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 context){
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* Called when the database is first created.
* @param db The database being created, which all SQL statements will be executed on.
*/
@Override
public void onCreate(SQLiteDatabase db) {
addGenreTable(db);
addMovieTable(db);
}
/**
* Called whenever DATABASE_VERSION is incremented. This is used whenever schema changes need
* to be made or new tables are added.
* @param db The database being updated.
* @param oldVersion The previous version of the database. Used to determine whether or not
* certain updates should be run.
* @param newVersion The new version of the database.
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
/**
* Inserts the genre table into the database.
* @param db The SQLiteDatabase the table is being inserted into.
*/
private void addGenreTable(SQLiteDatabase db){
db.execSQL(
"CREATE TABLE " + MovieContract.GenreEntry.TABLE_NAME + " (" +
MovieContract.GenreEntry._ID + " INTEGER PRIMARY KEY, " +
MovieContract.GenreEntry.COLUMN_NAME + " TEXT UNIQUE NOT NULL);"
);
}
/**
* Inserts the movie table into the database.
* @param db The SQLiteDatabase the table is being inserted into.
*/
private void addMovieTable(SQLiteDatabase db){
db.execSQL(
"CREATE TABLE " + MovieContract.MovieEntry.TABLE_NAME + " (" +
MovieContract.MovieEntry._ID + " INTEGER PRIMARY KEY, " +
MovieContract.MovieEntry.COLUMN_NAME + " TEXT NOT NULL, " +
MovieContract.MovieEntry.COLUMN_RELEASE_DATE + " TEXT NOT NULL, " +
MovieContract.MovieEntry.COLUMN_GENRE + " INTEGER NOT NULL, " +
"FOREIGN KEY (" + MovieContract.MovieEntry.COLUMN_GENRE + ") " +
"REFERENCES " + MovieContract.GenreEntry.TABLE_NAME + " (" + MovieContract.GenreEntry._ID + "));"
);
}
}
内容提供商:
public class MovieProvider extends ContentProvider {
// Use an int for each URI we will run, this represents the different queries
private static final int GENRE = 100;
private static final int GENRE_ID = 101;
private static final int MOVIE = 200;
private static final int MOVIE_ID = 201;
private static final UriMatcher sUriMatcher = buildUriMatcher();
private MovieDBHelper mOpenHelper;
@Override
public boolean onCreate() {
mOpenHelper = new MovieDBHelper(getContext());
return true;
}
/**
* Builds a UriMatcher that is used to determine witch database request is being made.
*/
public static UriMatcher buildUriMatcher(){
String content = MovieContract.CONTENT_AUTHORITY;
// All paths to the UriMatcher have a corresponding code to return
// when a match is found (the ints above).
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(content, MovieContract.PATH_GENRE, GENRE);
matcher.addURI(content, MovieContract.PATH_GENRE + "/#", GENRE_ID);
matcher.addURI(content, MovieContract.PATH_MOVIE, MOVIE);
matcher.addURI(content, MovieContract.PATH_MOVIE + "/#", MOVIE_ID);
return matcher;
}
@Override
public String getType(Uri uri) {
switch(sUriMatcher.match(uri)){
case GENRE:
return MovieContract.GenreEntry.CONTENT_TYPE;
case GENRE_ID:
return MovieContract.GenreEntry.CONTENT_ITEM_TYPE;
case MOVIE:
return MovieContract.MovieEntry.CONTENT_TYPE;
case MOVIE_ID:
return MovieContract.MovieEntry.CONTENT_ITEM_TYPE;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Cursor retCursor;
switch(sUriMatcher.match(uri)){
case GENRE:
retCursor = db.query(
MovieContract.GenreEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
case GENRE_ID:
long _id = ContentUris.parseId(uri);
retCursor = db.query(
MovieContract.GenreEntry.TABLE_NAME,
projection,
MovieContract.GenreEntry._ID + " = ?",
new String[]{String.valueOf(_id)},
null,
null,
sortOrder
);
break;
case MOVIE:
retCursor = db.query(
MovieContract.MovieEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder
);
break;
case MOVIE_ID:
_id = ContentUris.parseId(uri);
retCursor = db.query(
MovieContract.MovieEntry.TABLE_NAME,
projection,
MovieContract.MovieEntry._ID + " = ?",
new String[]{String.valueOf(_id)},
null,
null,
sortOrder
);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
// Set the notification URI for the cursor to the one passed into the function. This
// causes the cursor to register a content observer to watch for changes that happen to
// this URI and any of it's descendants. By descendants, we mean any URI that begins
// with this path.
retCursor.setNotificationUri(getContext().getContentResolver(), uri);
return retCursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long _id;
Uri returnUri;
switch(sUriMatcher.match(uri)){
case GENRE:
_id = db.insert(MovieContract.GenreEntry.TABLE_NAME, null, values);
if(_id > 0){
returnUri = MovieContract.GenreEntry.buildGenreUri(_id);
} else{
throw new UnsupportedOperationException("Unable to insert rows into: " + uri);
}
break;
case MOVIE:
_id = db.insert(MovieContract.MovieEntry.TABLE_NAME, null, values);
if(_id > 0){
returnUri = MovieContract.MovieEntry.buildMovieUri(_id);
} else{
throw new UnsupportedOperationException("Unable to insert rows into: " + uri);
}
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
// Use this on the URI passed into the function to notify any observers that the uri has
// changed.
getContext().getContentResolver().notifyChange(uri, null);
return returnUri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int rows; // Number of rows effected
switch(sUriMatcher.match(uri)){
case GENRE:
rows = db.delete(MovieContract.GenreEntry.TABLE_NAME, selection, selectionArgs);
break;
case MOVIE:
rows = db.delete(MovieContract.MovieEntry.TABLE_NAME, selection, selectionArgs);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
// Because null could delete all rows:
if(selection == null || rows != 0){
getContext().getContentResolver().notifyChange(uri, null);
}
return rows;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int rows;
switch(sUriMatcher.match(uri)){
case GENRE:
rows = db.update(MovieContract.GenreEntry.TABLE_NAME, values, selection, selectionArgs);
break;
case MOVIE:
rows = db.update(MovieContract.MovieEntry.TABLE_NAME, values, selection, selectionArgs);
break;
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
if(rows != 0){
getContext().getContentResolver().notifyChange(uri, null);
}
return rows;
}
}
希望对您有帮助。
在 GitHub上的演示:https://github.com/androidessence/MovieDatabase
全文:https://guides.codepath.com/android/creating-content-providers
参考:
http://code.tutsplus.com/tutorials/android-sdk_content-providers--mobile-5549
http://www.grokkingandroid.com/android-tutorial-writing-your-own-content-provider/
http://developer.android.com/guide/topics/providers/content-providers.html
https://thenewcircle.com/s/post/1375/android_content_provider_tutorial
http://www.grokkingandroid.com/android-tutorial-content-provider-basics/
注意:我复制代码只是因为将来可能会删除演示或文章的链接。