Interpreting Android ContentProvider (2) creating your own Provider and android creating provider
This article is translated from the official android documentation and tested by yourself.
Content provider manages data access. We can implement one or more custom providers in our own applications (by inheriting the abstract class ContentProvider ), of course, these providers must be registered in the manifest file. Although content providers are used to access data for other programs, activities in their own programs can obviously process the data.
Notes before creating a provider
Determine whether the content provider is required. To meet one or more of the following requirements, you must create a content provider:
- To provide complex data or files to other programs;
- To allow users to copy complex data from our program to another program;
- You want to use the query framework to provide custom query recommendations.
If the SQLite database is used in the program, no provider is required.
Next, follow these steps to create a provider ):
The above steps are described in detail below.
Design Data Storage
Before providing the provider, we must determine how to store our data. Of course, we can specify the storage method and then design the provider for the storage method.
There are several storage methods:
- Stored in the SQLite database.
- Stored in files.
- Stored in the network.
For this part, refer to my translated article: Android Data Storage Solution
Notes
- A primary key is usually required for table data. The provider assigns a unique value to each row. Although the primary key column can be any name, we recommend that you use
BaseColumns._ID
In this way, you can easily search in ListView.
- If a bitmap or larger file data is provided, the data is stored in the file and provided indirectly. If the data is used, we should notify external applications that they should use the file method in the ContentResovler class to obtain the data, such
openInputStream(Uri uri)
AndopenOutputStream(Uri uri)
.
- If the BLOB data type is stored, the size or structure will change. We can use BLOB to implement a schema-independent table. For this type of table, we need to define a primary key, a MIME-type column, and one or more BLOB-type columns. The meaning of data in the BLOB column is expressed by the value in the MIME type column. This method allows different row types to be stored in the same table.
Design Content: URIs
Content URI is the URI that can recognize data in the provider, including authority and path ). Find the permission provider and find the table or file in the path. You can also have an id that represents a row.
Design permission authority
Permissions are used to differentiate the providers of different programs. Generally, to avoid conflicts, they are named in the form of package names. For example, the package name is:com.example.sywyg
, The permission can be:com.example.sywyg.provider
. In the AndroidManifest configuration file<provider>
Label settings<android:authority>
Label.
Design path
URI is used to search for the specified table by adding the permission and path. The path is to distinguish between tables or other forms (such as files) in the same program, and can be directly added after the permission. For example, table1 and table2 form the following Uris:com.example.sywyg.provider/table1
Andcom.example.sywyg.provider/table2
.
The URI must be added before the permission and path.content://
Indicates the content URI. For example, a standard content URI is written as follows:content://com.example.sywyg.provider/table1
.
ID of the URI
Append the ID to the URI to retrieve the specified row in the table. The column name corresponding to the ID is _ ID.
URI pattern matching
UriMatcher class ing content URI mode to an integer type number, we can use this value for pattern matching.
URI mode matches by wildcards:
- *: Match any length and Valid String;
- #: Match numbers of any length;
Assume the permission is:com.example.app.provider
To identify the table corresponding to the URI below:
'
Content: // com. example. app. provider/table1: The table is table1.
Content: // com. example. app. provider/table2/dataset1: The table is dataset1.
Content: // com. example. app. provider/table2/dataset2: The table is dataset2.
Content: // com. example. app. provider/table3: The table is table3.
`
If it carries an ID, it can also be identified:
content://com.example.app.provider/table3/1
1st rows in Table 3
The following URI mode:
content://com.example.app.provider/*
Match any URI in the provider
content://com.example.app.provider/table2/*
Dataset1 and dataset2 will be matched, but table1 or table3 will not be matched.
content://com.example.app.provider/table3/#
Any row in Table 3 will be matched.
content://com.example.app.provider/table3/6
The 6th rows in Table 3 are matched.
To sum up, the URI standard is: content: // or content: //. The former is for the table and the latter is for the specified row.
We can use the UriMatcher class to quickly match the content URI. Common Code:
public static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);mUriMatcher.addURI(authority,path,customMatch);
addURI()
Parses the string authority and string path as follows:content://<authority>/<path>
(The path here can contain an id .)match(URI)
Method to match the string customMatch,match(URI)
The returned value is the customMatch (addURI()
The third parameter), which can be used to respond to the data that the caller expects to access.
Other classes, such as ContentUris, Uri, and Uri. Builder. ContentUris allows you to easily add an id after a uri. Uri and Uri. builder can easily parse the Uri object and generate a new Uri object.
Inherit ContentProvider class
The ContentProvider class can manage the data in our provider. External programs can use the ContentResovler object to call the corresponding ContentProvider method to implement data operations. Therefore, we must provide corresponding methods to operate data.
Overwrite Method
We need to implement the following methods to facilitate ContentResovler to access data.
We can see that the ContentResovler has the same name for the methods of the preceding operations, and the parameters are the same.
Pay attention to the following points when overwriting methods:
- Besides
onCreate()
You must pay attention to the multi-thread security issues in other methods.
- Avoid
onCreate()
For a long time. Wait until the corresponding content is initialized as needed.
- Although we need to implement these methods, we do not need to do anything except the return value. For example, we do not want to delete data, so we only need to return the corresponding row number, instead of writing any code in the method.
Implementation
query()
Method
This method returns a Cursor object or throws an exception if it fails. If no corresponding row is found,getCount()
The Cursor object whose method is 0. Null is returned only when an internal error occurs. If you use the SQLite database to save data, you can directly callquery()
Method to return the Cursor object. If you do not use it, you must use a specific subclass of the Cursor class.
The following exceptions may be thrown during query:
- IllegalArgumentException (when an invalid URI is received)
- NullPointerException
If the SQLite database is accessedquery()
The simple implementation code is as follows:
Public class ExampleProvider extends ContentProvider {private static final UriMatcher sUriMatcher; sUriMatcher. addURI ("com. example. app. provider "," table3 ", 1); sUriMatcher. addURI ("com. example. app. provider "," table3/# ", 2); // The parameter is the public Cursor query (Uri uri, String [] projection, String selection, string [] selectionArgs, String sortOrder) {switch (sUriMatcher. match (uri) {// corresponding to table3 case 1: if (TextUtils. isEmpty (sortOrder) sortOrder = "_ id asc"; break; // corresponding to table3 case 2 with id: selection = selection + "_ ID =" uri. getLastPathSegment (); break; default: throw new IllegalArgumentException ("Unknown URI" + uri);} // The actual SQLite database query statement}
Implementation
insert()
Method
The insert method adds a new row to the specified table. The data comes from the data transmitted by the ContentValues parameter. If a column is not specified, the default value is provided (the default value depends on the provider or the database itself ).
This method returns the URI of the new row. We can use ContentUris'swithAppendId()
Add the primary key ID (usually _ ID) after the URI to construct the URI. Directly passparse()
Method.
Implementation
update()
Method
It is similar to insert.
Implementation
delete()
Method
Deletes a specified row. This method does not need to delete a row. If we use a synchronization adapter, we can mark and delete the data to be deleted first. The synchronization adapter can check the deleted rows before deleting the data from the provider, and exclude these rows for false deletion.
Implementation
getType()
Method
The following sections describe in detail.
Implementation
onCreate()
Method
When a provider is created, the system callsonCreate()
Method. We should ensure that the content initialized here is required and can be executed quickly. For non-essential and time-consuming content, it can be initialized as needed. For example, database creation and data loading can be performed again when the data is actually requested. If it is too time-consuming, it will take time for the provider to start. Obviously, this will affect the program that responds to the provider request.
MIME Type of ContentProvider
ContentProvider has two return types:
getType()
This needs to be implemented in the subclass
getStreamTypes()
If the provider provides file access, you need to implement this.
MIME Type of the table
getType()
Method returns a MIME-formatted string to describe the data type corresponding to the parameter URI. The parameter Uri can be a matching pattern rather than a detailed URI, so that we should return the MIME type related to the URL of the matching pattern.
For common types: text, HTML, JPEG,getType()
The method should return the standard MIME type.
For the URIs of a specified row or multiple rows,getType()
The method should return the MIME format specified by android:
- Type part: vnd
- Subtype:
- URI mode has only one row: android. cursor. item/
- URI mode has multiple lines: android. cursor. dir/
- Specified by Provider: vnd ..
Name should be globally unique, and type should be unique in the URI mode. Generally, the name should be the package name, And the type should be the URI-related table name.
For example, the provider permission iscom.example.app.provider
, Table name: table1, the MIME type of multiple rows of table1 is:
vnd.android.cursor.dir/vnd.com.example.provider.table1
The MIME type of a single row is:
vnd.android.cursor.item/vnd.com.example.provider.table1
Object MIME type
If the provider provides file access, you must implementgetStreamTypes()
Method. This method returns a MIME-type String Array Based on the given URI. We should filter the MIME type based on the MIME type filter parameter to return the MIME type that the external program wants to process.
For example, the provider provides image files in the format of .jpg, .png, and .gif. If a program callsgetStreamTypes()
If the filter parameter image/* is used, this method returns:
{ "image/jpeg", "image/png", "image/gif"}
If the program only uses the. JPG file, you can use the filter parameter * \/jpeg, then the method returns:
{"image/jpeg"}
If the provider does not provide a type, the method returns null.
Implementation class
A public final class is generally required to define constants: URIs, column name, MIME, or other provider-related information. This class enables the provider to establish a relationship with other programs to ensure that the provider is correctly obtained. For other programs, you can use the. jar file we provide to operate on this class, and then implement the operation provider.
Implement Content Provider permission
Pay attention to the following points:
- By default, data files stored in the device memory (not running memory) are private to our programs and providers.
- SQLite databases are private to our programs and providers.
- By default, data stored on the memory card is public. External programs can access the data. We cannot use providers to restrict access to the data.
- Opening or creating a file in the storage memory or calling the SQLite database method potentially grants other programs the permission to read and write the data. If you use the data stored in the memory as the provider's dataset, other programs have read and write permissions, but what we set in manifest does not work. The data obtained by default is private and should not be changed.
If we want to use the content provider permission to Control Data Reading, We need to store the data in internal files, SQLite databases or servers, and ensure that these files and databases are private.
Implement license
By default, no permission is set for the provider, and all programs can obtain the provider data. In the mainfest file<provider>
Tag attributes or sub-tag configurations. The license can be configured for the entire provider, a specific table, or a specific record.
Declare license<permission>
Tag, for example:
<permission android:name="com.example.app.provider.permission.READ_PROVIDER">
The detailed license settings of providers are described below:
- Single read-write provider-level permission)
This license controls the read and write permissions of the entire provider. In<provider>
Labelandroid:permission
Attribute.
- Separate read or write permissions at the provider level (Separate read and write provider-level permission
)
In<provider>
Labelandroid:readPermission
Set the read permission in<provider>
Labelandroid:writePermission
Set write permission in properties. These two licenses are better than those set for android: permission.
- Path-level permission)
Permission to read, write, or read a specified URI. In<provider>
Label<path-permission>
Sub-label settings. This level of permission is superior to the above two permissions.
Temporary License (Temporary permission)
Grant the program temporary permission to obtain data.
In<provider>
Labelandroid:grantUriPermissions
Attribute, or<provider>
Label<grant-uri-permission>
Add one or more sub-tags.
If a temporary license is used, you must callContext.revokeUriPermission()
The URI is related to the temporary license. If this attribute is set to true, The system supports authorizing temporary licenses and overwriting any other licenses (provider-level or path-level ).
If false is set<provider>
Label<grant-uri-permission>
Add one or more sub-tags. Each sub-tag specifies that one or more URIs have temporary access permission.
Intent must containFLAG_GRANT_READ_URI_PERMISSION
AndFLAG_GRANT_WRITE_URI_PERMISSION
One or two flags insetFlags()
).
If notandroid:grantUriPermissions
Attribute is considered as false.
<provider>
Tag
We know that all the four components need to be configured in the mainfest file. The ContentProvider implementation class uses<provider>
Label configuration. This tag also includes some important attributes and sub-tags:
android:authorities
Identifies the provider in this system (first identifies the application ).
android:name
The name of the ContentProvider implementation class.
Permission
It has been described in detail above, mainly including:
- `android:grantUriPermssions`;- `android:permission`;- `android:readPermission`;- `android:writePermission`。
Launch and Control Properties
android:enabled
Whether instantiation is allowed
android:exported
Whether external services can be used
android:initOrder
Integer value indicates the initialization order in the same process. The larger the value, the earlier it is initialized.
android:multiProcess
Whether instantiation is allowed in multiple processes
android:process
Process
android:syncable
Information attributes
android:icon
Icon
android:label
Name
Summary
The collaboration between ContentResovler and ContentProvider is described by querying the SQLite database as an example:
ContentResovler objectquery()
The parameter URI in the method. The permission authority in the URI can be used to find the corresponding ContentProvider implementation class, instantiate the class and callquery()
Method, inquery()
TheUriMatcher.match()
The method matches the Uri, returns the method to the SQLite database, returns the Cursor, and then returns the Cursor to the caller through the ContentProvider instance. The permission can be used to determine a provider. Therefore, a program can contain multiple providers.