数据存储(3)自定义内容提供器

综述

使用系统的内容提供器先不讲,这里讨论自定义内容提供器。

因为sunshine使用了自定义内容提供器,那就要想想了,SQLite数据库完全可以满足需要了,为什么要加个内容提供器呢,

内容提供器是对SQLite的封装,也就是说,封装了之后,可以提供给别人用的,基于下面的原因:

想为其他应用提供复杂数据和文件
想让用户从我们的应用拷贝复杂数据到其他应用
想使用搜索引擎提供自定义搜索建议

天气应用的数据要提供给别人,所以用内容提供器封装一下,然后我们自己定义SQL,自己用,完全可以在DBHelper定义相关的查询语句就好,

不要内容提供器,那么下面还是继续讨论自定义内容提供器。

Designing Data Storage


其实这就是我们在SQLite设计的数据库

下面是一些设置提供器数据结构的建议
表数据应该总是有一个“primary key”作为每行的唯一标记。可以使用这个值连接其他表中的相关行(作为foreignkey)。虽然可以为这个列使用任何名,使用BaseColumns._ID(这个表类继承BaseColumns)是最好的选择,因为连接提供器的查询结构到一个ListView要求返回的列有一个名字叫_ID.
使用Binary Large OBject (BLOB)数据类型保存不同大小或者有不同的结构的数据。比如可以使用BLOB列保存portocol buffer或者Json structure。


Designing Content URIs

A content URI is a URI that identifies data in a provider. Content URIs include the symbolic name of the entire provider (its authority) and a name that points to a table or file (a path). The optional id part points to an individual row in a table. Every data access method of ContentProvider has a content URI as an argument; this allows you to determine the table, row, or file to access.

Designing an authority
设计一个权限,这个权限用来唯一标识这个Provider,因此这个权限第一在定义Provider的URi时使用,另一个在Manifest文件中定义Provider的时候需要,
所以最好是写在String中,不然乱了,自己就吃过亏的。

权限其实就是我们约定的一个字符串,一般域名倒过来,总是要唯一。我们建立的所有对内容提供器查询的URI都要以这个权限为根本。

Designing a path structure

然后级是path了,一般这个就是表名了。

Handling content URI IDs

指定行,具体要处理行。在ListView点击的时候,我们可以传递这个_ID值,然后拼出来一个具体的指向某行的URI,可以执行修改操作等。

URI匹配的的问题

我们要为查询建立匹配,首先要添加URI。

private static final UriMatcher sUriMatcher = buildUriMatcher();
静态方法中添加了我们需要匹配的URI,如果匹配就返回匹配值。

static UriMatcher buildUriMatcher() {
    // 1) The code passed into the constructor represents the code to return for the root
    // URI.  It's common to use NO_MATCH as the code for this case. Add the constructor below.
    UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    // 2) Use the addURI function to match each of the types.  Use the constants from
    // WeatherContract to help define the types to the UriMatcher.
    uriMatcher.addURI(WeatherContract.CONTENT_AUTHORITY, WeatherContract.PATH_WEATHER,WEATHER);
    uriMatcher.addURI(WeatherContract.CONTENT_AUTHORITY, WeatherContract.PATH_WEATHER + "/*",WEATHER_WITH_LOCATION);
    uriMatcher.addURI(WeatherContract.CONTENT_AUTHORITY, WeatherContract.PATH_WEATHER + "/*/#",WEATHER_WITH_LOCATION_AND_DATE);
    uriMatcher.addURI(WeatherContract.CONTENT_AUTHORITY, WeatherContract.PATH_LOCATION,LOCATION);


    // 3) Return the new matcher!
    return uriMatcher;
}
在查询中使用这个uriMatcher匹配:

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                    String sortOrder) {
    // Here's the switch statement that, given a URI, will determine what kind of request it is,
    // and query the database accordingly.
    Cursor retCursor;
    switch (sUriMatcher.match(uri)) {
        // "weather/*/*"
        case WEATHER_WITH_LOCATION_AND_DATE:
        {
            retCursor = getWeatherByLocationSettingAndDate(uri, projection, sortOrder);
            break;
        }
        // "weather/*"
        case WEATHER_WITH_LOCATION: {
            retCursor = getWeatherByLocationSetting(uri, projection, sortOrder);
            break;
        }
        // "weather"
        case WEATHER: {
            retCursor = mOpenHelper.getReadableDatabase().query(WeatherContract.WeatherEntry.TABLE_NAME,
                    projection,
                    selection,
                    selectionArgs,
                    null,
                    null,
                    sortOrder);
            break;
        }
        // "location"
        case LOCATION: {
            retCursor = mOpenHelper.getReadableDatabase().query(WeatherContract.LocationEntry.TABLE_NAME,
                    projection,
                    selection,
                    selectionArgs,
                    null,
                    null,
                    sortOrder);
            break;
        }

        default:
            throw new UnsupportedOperationException("Unknown uri: " + uri);
    }
    retCursor.setNotificationUri(getContext().getContentResolver(), uri);
    return retCursor;
}


