ContentProvider is one of the four components of Android. It manages data stored in a structured way, encapsulates data (tables) in a relatively secure way, and provides a simple processing mechanism and a unified access interface for other programs to call.

There are five data storage options for Android: Shared Preferences, Network Storage, file storage, external storage, and SQLite. However, these stores are generally shared within a single application. Sometimes we need to manipulate some data of other applications and use contentProviders. And Android provides a default ContentProvider for common data (audio, video, images, contacts, etc.).

 

To achieve communication with other contentProviders, we must first find the corresponding ContentProvider to match. The ContenProvider in Android uses a ContentResolver to match and communicate with other contentProviders through URIs.

URI (Uniform Resource Identifier)

Other applications use a ContentResolver to access data provided by a ContentProvider, but a ContentResolver uses a URI to locate the data it wants to access, so we need to understand the URI first. Universal Resource Identifier (URI). If you have used Android’s implicit launch, you will know that during implicit launch, we also use the URI to locate the Activity we need to open and can pass parameters in the URI.

A URI assigns a name to each resource in the system, such as call logs. Each ContentProvider has a common URI that represents the data provided by the ContentProvider. The format of the URI is as follows:

/ / rules [scheme:] [/ / host: port] [path]? [query] / / sample content: / / com. Wang. The provider. Myprovider/tablename/id:Copy the code
  1. The standard prefix (Scheme) — content:// is used to indicate that a Content Provider controls this data;
  2. URI identifier (host: port) — com. Wang. The provider. Myprovider, used to uniquely identifies the ContentProvider, external caller can find it according to the logo. For third-party applications, the URI identifier must be a full, lowercase class name in order to be unique. This identity is specified in the authorities attribute of the element, typically the package that defines the ContentProvider. Class name;
  3. Tablename (path) — tablename (name of the table in the database that you want to operate on);
  4. Record ID(query) — ID, if the URI contains the ID of the record to be retrieved, the corresponding data of that ID is returned, if there is no ID, the whole data is returned;

The third part, path, is further explained to represent the data to be manipulated. The construction should be based on actual project requirements. Such as:

  • Select * from table tablename where id = 11; /tablename/11;
  • Alter table tablename alter table tablename/11/name;
  • Operation on all records in the tablename table: /tablename;
  • To operate data from files, XML, or other storage methods, for example, / tablename/name under the Tablename node in the XML file.
  • To convert a string to a Uri, use the parse() method of the Uri class, as in:
Uri Uri = Uri. Parse (" content: / / com. Wang. The provider. Myprovider/tablename ");Copy the code

Here’s another example:

http://www.baidu.com:8080/wenku/jiatiao.html?id=123456&name=jack
Copy the code

Each part of the URI can be obtained by android code. Here is an example of how to obtain each part of the URI:

  • GetScheme () : Gets the Scheme string part of the Uri, in this case HTTP
  • GetHost () : gets the Host string in Authority, that is, www.baidu.com
  • GetPost () : Gets the Authority Port string, 8080
  • GetPath () : Gets the path part of the Uri, wenku/jiatiao.html
  • GetQuery () : Gets the query part of the Uri, id=15&name=du

MIME

MIME is a file with a specific extension that can be opened in an application, just like when you view a PDF file in a browser, the browser will select the appropriate application to open it. Similar to how HTTP works on Android, the ContentProvider returns the MIME type based on the URI, and the ContentProvider returns a two-part string. A MIME type usually consists of two parts, such as:

text/html
text/css
text/xml
application/pdf
Copy the code

Divided into types and subtypes, Android follows a similar convention for defining MIME types. The Android MIME type for each content type comes in two forms: multiple records (collections) and single records.

  • Collection Record (DIR) :
VND. Android. Cursor. Dir/customCopy the code
  • Single record (item)
VND. Android. The cursor. The item/customCopy the code

VND indicates that these types and subtypes have a non-standard, vendor-specific form. In Android, the type is fixed and cannot be changed. It can only be distinguished as a collection or a single specific record. The subtype can be filled in according to the format itself.

When an Intent is used, MIME is used to open the qualifying activity based on the Mimetype.

UriMatcher

Uris represent data to be manipulated, and urIs need to be parsed when retrieving data during development. Android provides two utility classes for manipulating URIs: UriMatcher and ContentUris. Mastering their basic concepts and how to use them is a necessary skill for an Android developer.

