Explain the use of ContentProvider

Posted by dgrinberg on Thu, 27 Jun 2019 00:30:24 +0200

Preface

In Android, data sharing between applications is a very common thing, and the typical scenario is to read the phone address book. In order to share data safely and efficiently, Android provides ContentProvider, a component called ContentProvider. This article briefly explains the use of ContentProvider.

Accessing data in other applications

In fact, there are some content providers in mobile phones. It is also very important to learn how to access data in other applications through these content providers. In fact, with the help of ContentResolver, we can easily access the shared data provided by other applications. To get an instance of ContentResolver, simply call the getContentResolver method of Context. The prototype of this method is as follows:

public ContentResolver getContentResolver()

Content Uri

It should be noted that content providers differentiate the data they want to access through Uri objects, rather than table names in the database. The reason is also obvious. Tables may have the same name in many applications, so they cannot be used as unique identifiers. The Uri string consists of two parts, authority and path. Authority is generally composed of application package name +. provider, and path is the name of the data table we want to access. Of course, in order to show that this is a Uri string, it is often necessary to add content:///in the header. A typical example is as follows:

content://com.example.providerlibrary.provider/Book

The Uri string above indicates that you want to access the Book table in the provider library. Of course, only one Uri string is not enough. We also need to parse the string into Uri objects through Uri's parse method. Through Uri objects, you can access shared data in other applications. The prototype of Parse method is as follows:

public static Uri parse(String uriString)

Add data

The operation of adding data is implemented by the insert method of ContentResolver, whose prototype is as follows:

public final Uri insert(Uri url,ContentValues values)

The first parameter is Uri, and the second parameter is the data content that needs to be inserted. ContentValues is similar to HashMap in that it stores data through key-value pairs. The insert method returns the Uri of the inserted data in the target data table. Here is an example of adding data:

ContentValues values=new ContentValues();
values.put("name","Java");
values.put("author","Bill");
values.put("price","60");
Uri uri=Uri.parse("content://com.example.providerlibrary.provider/Book");
getContentResolver().insert(uri,values);

Delete data

The operation of deleting data is implemented by the delete method of ContentResolver, whose prototype is as follows:

public final int delete(Uri url,String where,String[] selectionArgs)

The first parameter is Uri, and the second and third parameters are deletion restrictions. If all the rows are passed in null, they represent deletion of all rows in the data table. The delete method returns the number of rows deleted. Here is an example of deleting data:

Uri uri=Uri.parse("content://com.example.providerlibrary.provider/Book");
getContentResolver().delete(uri,"name=?",new String[]{"Java"});

The above actions represent the deletion of all data in the Book table whose name field is "Java".

Update data.

The operation of updating data is implemented by ContentResolver's update method. Its prototype is as follows:

public final int update(Uri uri,ContentValues values,String where,String[] selectionArgs)

The first parameter is Uri, the second parameter is for updating data, the third and fourth parameters are for updating constraints, and if all null is passed in, all rows in the update table are represented. The update method returns the number of rows updated. Here is an example of updating data:

ContentValues values=new ContentValues();
values.put("price","70");
Uri uri=Uri.parse("content://com.example.providerlibrary.provider/Book");
getContentResolver().update(uri,values,"name=?",new String[]{"Java"});

The above operation means finding all the data in the Book table whose name field is "Java" and updating their price field to "70".

Query data

The operation of querying data is implemented by ContentResolver's query method. Its prototype is as follows:

public final Cursor query(Uri uri,String[] projection,String selection,
    String[] selectionArgs,String sortOrder)

The first parameter is Uri. The second parameter is the column you want to query, and passing null represents querying all columns. The third and fourth parameters are query constraints, and if they are passed in null, they represent all rows in the query data table. The fifth parameter is the sort rule, which is passed in null to represent the default sort. The query method returns the Cursor object, through which we can access the queried data. Here is an example of querying data:

Uri uri=Uri.parse("content://com.example.providerlibrary.provider/Book");
Cursor cursor=getContentResolver().query(uri,new String[]{"author","price"},
        "name=?",new String[]{"Java"},null);
if(cursor.moveToFirst()){
    do{
        Log.d("MainActivity",cursor.getString(cursor.getColumnIndex("name")));
        Log.d("MainActivity",cursor.getString(cursor.getColumnIndex("price")));
    }while(cursor.moveToNext());
}
cursor.close();

The above operations represent querying all the data in the Book table whose name field is "Java" and returning only the contents of their author and price fields. Finally, the returned results are sorted by default. Through the do-while loop, we get all the queried data with the help of Cursor objects.

practical application

As we said at the beginning of this article, a typical application scenario of content providers is to read the data of mobile phone address books. For this, you can refer to this blog:

Read system contacts (dig holes to fill)

Custom Content Provider

Learned how to access data in other applications, now let's learn how to create our own content providers. In this way, other applications can access our shared data through ContentResolver.

How to Create a Content Provider

The way to create a content provider is simple. Just define a class to inherit ContentProvider. The sample code is as follows:

public class BookContentProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        return 0;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        return null;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }
}

ContentProvider is an abstract class, and we need to rewrite these six methods. Their functions are as follows:

  • onCreate: When the content provider is initialized, it will be called. Returning true means successful initialization, and returning fasle means failed initialization. As long as there is ContentResolver outside trying to access the application, the content provider initializes.
  • insert, delete, update, query: add, delete and modify.
  • getType: Returns the MIME type string of Uri corresponding data, details of which will be described later.

The parameters and return values of insert, delete, update, query have been explained in the ContentResolver section, which will not be repeated here.

How to Match Uri

As you can see, Uri parameters are required for the last five methods of ContentProvider. Therefore, we need a means to match the incoming Uri and to interpret its intentions. In fact, these tasks can be easily accomplished with the help of UriMatcher.

Before explaining UriMatcher, we need to know that Uri has two types. In addition to the Uri described earlier, you can add an id at the end of the Uri string to represent the data of a specific row in the data table. The sample code is as follows:

content://com.example.providerlibrary.provider/Book
content://com.example.providerlibrary.provider/Book/1

The first Uri string represents all data in the Book table, and the second Uri string represents data with id 1 in the Book table.

The main methods in UriMatcher are addURI and match. Their prototypes are as follows:

public void addURI(String authority, String path, int code)
public int match(Uri uri)

The addURI method is used to add matching Uri to UriMatcher. The first and second parameters are authority and path respectively, and the third parameter is custom code. The wildcards (# and *) can be used in path, and they differ as follows:

  • * Match any number of arbitrary characters
  • # Match any number of numbers

Using these two wildcards can express relatively rich intentions. The sample code is as follows:

content://com.example.providerlibrary.provider/*
content://com.example.providerlibrary.provider/Book/#

The first Uri matches all data tables in the provider library, and the second Uri matches all rows in the Book table.

The match method is used to match the incoming Uri and return the custom code corresponding to the successfully matched Uri. The following is a demonstration of how these two methods are used:

public class BookContentProvider extends ContentProvider {
    private static final int BOOK=0;
    private static final int BOOK_ITEM=1;
    private static final int AUTHOR=2;
    private static final int AUTHOR_ITEM=3;

    private static final String AUTHORITY="com.example.providerlibrary.provider";

    private static UriMatcher uriMatcher;

    static{//Initialization
        uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY,"Book",BOOK);
        uriMatcher.addURI(AUTHORITY,"Book/#",BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY,"Author",AUTHOR);
        uriMatcher.addURI(AUTHORITY,"Author/#",AUTHOR_ITEM);
    }
    ......
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        switch(uriMatcher.match(uri)){
            case BOOK:
                //Access the Book table
                break;
            case BOOK_ITEM:
                //Accessing a data in the Book table
                break;
            case AUTHOR:
                //Access Author table
                break;
            case AUTHOR_ITEM:
                //Accessing a piece of data in the Author table
                break;
            default:
                break;
        }
        return 0;
    }
    ......
}

As you can see, we initialized UriMatcher in a static block and added four Uri data through the addURI method. In the delete method below, the matching method of UriMatcher returns the custom code matching Uri. With these custom codes, we succeeded in parsing Uri's intentions. In other ways, this switch-case approach is also used to match Uri, and the sample code is no longer presented here.

How to Implement getType Method

The getType method is used to return the MIME type corresponding to Uri. Android specifies the data format returned by the getType method, mainly in the following two formats:

vnd.android.cursor.dir/vnd.<authority>.<path>
vnd.android.cursor.item/vnd.<authority>.<path>

If the incoming Uri accesses a table, the first format is used. If the incoming Uri accesses data corresponding to an id in the table, the second format is used. The sample code is as follows:

Uri: content://com.example.providerlibrary.provider/Book
getType: vnd.android.cursor.dir/vnd.com.example.providerlibrary.provider.Book

Uri: content://com.example.providerlibrary.provider/Book/1
getType: vnd.android.cursor.item/vnd.com.example.providerlibrary.provider.Book

How to Prevent Privacy Data Leakage

Since the outside world can access the data in our application through ContentProvider, how to prevent the leakage of private data has become an important issue. But thanks to the good design of ContentProvider, we don't need to worry about that anymore. As you can see, all methods of accessing data in Content Provider need Uri matching first. As long as we don't add Uri corresponding to privacy data to UriMatcher, the outside world can't access these data in any case.

A practical example

The use of ContentProvider is outlined above, followed by a specific example. Note that in this example, you need to use the SQLite database. For the use of SQLite, you can refer to this blog:

Details of SQLite database storage in Android

public class BookContentProvider extends ContentProvider {
    private static final int BOOK=0;
    private static final int BOOK_ITEM=1;
    private static final int AUTHOR=2;
    private static final int AUTHOR_ITEM=3;

    private static final String AUTHORITY="com.example.providerlibrary.provider";

    private BookOpenHelper bookOpenHelper;
    private static UriMatcher uriMatcher;

