content-providers

阅读:http://developer.android.com/guide/topics/providers/content-providers.html

When you want to access data in a content provider, you use the ContentResolver object in your application's Context to communicate with the provider as a client. The ContentResolver object communicates with the provider object, an instance of a class that implements ContentProvider. The provider object receives data requests from clients, performs the requested action, and returns the results.

  当我们想获得content provider,可以使用 ContentResolver对象来获得provider object ,继承ContentProvider的一个对象。provider object能够从用户获得请求并返回结果。


 

 

// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
    mProjection,                        // The columns to return for each row
    mSelectionClause                    // Selection criteria
    mSelectionArgs,                     // Selection criteria
    mSortOrder);                        // The sort order for the returned rows

一般通过ContentResolver的方法来进行请求,相应的参数如下:

Table 2: Query() compared to SQL query.

query() argumentSELECT keyword/parameterNotes
Uri FROM table_name Uri maps to the table in the provider named table_name.
projection col,col,col,... projection is an array of columns that should be included for each row retrieved.
selection WHERE col = value selection specifies the criteria for selecting rows.
selectionArgs (No exact equivalent. Selection arguments replace ? placeholders in the selection clause.)
sortOrder ORDER BY col,col,... sortOrder specifies the order in which rows appear in the returned Cursor.

 

   content URI是一个在provide里已经定义好的URI,它是用户请求的一个URI。形式像是这样:

content://user_dictionary/words

  


 

  你还可以通过URI直接连接到_ID为某个值的那行,使用这个方法:

  Uri singleUri =ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

  最后的参数为_ID的值。


 

官方文档中,建议使用异步操作进行数据的请求:

 


 

 

一般数据的请求还要有相关的权限。

 


 

 

/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] mSelectionArgs = {""};

// Gets a word from the UI
mSearchString = mSearchWord.getText().toString();

// Remember to insert code here to check for invalid or malicious input.

// If the word is the empty string, gets everything
if (TextUtils.isEmpty(mSearchString)) {
    // Setting the selection clause to null will return all words
    mSelectionClause = null;
    mSelectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered.
    mSelectionClause = UserDictionary.Words.WORD + " = ?";

    // Moves the user's input string to the selection arguments.
    mSelectionArgs[0] = mSearchString;

}

// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    mProjection,                       // The columns to return for each row
    mSelectionClause                   // Either null, or the word the user entered
    mSelectionArgs,                    // Either empty, or the string the user entered
    mSortOrder);                       // The sort order for the returned rows

// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
    /*
     * Insert code here to handle the error. Be sure not to use the cursor! You may want to
     * call android.util.Log.e() to log this error.
     *
     */
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {

    /*
     * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily
     * an error. You may want to offer the user the option to insert a new row, or re-type the
     * search term.
     */

} else {
    // Insert code here to do something with the results

}

以上就是一个例子,注意,之所以用“ = ? ” 以及后面的参数集合来查询,是为了避免用户非法操作。


 

查询会返回结果,如果是NULL则表示出错,而查询正确则会返回Cursor,如果查询结果为0,那么Cursor.getCount()==0;


 

 

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
};