如果是单表的简单查询,直接用DBHelper的查询就好,如果有连表查询,使用下面的辅助类:

private static final SQLiteQueryBuilder sWeatherByLocationSettingQueryBuilder;

static{
    sWeatherByLocationSettingQueryBuilder = new SQLiteQueryBuilder();
    
    //This is an inner join which looks like
    //weather INNER JOIN location ON weather.location_id = location._id
    sWeatherByLocationSettingQueryBuilder.setTables(
            WeatherContract.WeatherEntry.TABLE_NAME + " INNER JOIN " +
                    WeatherContract.LocationEntry.TABLE_NAME +
                    " ON " + WeatherContract.WeatherEntry.TABLE_NAME +
                    "." + WeatherContract.WeatherEntry.COLUMN_LOC_KEY +
                    " = " + WeatherContract.LocationEntry.TABLE_NAME +
                    "." + WeatherContract.LocationEntry._ID);
}

然后查询的话,这样:

private Cursor getWeatherByLocationSettingAndDate(
        Uri uri, String[] projection, String sortOrder) {
    String locationSetting = WeatherContract.WeatherEntry.getLocationSettingFromUri(uri);
    long date = WeatherContract.WeatherEntry.getDateFromUri(uri);

    return sWeatherByLocationSettingQueryBuilder.query(mOpenHelper.getReadableDatabase(),
            projection,
            sLocationSettingAndDaySelection,
            new String[]{locationSetting, Long.toString(date)},
            null,
            null,
            sortOrder
    );
}

删除和插入,更新类似吧,下面正式讲如何实现ContentProvider。

继承实现ContentProvider

query() query查询,上面讲了,返回一个cursor。
Retrieve data from your provider. Use the arguments to select the table to query, the rows and columns to return, and the sort order of the result. Return the data as a Cursor object.
insert()
Insert a new row into your provider. Use the arguments to select the destination table and to get the column values to use. Return a content URI for the newly-inserted row.
插入:返回具体行的URI,使用sb.insrt返回的id
public static Uri buildWeatherUri(long id) {
    return ContentUris.withAppendedId(CONTENT_URI, id);
}
sunshine中的例子:
@Override
public Uri insert(Uri uri, ContentValues values) {
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    final int match = sUriMatcher.match(uri);
    Uri returnUri;

    switch (match) {
        case WEATHER: {
            normalizeDate(values);
            long _id = db.insert(WeatherContract.WeatherEntry.TABLE_NAME, null, values);
            if ( _id > 0 )
                returnUri = WeatherContract.WeatherEntry.buildWeatherUri(_id);
            else
                throw new android.database.SQLException("Failed to insert row into " + uri);
            break;
        }
        case LOCATION:{
            long _id = db.insert(WeatherContract.LocationEntry.TABLE_NAME, null, values);
            if ( _id > 0 )
                returnUri = WeatherContract.WeatherEntry.buildWeatherUri(_id);
            else
                throw new android.database.SQLException("Failed to insert row into " + uri);
            break;
        }
        default:
            throw new UnsupportedOperationException("Unknown uri: " + uri);
    }
    getContext().getContentResolver().notifyChange(uri, null);//通知的是传入的uri,而不是返回的uri
    return returnUri;
}