    static{//Initialization
        uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY,"Book",BOOK);
        uriMatcher.addURI(AUTHORITY,"Book/#",BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY,"Author",AUTHOR);
        uriMatcher.addURI(AUTHORITY,"Author/#",AUTHOR_ITEM);
    }

    @Override
    public boolean onCreate() {
        bookOpenHelper=new BookOpenHelper(getContext(),"ProviderDemo.db",null,1);
        return true;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase database=bookOpenHelper.getWritableDatabase();
        long dataId=0;//Data id after inserting data into a table
        Uri insertUri=null;
        switch(uriMatcher.match(uri)){
            case BOOK:
            case BOOK_ITEM:
                dataId=database.insert("Book",null,values);
                insertUri=Uri.parse("content://com.example.providerlibrary.provider/Book/"+dataId);
                break;
            case AUTHOR:
            case AUTHOR_ITEM:
                dataId=database.insert("Author",null,values);
                insertUri=Uri.parse("content://com.example.providerlibrary.provider/Author/"+dataId);
                break;
            default:
                break;
        }
        return insertUri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase database=bookOpenHelper.getWritableDatabase();
        int deleteNum=0;//Number of deleted data bars
        switch(uriMatcher.match(uri)){
            case BOOK:
                //Access the Book table
                deleteNum=database.delete("Book",selection,selectionArgs);
                break;
            case BOOK_ITEM:
                //Accessing a data in the Book table
                String bookDeleteId=uri.getPathSegments().get(1);//id of deleted data
                deleteNum=database.delete("Book","id=?",new String[]{bookDeleteId});
                break;
            case AUTHOR:
                //Access Author table
                deleteNum=database.delete("Author",selection,selectionArgs);
                break;
            case AUTHOR_ITEM:
                //Accessing a piece of data in the Author table
                String authorDeleteId=uri.getPathSegments().get(1);//id of deleted data
                deleteNum=database.delete("Author","id=?",new String[]{authorDeleteId});
                break;
            default:
                break;
        }
        return deleteNum;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        SQLiteDatabase database=bookOpenHelper.getWritableDatabase();
        int updateNum=0;//Number of updated data bars
        switch (uriMatcher.match(uri)){
            case BOOK:
                updateNum=database.update("Book",values,selection,selectionArgs);
                break;
            case BOOK_ITEM:
                String bookUpdateId=uri.getPathSegments().get(1);//Updated data id
                updateNum=database.update("Book",values,"id=?",new String[]{bookUpdateId});
                break;
            case  AUTHOR:
                updateNum=database.update("Author",values,selection,selectionArgs);
                break;
            case AUTHOR_ITEM:
                String authorUpdateId=uri.getPathSegments().get(1);
                updateNum=database.update("Author",values,"id=?",new String[]{authorUpdateId});
                break;
            default:
                break;
        }
        return updateNum;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        SQLiteDatabase database=bookOpenHelper.getReadableDatabase();
        Cursor cursor=null;//Cursor object for return
        switch (uriMatcher.match(uri)){
            case BOOK:
                cursor=database.query("Book",projection,selection,selectionArgs,
                        null,null,sortOrder);
                break;
            case BOOK_ITEM:
                String bookQueryId=uri.getPathSegments().get(1);//id for query
                cursor=database.query("Book",projection,"id=?",new String[]{bookQueryId},
                        null,null,sortOrder);
                break;
            case AUTHOR:
                cursor=database.query("Author",projection,selection,selectionArgs,
                        null,null,sortOrder);
                break;
            case AUTHOR_ITEM:
                String authorQueryId=uri.getPathSegments().get(1);//id for query
                cursor=database.query("Author",projection,"id=?",new String[]{authorQueryId},
                        null,null,sortOrder);
                break;
            default:
                break;
        }
        return cursor;
    }

    @Override
    public String getType(Uri uri) {
        String type="";
        switch (uriMatcher.match(uri)){
            case BOOK:
                type="vnd.android.cursor.dir/vnd."+AUTHORITY+".Book";
                break;
            case BOOK_ITEM:
                type="vnd.android.cursor.item/vnd."+AUTHORITY+".Book";
                break;
            case AUTHOR:
                type="vnd.android.cursor.dir/vnd."+AUTHORITY+".Author";
                break;
            case AUTHOR_ITEM:
                type="vnd.android.cursor.item/vnd."+AUTHORITY+"Author";
                break;
            default:
                break;
        }
        return type;
    }
}

Most of the knowledge points in the above examples have already been mentioned in the preceding section, and will not be repeated here. Only one point needs to be explained, that is, how to parse the id string from Uri with id. As you can see, we used Uri's getPathSegments method in our code. The prototype of the method is as follows:

public abstract List<String> getPathSegments();

This method divides Uri's path section by / and returns a List < String > object. With this List, we can get the id string. In the example above, the second element in the returned List is the id string.

Project demo download address

The demo download address for the above example is given below. ProviderDemo

Topics: Database Android Java SQLite