// Defines a list of View IDs that will receive the Cursor columns for each row
int[] mWordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
mCursorAdapter = new SimpleCursorAdapter(
    getApplicationContext(),               // The application's Context object
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
    mCursor,                               // The result from the query
    mWordListColumns,                      // A string array of column names in the cursor
    mWordListItems,                        // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);

上面例子表示了如何和适配器搭配使用。

Note: To back a ListView with a Cursor, the cursor must contain a column named _ID. Because of this, the query shown previously retrieves the _ID column for the "words" table, even though the ListView doesn't display it. This restriction also explains why most providers have a _ID column for each of their tables.


// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);

/*
 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
 * an internal error occurs. Other providers may throw an Exception instead of returning null.
 */

if (mCursor != null) {
    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you will get an
     * exception.
     */
    while (mCursor.moveToNext()) {

        // Gets the value from the column.
        newWord = mCursor.getString(index);

        // Insert code here to process the retrieved word.

        ...

        // end of while loop
    }
} else {

    // Insert code here to report an error if the cursor is null or the provider threw an exception.
}


如果需要获取某列的值,需要先获得某列的index再去获取相关值,关于值的类型可以用 getType()获取。


 

// Defines a new Uri object that receives the result of the insertion
Uri mNewUri;

...

// Defines an object to contain the new values to insert
ContentValues mNewValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value"
 */
mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
mNewValues.put(UserDictionary.Words.WORD, "insert");
mNewValues.put(UserDictionary.Words.FREQUENCY, "100");

mNewUri = getContentResolver().insert(
    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
    mNewValues                          // the values to insert
);

通过新建一个ContentValues来进行插入数据的存储,然后后面直接用insert插入,注意_ID是自动插入的不需要自己添加,返回的uri可以通过ContentUris.parseId().获取_ID。


 

 

// Defines an object to contain the updated values
ContentValues mUpdateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
String[] mSelectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int mRowsUpdated = 0;

...

/*
 * Sets the updated value and updates the selected words.
 */
mUpdateValues.putNull(UserDictionary.Words.LOCALE);

mRowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mUpdateValues                       // the columns to update
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

插入代码如上。


 

 

// Defines selection criteria for the rows you want to delete
String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] mSelectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int mRowsDeleted = 0;

...

// Deletes the words that match the selection criteria
mRowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

删除的代码。

 

接下来谈谈Content-provider如何设计开发

首先,基于这样的需求你才需要CP:

1、与其他的APP有一定数据的交互;

2、需要用到搜索那块;

设计流程:

1、考虑数据形式:如果是文件,建议存放在私有空间,然后通过CP获得手柄去访问,又或者,只是单纯的数据结构,那么就可以通过类似数据库等来解决。

2、实现CP的类;

3、Define the provider's authority string, its content URIs, and column names.

 

此外,还需要考虑:主键的问题;对于文件数据还是存为文件然后让CP提供访问较好;

 

接下来,你还要设计名字,权限,结构等问题比较主要的是ID问题。

接下来就是,URI的配对问题了。

 

To help you choose which action to take for an incoming content URI, the provider API includes the convenience class UriMatcher, which maps content URI "patterns" to integer values. You can use the integer values in a switch statement that chooses the desired action for the content URI or URIs that match a particular pattern.

A content URI pattern matches content URIs using wildcard characters:

  • *: Matches a string of any valid characters of any length.
  • #: Matches a string of numeric characters of any length.

As an example of designing and coding content URI handling, consider a provider with the authority com.example.app.provider that recognizes the following content URIs pointing to tables:

  • content://com.example.app.provider/table1: A table called table1.
  • content://com.example.app.provider/table2/dataset1: A table called dataset1.
  • content://com.example.app.provider/table2/dataset2: A table called dataset2.
  • content://com.example.app.provider/table3: A table called table3.

The provider also recognizes these content URIs if they have a row ID appended to them, as for example content://com.example.app.provider/table3/1 for the row identified by 1 in table3.

The following content URI patterns would be possible:

content://com.example.app.provider/*
Matches any content URI in the provider.
content://com.example.app.provider/table2/*:
Matches a content URI for the tables dataset1 and dataset2, but doesn't match content URIs for table1 or table3.
content://com.example.app.provider/table3/#: Matches a content URI for single rows in table3, such as content://com.example.app.provider/table3/6 for the row identified by 6.

The following code snippet shows how the methods in UriMatcher work. This code handles URIs for an entire table differently from URIs for a single row, by using the content URI pattern content://<authority>/<path> for tables, and content://<authority>/<path>/<id> for single rows.

The method addURI() maps an authority and path to an integer value. The method match() returns the integer value for a URI. A switch statement chooses between querying the entire table, and querying for a single record:

 

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher sUriMatcher;
...
    /*
     * The calls to addURI() go here, for all of the content URI patterns that the provider
     * should recognize. For this snippet, only the calls for table 3 are shown.
     */