update()
Update existing rows in your provider. Use the arguments to select the table and rows to update and to get the updated column values. Return the number of rows updated.
更新:
@Override
public int update(
        Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    // Student: This is a lot like the delete function.  We return the number of rows impacted
    // by the update.
    final  SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    final int match = sUriMatcher.match(uri);
    int updateRows = 0;
    switch (match){
        case WEATHER :
            updateRows = db.update(WeatherContract.WeatherEntry.TABLE_NAME, values,  selection, selectionArgs);
            break;

        case LOCATION :
            updateRows = db.update(WeatherContract.LocationEntry.TABLE_NAME, values,  selection, selectionArgs);
            break;

        default: throw new UnsupportedOperationException("Unknown uri: " + uri);
    }
    if(updateRows > 0){
        getContext().getContentResolver().notifyChange(uri, null);
    }
    return updateRows;
}

delete()
Delete rows from your provider. Use the arguments to select the table and the rows to delete. Return the number of rows deleted.

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
    // Student: Start by getting a writable database
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    final int match = sUriMatcher.match(uri);
    int deleteRows = 0;
    if(selection == null) selection="1";
    switch (match){
        case WEATHER:
            deleteRows = db.delete(WeatherContract.WeatherEntry.TABLE_NAME,selection,selectionArgs);
            break;
        case LOCATION:
            deleteRows = db.delete(WeatherContract.LocationEntry.TABLE_NAME, selection, selectionArgs);
            break;
        default: throw  new UnsupportedOperationException("Unknown uri: " + uri);
    }
if(deleteRows > 0){
    getContext().getContentResolver().notifyChange(uri, null);
}
// Student: return the actual rows deleted
return deleteRows;

}

getType()
Return the MIME type corresponding to a content URI. This method is described in more detail in the section Implementing Content Provider MIME Types.

表示这

定义的几个MIMEtype,每个表的MIME type在那个内部类中定义。
  •      If the URI pattern is for a single row: android.cursor.item/
  • If the URI pattern is for more than one row: android.cursor.dir/
  •      For example, if a provider's authority is com.example.app.provider, and it exposes a table named table1, the MIME type for multiple rows in table1 is:

    vnd.android.cursor.dir/vnd.com.example.provider.table1

    For a single row of table1, the MIME type is:

    vnd.android.cursor.item/vnd.com.example.provider.table1


public static final String CONTENT_TYPE =
        ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_WEATHER;
public static final String CONTENT_ITEM_TYPE =
        ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_WEATHER


@Override
public String getType(Uri uri) {

    // Use the Uri Matcher to determine what kind of URI this is.
    final int match = sUriMatcher.match(uri);

    switch (match) {
        // Student: Uncomment and fill out these two cases
        case WEATHER_WITH_LOCATION:
        case WEATHER:
            return WeatherContract.WeatherEntry.CONTENT_TYPE;
        case WEATHER_WITH_LOCATION_AND_DATE:
            return  WeatherContract.WeatherEntry.CONTENT_ITEM_TYPE;
        case LOCATION:
            return WeatherContract.LocationEntry.CONTENT_TYPE;
        default:
            throw new UnsupportedOperationException("Unknown uri: " + uri);
    }
}



onCreate()
Initialize your provider. The Android system calls this method immediately after it creates your provider. Notice that your provider is not created until a ContentResolver object tries to access it.     
最简单,初始化DBHelper类就好了。

@Override
public boolean onCreate() {
    mOpenHelper = new WeatherDbHelper(getContext());
    return true;
}

创建ContentProvider注意
(1)除了OnCreate,其他几个都可以被多个线程调用,要保证线程安全
(2)OnCreate中避免长时间耗时操作,我们一般就定义一个DBHelper,不要调用getWritableDatabse方法,耗时。需要的时候在用
(3)虽然必须实现这几个方法,不过要忽略某个方法,比如insert,返回0(??可以吗)就好了。

几个函数接收的参数:

更相信慢慢看ContentProvider的文档。

         public abstract Uri insert (Uri uri, ContentValues values)
Added in API level 1

Implement this to handle requests to insert a new row. As a courtesy, call notifyChange() after inserting. This method can be called from multiple threads, as described in Processes and Threads.