The UriMatcher class is used to match urIs as follows:

  • Register the Uri paths that need to be matched as follows:
UriMatcher sMatcher = new UriMatcher(urimatcher.no_match); / / if the match () method of matching "content: / / com. Wang. The provider. Myprovider/tablename" path, Return matching code for 1 sMatcher. AddURI (" content: / / com. Wang. The provider. Myprovider ", "tablename", 1); / / if the match () method to match the content: / / com. Wang. The provider. Myprovider tablename / 11 path, Return matching code for 2 sMatcher. AddURI (" com. Wang. The provider. Myprovider ", "tablename / #", 2);Copy the code

Here the addURI is used to register the two URIs needed; Note that when the second URI is added, the id after the path takes the form of a wildcard “#”, indicating that it is OK as long as the first three parts match.

  • After registering the URIs to be matched, the smatcher.match (Uri) method can be used to match the entered Uri. If a match is made, the matching code is returned, which is the third parameter passed in when the addURI() method is called.
switch (sMatcher.match(Uri.parse("content://com.zhang.provider.yourprovider/tablename/100"))) {
    case 1:
        //match 1, todo something
        break;
    case 2
        //match 2, todo something
        break;
    default:
        //match nothing, todo something
        break;
}
Copy the code

ContentUris

The ContentUris class is used to manipulate the ID part after the Uri path. It has two useful methods: withAppendedId(Uri Uri, long ID) and parseId(Uri Uri).

  • WithAppendedId (Uri Uri, long ID) is used to add the ID part to the path:
Uri Uri = Uri. Parse (" content: / / cn. Scu. Myprovider/user ") / / generates the Uri of the: content://cn.scu.myprovider/user/7 Uri resultUri = ContentUris.withAppendedId(uri, 7);Copy the code
  • ParseId (Uri Uri) gets the ID part from the path:
Uri Uri = Uri. Parse (" content: / / cn. Scu. Myprovider/user / 7 ") / / get the results as follows: 7 long personid = ContentUris. ParseId (Uri).Copy the code

ContentProvider main method

ContentProvider is an abstract class. If we want to develop our own ContentProvider, we need to inherit this class and duplicate its methods. The main methods to implement are as follows: \

  • Public Boolean onCreate () :Used when creating a ContentProvider
  • Public Cursor query () :The data used to query the specified URI returns a Cursor
  • Public Uri insert () :Use to add data to the ContentProvider of the specified URI
  • Public int the delete () :Deletes data from the specified URI
  • Public int the update () :The user updates the data for the specified URI
  • Public String getType () :Used to return the data MIME type in the specified Uri

The data access methods INSERT, DELETE, and UPDATE may be called by multiple threads at the same time and must be thread-safe.

If the data being operated on is of the collection type, the MIME type string should start with vnd.Android.cursor.dir /,

  • To get all the tablename records: the Uri for the content: / / com. Wang. The provider. Myprovider/tablename, then returns the MIME type of the string should be as follows: VND. Android. Cursor. Dir/table.

If the data to be manipulated is non-collection data, the MIME type string should start with vnd.Android.cursor.item /,

  • To get the id of 10 tablename record, the Uri for the content: / / com. Wang. The provider. Myprovider/tablename / 10, then the return of the MIME type string as follows: VND. Android. The cursor. The item/tablename.

Method Usage Examples

The code to operate on the data in the ContentProvider using the ContentResolver is as follows:

ContentResolver resolver = getContentResolver(); Uri uri = Uri.parse("content://com.wang.provider.myprovider/tablename"); // Add a record ContentValues values = new ContentValues(); values.put("name", "wang1"); values.put("age", 28); resolver.insert(uri, values); Cursor Cursor = resolver.query(URI, NULL, NULL, null, "tablename data"); while(cursor.moveToNext()){ Log.i("ContentTest", "tablename_id="+ cursor.getInt(0)+ ", name="+ cursor.getString(1)); } // set name = new ContentValues(); // set name = new ContentValues(); updateValues.put("name", "zhang1"); Uri updateIdUri = ContentUris.withAppendedId(uri, 2); resolver.update(updateIdUri, updateValues, null, null); / / delete id of 2 records, namely field age Uri deleteIdUri = ContentUris. WithAppendedId (Uri, 2); resolver.delete(deleteIdUri, null, null);Copy the code