...
    /*
     * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
     * in the path
     */
    sUriMatcher.addURI("com.example.app.provider", "table3", 1);

    /*
     * Sets the code for a single row to 2. In this case, the "#" wildcard is
     * used. "content://com.example.app.provider/table3/3" matches, but
     * "content://com.example.app.provider/table3 doesn't.
     */
    sUriMatcher.addURI("com.example.app.provider", "table3/#", 2);
...
    // Implements ContentProvider.query()
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * Choose the table to query and a sort order based on the code returned for the incoming
         * URI. Here, too, only the statements for table 3 are shown.
         */
        switch (sUriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query
                 */
                selection = selection + "_ID = " uri.getLastPathSegment();
                break;

            default:
            ...
                // If the URI is not recognized, you should do some error handling here.
        }
        // call the code to actually do the query
    }

然后,继承CP需要重写一些方法:

 

 
query()

根据所提出的要求返回Cursor。Cursor有可能需要自己去实现


insert()

插入新数据,返回新数据的URI
update()
更新数据,返回更新的数目。
delete()
删除,返回删除数目。
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.
onCreate()

当ContentResolver使用的时候就会启动这个方法,初始化CP吧!

Your implementation of these methods should account for the following:

  • All of these methods except onCreate() can be called by multiple threads at once, so they must be thread-safe. To learn more about multiple threads, see the topic Processes and Threads.
  • Avoid doing lengthy operations in onCreate(). Defer initialization tasks until they are actually needed. The section Implementing the onCreate() method discusses this in more detail.
  • Although you must implement these methods, your code does not have to do anything except return the expected data type. For example, you may want to prevent other applications from inserting data into some tables. To do this, you can ignore the call to insert() and return 0.

   除了oncreate之外,其他方法都要加上线程安全。

  不要再oncreate执行太多操作。

  并不是每个方法都要很认真的返回所要求的数据,根据需求看吧!

 

  接下来就是关于CP的权限了,是在manifest立马配置的,而且,有多种类型:

Single read-write provider-level permission
一次性定义读和写权限;
Separate read and write provider-level permission
分开定义android:readPermission and android:writePermission。
Path-level permission
定义特定路径下的路径,详细见官方。
Temporary permission
  可赋予其他APP临时权限。

 

Like Activity and Service components, a subclass of ContentProvider must be defined in the manifest file for its application, using the <provider> element. The Android system gets the following information from the element:

Authority (android:authorities)
Symbolic names that identify the entire provider within the system. This attribute is described in more detail in the section Designing Content URIs.
Provider class name ( android:name )
The class that implements ContentProvider. This class is described in more detail in the section Implementing the ContentProvider Class.
Permissions
Attributes that specify the permissions that other applications must have in order to access the provider's data:

Permissions and their corresponding attributes are described in more detail in the section Implementing Content Provider Permissions.

Startup and control attributes
These attributes determine how and when the Android system starts the provider, the process characteristics of the provider, and other run-time settings:
  • android:enabled: Flag allowing the system to start the provider.
  • android:exported: Flag allowing other applications to use this provider.
  • android:initOrder: The order in which this provider should be started, relative to other providers in the same process.
  • android:multiProcess: Flag allowing the system to start the provider in the same process as the calling client.
  • android:process: The name of the process in which the provider should run.
  • android:syncable: Flag indicating that the provider's data is to be sync'ed with data on a server.

The attributes are fully documented in the dev guide topic for the <provider> element.

Informational attributes
An optional icon and label for the provider:
  • android:icon: A drawable resource containing an icon for the provider. The icon appears next to the provider's label in the list of apps in Settings > Apps > All.
  • android:label: An informational label describing the provider or its data, or both. The label appears in the list of apps in Settings > Apps > All.

The attributes are fully documented in the dev guide topic for the <provider> element.

 

 

 

 

 

posted @ 2013-10-22 16:13  yutoulck  阅读(306)  评论(0编辑  收藏  举报