Android content provider in 2020

Junior to intermediate

1. What is the content provider?

Content Provider is mainly used to realize the function of data sharing between different applications. It provides a complete set of mechanism, which allows one program to access the data in another program, and also ensures the security of the accessed data. Currently, using content providers is the standard way for Android to share data across programs.
   different from the two global readable and writable operation modes in file storage and SharedPreferences storage, the content provider can choose which part of the data to share, so as to ensure that the privacy data in our program will not be disclosed.

2. Use of content providers & what content providers are provided by the system

2.1 use of content providers

We usually use content providers to query data:

Cursor cursor = getContentResolver().query(final Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)

  • uri, which specifies to query a table under a program
  • Project, specifying the column name of the query
  • selection, which specifies the query condition, equivalent to the condition after where in the sql statement
  • selectionArgs, which provides specific values to placeholders in the selection
  • orderBy, specify the sorting method of query results
  • Cancelationsignal, canceling the semaphore of the operation in progress

   you who have written SQLite code must be very familiar with this method! When you see the principle and mechanism of ContentProvider, you will realize it!

   if you want to access the shared data in the content provider, you must use the covertresolver class, and you can get an instance of this class through the getContentResolver() method in the Context. A series of methods are provided in ContentResolver for CRUD (add, delete, modify and query) operation of data, among which insert() method is used to add data, update() method is used to update data, delete() method is used to delete data, query() method is used to query data. It seems that SQLite database operation has some problems?
                           . The content URI establishes a unique identifier for the data in the content provider, which consists of two parts: authority and path. Authority is used to distinguish different applications. In general, in order to avoid conflicts, it uses the way of package name to name. For example, if the package name of a program is com.example.app, the corresponding authority of the program can be named com.example.app.provider. Path is used to distinguish different tables in the same application, which is usually added after authority. For example, there are two tables in the database of a program: table1 and table2. In this case, you can name the path as / table1 and / table2 respectively, and then combine the authority and path. The URI of the content becomes com.example.app.provider/table1 and com.example.app.provider/table2. However, at present, it is difficult to recognize that these two strings are two content URIs. We need to add a protocol declaration to the head of the string. Therefore, the most standard format of content URI is as follows:

content://com.example.app.provider/table1
content://com.example.app.provider/table2

   after getting the content URI string, we need to parse it into a URI object before we can pass it in as a parameter. The parsing method is also quite simple, and the code is as follows:

Uri uri = new Uri.parse("content://com.example.app.provider/table1");

   just call the static method parse() of URI to parse the content URI string into a URI object.
   now, we can query the data in the table1 table through the Uri object. The code is as follows:

Cursor cursor = getContentResolver()
                .query(
                       uri,projection,selection,selectionArgs,sortOrder
                 );

The parameters received by the   query() method are similar to those received by the query() method in SQLiteDatabase, but in general, this is a little simpler. After all, this is to access data in other programs, so it is unnecessary to build complex query statements. The subscript explains in detail the parameters received by query in the content provider:

After the query is completed, a Cursor object will still be returned. At this time, we can read the data from the Cursor object one by one. The idea of reading is still to traverse the Cursor object, and then take out the data one by one. The code is as follows:

if(cursor != null){//Note that there must be a void determination here, because it is possible that the table you want to query does not exist at all
    while(cursor.moveToNext()){
        String column1 = cursor.getString(cursor.getColumnIndex("column1"));
        int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
    }
}

The                      

//Add data
ContentValues values = new ContentValues();
values.put("Column1","text");
values.put("Column2","1");
getContextResolver.insert(uri,values);

//Delete data
getContextResolver.delete(uri,"column2 = ?",new String[]{ "1" });

//Update data
ContentValues values = new ContentValues();
values.put("Column1","Change data");
getContextResolver.update(uri,values,"column1 =  ? and column2 = ?",new String[]{"text","1"});

3. How to create content providers that belong to their own applications?

                            . There are six abstract methods in the ContentProvider class. When we use a subclass to inherit it, we need to rewrite all the six methods. Create a new MyProvider inherits the ContentProvider class. The code is as follows:

public class MyProvider extends ContentProvider {

    [@Override](https://my.oschina.net/u/1162528)
    public boolean onCreate() {
        return false;
    }

    [@Override](https://my.oschina.net/u/1162528)
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        return null;
    }//query

    [@Override](https://my.oschina.net/u/1162528)
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }//Add to

    [@Override](https://my.oschina.net/u/1162528)
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        return 0;
    }//To update

    [@Override](https://my.oschina.net/u/1162528)
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }//delete

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

                         

   1.onCreate() method:
   called when the content provider is initialized. Usually, the creation and upgrade of the database will be completed here. Return true to indicate successful content provider initialization, and false to indicate failure. Note that the content provider is initialized only if there is a ContentResolver trying to access data in our program.

   2.query() method:
    queries data from the content provider. The uri parameter is used to determine which table to query, the project parameter is used to determine which column to query, the selection and selectionArgs parameters are used to constrain which rows to query, the sortOrder parameter is used to sort the results, and the results of the query are stored in the Cursor object and returned.

   3.insert() method:
   add a piece of data to the content provider. The URI parameter is used to determine the table to be added, and the data to be added is saved in the values parameter. When the addition is complete, a URI representing the new record is returned.

   4.update() method:
Update existing data in the content provider. The uri parameter is used to determine which table data to update. The new data is stored in the values parameter. The selection and selectionArgs parameters are used to constrain which rows to update. The number of affected rows will be returned as the return value.

   5.delete() method:
   delete data from the content provider. The uri parameter is used to determine which table data to delete. The selection and selectionArgs parameters are used to constrain which rows to delete. The number of deleted rows will be returned as the return value.

   6.getType() method:
   returns the corresponding MIME type based on the incoming content URI.

   as you can see, almost every method has the Uri parameter, which is passed when the ContentResolver's add, delete, modify and query method is called. Now, we need to parse the incoming Uri parameter, from which we can analyze the table and data that the call places the expected access.
   recall that a standard content URI is written as follows:

content://com.example.app.provider/table1  

   this means that the caller expects to access the data in the table1 table of the application com.example.app. In addition, we can add an id after the content URI as follows:

content://com.example.app.provider/table1/1

   this means that the caller expects to access the data with id 1 in the table1 table of the application com.example.app. There are two main formats of    content URI. The end of the path indicates the expected access to all data in the table, and the result of id indicates the expected access to data with corresponding id in the table. We can use wildcards to match the content URIs of these two formats. The rules are as follows:

  • *: means to match any character of any length.
  • #: any number that matches any length.

Therefore, a content URI format that can match any table can be written as:

content://com.example.app.provider/*

   and a content URI format that can match any row of data in the table table can be written as:

content://com.example.app.provider/table1/#

   next, we can easily match the content URI with the help of UriMatcher. UriMatcher provides an addURI() method, which takes three parameters and can pass in authority,path and a custom code. The custom code is actually a final int value. In this way, when the match() method of UriMatcher is called, a URI object can be passed in. The return value is a custom code that can match the URI object. With this code, we can determine which data in the table the caller expects to access. Modify the above MyProvider code as follows:

public class MyProvider extends ContentProvider {

    public static fianl int TABLE1_DIR = 0;

    public static fianl int TABLE1_ITEM = 1;

    public static fianl int TABLE2_DIR = 2;

    public static fianl int TABLE2_ITEM = 3;

    private static UriMatcher uriMatcher;

    static{

        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM);
        uriMatcher.addURI("com.example.app.provider","table2",TABLE2_DIR);
        uriMatcher.addURI("com.example.app.provider","table2/#",TABLE2_ITEM);

    }


    ...

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        switch(uriMatcher.match(uri)){
         case TABLE1_DIR:
                //Query data in table1
                break;

         case TABLE1_ITEM:
                //Query single data in table1
                break;

         case TABLE2_DIR:
                //Query data in table2
                break;

         case TABLE2_ITEM:
                //Query single data in table2
                break;
             
        }

        
        return null;

    }//query