Monitoring data changes

If a visitor to the ContentProvider needs to know that data has changed, the visitor registered with the URI can be notified by calling getContentResolver().notifyChange(URI, NULL) when the data has changed. Give only the code for the listening part of the class:

public class MyProvider extends ContentProvider { public Uri insert(Uri uri, ContentValues values) { db.insert("tablename", "tablenameid", values); getContext().getContentResolver().notifyChange(uri, null); }}Copy the code

Visitors must listen for data (described by URI) using ContentObserver, and when they hear about changes to the data, the system calls ContentObserver’s onChange() method:

getContentResolver().registerContentObserver(Uri.parse("content://com.ljq.providers.personprovider/person"),
       true, new PersonObserver(new Handler()));
public class PersonObserver extends ContentObserver{
   public PersonObserver(Handler handler) {
      super(handler);
   }
   public void onChange(boolean selfChange) {
      //to do something
   }
}
Copy the code

Example is given to illustrate

The data source is SQLite, and the ContentProvider is manipulated with the ContentResolver.

Constant.java (store some constants)

public class Constant {  
      
    public static final String TABLE_NAME = "user";  
      
    public static final String COLUMN_ID = "_id";  
    public static final String COLUMN_NAME = "name";  
       
       
    public static final String AUTOHORITY = "cn.scu.myprovider";  
    public static final int ITEM = 1;  
    public static final int ITEM_ID = 2;  
       
    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/user";  
    public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/user";  
       
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTOHORITY + "/user");  
}  
Copy the code

Dbhelper.java (operating database)

public class DBHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "finch.db"; private static final int DATABASE_VERSION = 1; public DBHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @override public void onCreate(SQLiteDatabase db) throws SQLException {// CREATE the TABLE db.execSQL("CREATE TABLE IF NOT EXISTS "+ Constant.TABLE_NAME + "("+ Constant.COLUMN_ID +" INTEGER PRIMARY KEY AUTOINCREMENT," + Constant.COLUMN_NAME +" VARCHAR NOT NULL);" ); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, Int newVersion) throws SQLException {// If you want to keep the original data, ExecSQL ("DROP TABLE IF EXISTS "+ constant.table_name +";" ); onCreate(db); }}Copy the code

Myprovider.java (custom ContentProvider)

public class MyProvider extends ContentProvider { DBHelper mDbHelper = null; SQLiteDatabase db = null; private static final UriMatcher mMatcher; Static {mMatcher = new UriMatcher(urimatcher.no_match); / / registered uri mMatcher. AddURI (Constant AUTOHORITY, Constant, TABLE_NAME, Constant. The ITEM). mMatcher.addURI(Constant.AUTOHORITY, Constant.TABLE_NAME+"/#", Constant.ITEM_ID); } @override public String getType(Uri Uri) {// Switch (mmatcher.match (Uri)) {case constant.item: return Constant.CONTENT_TYPE; case Constant.ITEM_ID: return Constant.CONTENT_ITEM_TYPE; default: throw new IllegalArgumentException("Unknown URI"+uri); } } @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub long rowId; if(mMatcher.match(uri)! =Constant.ITEM){ throw new IllegalArgumentException("Unknown URI"+uri); } rowId = db.insert(Constant.TABLE_NAME,null,values); if(rowId>0){ Uri noteUri=ContentUris.withAppendedId(Constant.CONTENT_URI, rowId); getContext().getContentResolver().notifyChange(noteUri, null); return noteUri; } throw new SQLException("Failed to insert row into " + uri); } @Override public boolean onCreate() { // TODO Auto-generated method stub mDbHelper = new DBHelper(getContext()); db = mDbHelper.getReadableDatabase(); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // TODO Auto-generated method stub Cursor c = null; switch (mMatcher.match(uri)) { case Constant.ITEM: c = db.query(Constant.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder); break; case Constant.ITEM_ID: c = db.query(Constant.TABLE_NAME, projection,Constant.COLUMN_ID + "="+uri.getLastPathSegment(), selectionArgs, null, null, sortOrder); break; default: throw new IllegalArgumentException("Unknown URI"+uri); } c.setNotificationUri(getContext().getContentResolver(), uri); return c; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub return 0; }}Copy the code

MainActivity. Java (ContentResolver operation)

public class MainActivity extends Activity { private ContentResolver mContentResolver = null; private Cursor cursor = null; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = (TextView) findViewById(R.id.tv); mContentResolver = getContentResolver(); Tv.settext (" Add initial data "); for (int i = 0; i < 10; i++) { ContentValues values = new ContentValues(); values.put(Constant.COLUMN_NAME, "fanrunqi"+i); mContentResolver.insert(Constant.CONTENT_URI, values); } tv.settext (" query data "); cursor = mContentResolver.query(Constant.CONTENT_URI, new String[]{Constant.COLUMN_ID,Constant.COLUMN_NAME}, null, null, null); if (cursor.moveToFirst()) { String s = cursor.getString(cursor.getColumnIndex(Constant.COLUMN_NAME)); Tv.settext (" first data: "+s); }}}Copy the code

Finally, in the manifest statement:

<provider android:name="MyProvider" android:authorities="cn.scu.myprovider" />
Copy the code

Conclusion:

  • How to query data through ContentProvider? Uri matching is done via ContentResolver
  • How to implement your own ContentProvider? Inherit the ContentProvider and implement the corresponding method. Declared in the manifest

Bonus: The <data> tag is implicit in the Intent

The Intent tag is not involved in the ContentProvider, but the URI is included in the Intent. If you don’t understand it, you can skip it and look at the next section, which is related to activity.

Matching rules for Data: If Data is defined in an intent-filter, the intent must also carry Data that can be matched

The syntax for data looks like this:

Data Android :scheme= "string" Android :host= "string" Android :port= "string" Android :path= "string" Android: pathPattern = "string" android: pathPrefix = "string" android: mimeType = "string" >Copy the code

Data consists of two parts: mimeType and URI. MimeType can be empty, and the URI must not be empty because there is a default value. MimeType refers to the media type, such as image/ JPEG, video/*, and other media formats

Example 1

Data matching:

<intent-filter>
     <action android:name="com.action.demo1"></action>
     <category android:name="android.intent.category.DEFAULT" />
     <data
         android:scheme="x7"
         android:host="www.360.com"
            />
 </intent-filter> 
Copy the code

In the data defined by the intent-filter file, there is only a URI but no mimeType

intent.setData(Uri.parse("x7://www.360.com"))
Copy the code

Example 2

<intent-filter>
       <action android:name="com.action.demo1"></action>
       <category android:name="android.intent.category.DEFAULT" />
       <data android:mimeType="image/*" />
 </intent-filter>
Copy the code

The scheme in the URI is content or file by default, and the host must not be empty. You can give any string ABC and it matches as follows

intent.setDataAndType(Uri.parse("content://abc"),"image/png");
Copy the code

Note: Content :// ABC :// ABC :// ABC :// ABC :// ABC :// ABC :// ABC :// ABC :// ABC Google provides a FileProvider to resolve this exception, which generates content://Uri instead of file://Uri.

Example 3

<intent-filter>
      <action android:name="com.action.demo1"></action>
      <category android:name="android.intent.category.DEFAULT" />
        <data
               android:mimeType="image/*"
               android:scheme="x7"
               android:host="www.360.com"
         />
        <data
             android:mimeType="video/*"
             android:scheme="x7"
             android:host="www.360.com"
         />
</intent-filter>
Copy the code

In the data defined by the intent-filter file, both the URI and the mimeType are matched as follows

intent.setDataAndType(Uri.parse("x7://www.360.com"),"image/png"); / / or intent. SetDataAndType (Uri. Parse (" x7://www.360.com "), "video/mpeg");Copy the code

 

The data corresponding to the data tag carried in Inent can be found in a group of intent-filters, indicating that the match is successful. The data tag can also have multiple groups of data in an Intent-filter

Implicit start to prevent matching failure can be detected in advance:

The judgment method is as follows:

PackageManager mPackageManager = getPackageManager(); // Return an ACT of the best match in a successful match, The intent calls for an action, Data, etc are set up ResolveInfo info = mPackageManager. ResolveActivity (intent, PackageManager. MATCH_DEFAULT_ONLY); / / return all matching act information of success, is a collection of a List < ResolveInfo > infoList = mPackageManager. QueryIntentActivities (intent, PackageManager.MATCH_DEFAULT_ONLY);Copy the code

StartActivity succeeds as long as the return value of the above two methods is not null

 

Reference article:

ContentProvider data access details

ContentProvider is fully parsed and used