Parameters
uriThe content:// URI of the insertion request. This must not be null.
valuesA set of column_name/value pairs to add to the database. This must not be null.
Returns
  • The URI for the newly inserted item
         public abstract boolean onCreate ()
Added in API level 1

Implement this to initialize your content provider on startup. This method is called for all registered content providers on the application main thread at application launch time. It must not perform lengthy operations, or application startup will be delayed.

You should defer nontrivial initialization (such as opening, upgrading, and scanning databases) until the content provider is used (via query(Uri, String[], String, String[], String), insert(Uri, ContentValues), etc). Deferred initialization keeps application startup fast, avoids unnecessary work if the provider turns out not to be needed, and stops database errors (such as a full disk) from halting application launch.

If you use SQLite, SQLiteOpenHelper is a helpful utility class that makes it easy to manage databases, and will automatically defer opening until first use. If you do use SQLiteOpenHelper, make sure to avoid calling getReadableDatabase() or getWritableDatabase() from this method. (Instead, override onOpen(SQLiteDatabase) to initialize the database when it is first opened.)

Returns
  • true if the provider was successfully loaded, false otherwise 
         public abstract Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
Added in API level 1

Implement this to handle query requests from clients. This method can be called from multiple threads, as described in Processes and Threads.

Example client call:

// Request a specific record.
 Cursor managedCursor = managedQuery(
                ContentUris.withAppendedId(Contacts.People.CONTENT_URI, 2),
                projection,    // Which columns to return.
                null,          // WHERE clause.
                null,          // WHERE clause value substitution
                People.NAME + " ASC");   // Sort order.
Example implementation:

// SQLiteQueryBuilder is a helper class that creates the
        // proper SQL syntax for us.
        SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();

        // Set the table we're querying.
        qBuilder.setTables(DATABASE_TABLE_NAME);

        // If the query ends in a specific record number, we're
        // being asked for a specific record, so set the
        // WHERE clause in our query.
        if((URI_MATCHER.match(uri)) == SPECIFIC_MESSAGE){
            qBuilder.appendWhere("_id=" + uri.getPathLeafId());
        }

        // Make the query.
        Cursor c = qBuilder.query(mDb,
                projection,
                selection,
                selectionArgs,
                groupBy,
                having,
                sortOrder);
        c.setNotificationUri(getContext().getContentResolver(), uri);
        return c;

Parameters
uriThe URI to query. This will be the full URI sent by the client; if the client is requesting a specific record, the URI will end in a record number that the implementation should parse and add to a WHERE or HAVING clause, specifying that _id value.
projectionThe list of columns to put into the cursor. If null all columns are included.
selectionA selection criteria to apply when filtering rows. If null then all rows are included.
selectionArgsYou may include ?s in selection, which will be replaced by the values from selectionArgs, in order that they appear in the selection. The values will be bound as Strings.
sortOrderHow the rows in the cursor should be sorted. If null then the provider is free to define the sort order.
Returns
  • a Cursor or null

         public abstract int update (Uri uri, ContentValues values, String selection, String[] selectionArgs)
Added in API level 1

Implement this to handle requests to update one or more rows. The implementation should update all rows matching the selection to set the columns according to the provided values map. As a courtesy, call notifyChange() after updating. This method can be called from multiple threads, as described in Processes and Threads.

Parameters
uriThe URI to query. This can potentially have a record ID if this is an update request for a specific record.
valuesA set of column_name/value pairs to update in the database. This must not be null.
selectionAn optional filter to match rows to update.
Returns
  • the number of rows affected. 

         public abstract int delete (Uri uri, String selection, String[] selectionArgs)
Added in API level 1

Implement this to handle requests to delete one or more rows. The implementation should apply the selection clause when performing deletion, allowing the operation to affect multiple rows in a directory. As a courtesy, call notifyChange() after deleting. This method can be called from multiple threads, as described in Processes and Threads.

The implementation is responsible for parsing out a row ID at the end of the URI, if a specific row is being deleted. That is, the client would pass in content://contacts/people/22 and the implementation is responsible for parsing the record number (22) when creating a SQL statement.

Parameters
uriThe full URI to query, including a row ID (if a specific record is requested).
selectionAn optional restriction to apply to rows when deleting.
Returns
  • The number of rows affected.
Throws
SQLException 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值