    ...
}

                               the above code is just an example of query() method. In fact, the implementation of insert() method, update(),delete() method.
                    . It is a method that all content providers must provide to get the MIME type corresponding to the URI object. The mime string corresponding to a content URI is mainly composed of three parts. Android has the following format requirements for these three parts:

  • Must start with vnd
  • If the content URI ends with a path, it is followed by android.cursor.dir /, if the content URI ends with an id, it is followed by android.cursor.item /.
  • Finally, connect to vnd. < authority > < Path >

So, for The content URI of content://com.example.app.provider/table1 can be written as follows:

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

On the contrary The content URI of content://com.example.app.provider/table1/1 can be written as follows:

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

   now, we can continue to improve the content of MyProvider class. This time, we implement the logic of getType() method, and the code is as follows:

public class MyProvider extends ContentProvider{

    ...
  
    @Override
    public String getType(Uri uri){
        switch(uriMatcher.match(uri)){
            case TABLE1_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"
                break;

             case TABLE1_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.app.provider.table1 "
                break;

             case TABLE2_DIR:
                 return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"
                break;

             case TABLE2_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.app.provider.table2 "
                break;
        }
    } 

    ...

}

   here, a complete content provider is created, and now any application can use it ContentResolver to access the data in our program. So, as mentioned above, how can we ensure that the privacy data will not leak out? In fact, thanks to the good mechanism of content provider, this problem has been solved unconsciously. Because all CRUD operations must be matched to the corresponding content URI format, of course, we can't add the URI of privacy data to the UriMatcher, so this part of data can't be accessed by external programs at all, and the security problem doesn't exist.
                       The function of sequence data sharing.

It's not over yet? We all know that the four components need to be registered in the Android manifest.xml file. Now that the completed content provider is written, the next step is to register in the Android manifest.xml file, and then the content provider can be used. Let's take an example to explain. The encapsulation code of a standard content provider is as follows:

public class DatabaseProvider extends ContentProvider {

    public static final int BOOK_DIR = 0;

    public static final int BOOK_ITEM = 1;

    public static final int CATEGORY_DIR = 2;

    public static final int CATEGORY_ITEM = 3;

    public static final String AUTHORITY = "com.example.databasetest.provider";

    private static UriMatcher uriMatcher;

    private MyDatabaseHelper dbHelper;//Content provider database support

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
        uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
    }

    @Override
    public boolean onCreate() {
        dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);//Create the database to be used by the content provider
        return true;//true must be returned here, otherwise the content provider cannot be used
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
    String[] selectionArgs, String sortOrder) {
        // Query data
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        switch (uriMatcher.match(uri)) {
         case BOOK_DIR:
             cursor = db.query("Book", projection, selection, selectionArgs,
                               null, null, sortOrder);
             break;

         case BOOK_ITEM:
             String bookId = uri.getPathSegments().get(1);
             cursor = db.query("Book", projection, "id = ?", new String[]
                              { bookId }, null, null, sortOrder);
             break;

         case CATEGORY_DIR:
             cursor = db.query("Category", projection, selection,
                              selectionArgs, null, null, sortOrder);
             break;

         case CATEGORY_ITEM:
             String categoryId = uri.getPathSegments().get(1);
             cursor = db.query("Category", projection, "id = ?", new String[]
                              { categoryId }, null, null, sortOrder);
             break;

         default:
             break;
        }
       
        return cursor;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // Add data
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (uriMatcher.match(uri)) {
            case BOOK_DIR:

            case BOOK_ITEM:
                long newBookId = db.insert("Book", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" +
                                      newBookId);
                break;

            case CATEGORY_DIR:

            case CATEGORY_ITEM:
                long newCategoryId = db.insert("Category", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" +
                                      newCategoryId);
                break;

            default:
                break;
        }

        return uriReturn;
     }

     @Override
     public int update(Uri uri, ContentValues values, String selection,
                       String[] selectionArgs) {
         // Update data
         SQLiteDatabase db = dbHelper.getWritableDatabase();
         int updatedRows = 0;
         switch (uriMatcher.match(uri)) {

             case BOOK_DIR:
             updatedRows = db.update("Book", values, selection, selectionArgs);
             break;

             case BOOK_ITEM:
             String bookId = uri.getPathSegments().get(1);
             updatedRows = db.update("Book", values, "id = ?", new String[]
                                     { bookId });
             break;

             case CATEGORY_DIR:
             updatedRows = db.update("Category", values, selection,
                                     selectionArgs);
             break;

             case CATEGORY_ITEM:
             String categoryId = uri.getPathSegments().get(1);
             updatedRows = db.update("Category", values, "id = ?", new String[]
                                     { categoryId });
             break;

             default:
             break;
         }

         return updatedRows;

     }

     @Override
     public int delete(Uri uri, String selection, String[] selectionArgs) {
         // Delete data
         SQLiteDatabase db = dbHelper.getWritableDatabase();
         int deletedRows = 0;
         switch (uriMatcher.match(uri)) {

             case BOOK_DIR:
             deletedRows = db.delete("Book", selection, selectionArgs);
             break;

             case BOOK_ITEM:
             String bookId = uri.getPathSegments().get(1);
             deletedRows = db.delete("Book", "id = ?", new String[] { bookId });
             break;

             case CATEGORY_DIR:
             deletedRows = db.delete("Category", selection, selectionArgs);
             break;

             case CATEGORY_ITEM:
             String categoryId = uri.getPathSegments().get(1);
             deletedRows = db.delete("Category", "id = ?", new String[]
                                     { categoryId });
             break;

             default:
             break;
         }

         return deletedRows;
     }

     @Override
     public String getType(Uri uri) {

         switch (uriMatcher.match(uri)) {

            case BOOK_DIR:
            return "vnd.android.cursor.dir/vnd.com.example.databasetest.
                    provider.book";

            case BOOK_ITEM:
            return "vnd.android.cursor.item/vnd.com.example.databasetest.
                    provider.book";

            case CATEGORY_DIR:
            return "vnd.android.cursor.dir/vnd.com.example.databasetest.
                    provider.category";

            case CATEGORY_ITEM:
            return "vnd.android.cursor.item/vnd.com.example.databasetest.
                    provider.category";

         }

         return null;

     }
}

The Android manifest.xml file registers the content provider with the label < provider >... < provider >

<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
          package="com.example.databasetest"  
          android:versionCode="1"  
          android:versionName="1.0" >  
          ......  
          <application  
              android:allowBackup="true"  
              android:icon="@drawable/ic_launcher"  
              android:label="@string/app_name"  
              android:theme="@style/AppTheme" >  
              ......  
              <provider  
                  android:name="com.example.databasetest.DatabaseProvider"  
                  android:authorities="com.example.databasetest.provider" >  
              </provider>  
          </application>  
</manifest>  

   the creation process of such a complete content provider is finished.

senior

1. Principle and mechanism of contentleader

In fact, the content provider can be accessed across programs, which can be considered as a way of inter process communication. In fact, the core of its principle is Binder.

  https://blog.csdn.net/tianmi1988/article/details/51077378

2. Some wheels or SDK s do not need to be initialized. why?

https://www.jianshu.com/p/5c0570263dfd

Interview questions (test how you learn)

  • 1. What is content provider? (school recruitment & Practice)
  • 2. Talk about how to create the content provider of your own application & usage scenarios. (school recruitment & Practice)
  • 3. Talk about the principle of ContentProvider.
  • 4. What is the relationship among ContentProvider, contentresolver and contentobserver?
  • 5. Talk about the permission management of ContentProvider.

Note: the interview questions at the end of the article are from the author's own summary, to seek answers or exchange, start author GitHub project AndroidFaceInterview

Updated on: January 17, 2020

Keywords: Mobile Android Database xml SQLite

Added by lady_bug on Fri, 17 Jan 2020 12:51:11 +0200