This is the fourth day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021

An overview of the

In the previous study notes, we learned how to use ContentProvider to obtain and modify data provided by other applications by adding, deleting, modifying and reviewing data in the contact database. Query (), Insert (),update(), and delete() are the four methods provided by the ContentResolver class.

The last note focused on the client type of role, where our application needs to read data from other applications. In this study note we will focus on the server type of role, where our application needs to provide data to other applications and accept changes made to our application data by other applications.

The content of this study note is from the official document creation content provider. Click the link to read the official document directly.

Content providers manage access to the central data store. If you want to implement a ContentProvider, you need to treat it as one or more classes in your application, one of which inherits a ContentProvider, which is the interface between the ContentProvider and other applications. You also need to create the element in the manifest file. Although the primary purpose of a content provider is to provide data to other applications, some activities in our application must allow users to query and modify the data managed by the provider. In other words, our content providers still need to implement the basic functions of add, delete, modify and check.

purpose

First we need to understand when we need to create a content provider. If we wanted to do any of the following, we could create a content provider:

  • We need to provide complex data or files for other applications
  • We need to copy complex data from our application to other applications
  • We want to use the search framework to provide custom search suggestions
  • We want to expose application data to widgets
  • We want to achieveAbstractThreadedSyncAdapter.CursorAdapterorCursorLoaderClass.

If we have identified that we need to implement a content provider, we can implement the content provider by following these steps:

  1. Determine how our application’s own data needs to be stored:

    • The file data

      This is data normally stored in files, such as photos, videos, text, etc. By storing files in private space, our application can provide file handles to other applications through content providers.

    • Structured data

      Usually stored in a database, array, or similar structure.

  2. Define subclasses of ContentProvider, the interface between data and the rest of the Android system, and the concrete implementation of the required methods.

  3. Defines the provider’s authorization string (that is, authority in the Uri), the content Uri of the string, and the column name. In addition, you need to provide the necessary permissions for applications that access the data, and you can define these values as constants. Finally, if you want to provide applications that handle intents, you need to define Intent actions, Extra data, and so on.

  4. Add additional optional sections.

Design content URI

The content URI is the URI used to identify data in the provider, and the content URI contains the symbolic name of the entire provider (that is, authorization) and the name pointing to the table or file (that is, the path). The optional ID section points to a single row in the table. Each data access method of the ContentProvider takes a content URI as a parameter, which determines which table or file to access.

The basic structure of a Uri is as follows:

Pattern name: / / scheme model specific parts: / / modelSpecialPartCopy the code

The result of the Uri we use here is:

Schema name :// Authorizer /path Content ://authority/pathCopy the code

Design of authorization

Providers typically have a single authorization that acts as their Android internal name. To avoid conflict with other applications, generally it is recommended to use package name as authorized prefix, such as package called com. The example. The authorization of the project can be com. Example. Project. The provider.

The design path

After authorizations are designed, we usually add path information to create content URIs based on permissions. For example, if we have two tables table1 and table2, we can add these two table names as the path to the content URI, combined with the above authorization, Add path after the content URI for the content: / / com. Example. The project/table1 and content: / / com. Example. Project/table2.

Provide the content URI ID

By convention, the provider accepts a content URI with a row ID at the end to provide access to the contents of a single row in the table. Combining the above two parts, if we specify the need to access the data in the table is table1 id for 10 data, then we can generate the content of the URI is: the content: / / com. Example. The project/table1/10

implementationContentProviderclass

The ContentProvider instance handles requests sent by other applications to manage access to structured data sets. All forms of access eventually invoke the ContentResolver, which in turn retrieves the data by calling the concrete methods of the ContentProvider. To implement the ContentProvider class, you must implement the following methods:

  • query()

Retrieves data from the provider, returns the data as a Cursor object, using parameters to select the table to query, the rows and columns to return, and the sort order of the results.

  • insert()

Inserts a new row in the provider. Use parameters to select the target table and get the column values to be used. Returns the content URI of the newly inserted row.

  • update()

Update existing rows in the provider. Use arguments to select the tables and rows to update and get the updated column values, returning the number of updated rows.

  • delete()

Deletes rows from the provider. Use arguments to select tables and rows to delete. Returns the number of deleted rows.

  • getType()

Returns the MIME type corresponding to the content URI.

  • onCreate()

Initialize the provider. The Android system calls this method immediately after the provider is created. This is created when the ContentResolver object attempts to access the provider.

There are a few things to be aware of when implementing the above method:

  1. All of these methods divide byonCreate()Methods can be called by multiple threads at the same time, so they must be thread-safe methods, in the implementation of the need to pay attention to the problem of multithreading.
  2. To avoid theonCreate()Method to defer initialization tasks until they are actually needed.
  3. Although we must implement the above method, we need to return the specified type of data according to the method signature. For example, we do not want other applications to insert new data into our database, so in the implementationinsert()Method can be returned directlynull.

implementationonCreate()methods

The Android system calls onCreate() when the provider is started, and in this method, we should only perform fast-running initialization tasks and defer database creation and data loading until the provider actually receives the data request. Here is the code from the official documentation that demonstrates building a database object in the onCreate() method and getting a handle to the data access object:

    // Defines a handle to the Room database
    private lateinit var appDatabase: AppDatabase

    // Defines a Data Access Object to perform the database operations
    private var userDao: UserDao? = null

    override fun onCreate(a): Boolean {

        // Creates a new database object.
        appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build()

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.userDao

        return true
    }
Copy the code

implementationquery()methods

The query() method must return a Cursor object. If it fails, the system throws an Exception. If we are using an SQLite database to hold objects, we simply return the Cursor returned by a Query () method of the SQLiteDatabase class. If the specified query does not match any data, a Curosr still needs to be returned, but getCount() is 0. Null should only be returned if the query has an internal error.

If we save the data in a way other than the SQLite database we are using, we need to return some concrete subclass of Cursor.

implementationinsert()methods

The insert() method uses the values in ContentValues to add a new row to the corresponding table. If the column name is not included in ContentValues, we can provide the default value in the provider code or in the database schema.

This method should return the new row Uri, in the SQLiteDatabase insert () method returns the new itself into the id value, we can through the ContentUris. WithAppendedId () this method to insert the new id value is set to the Uri, The Uri is returned as a result.

implementationdelete()methods

Instead of deleting the actual row from the data store when using the delete() method, we can consider adding a delete flag to the deleted row rather than removing the row completely in cases where we are using the synchronization adapter with the provider. The synchronization adapter can check for deleted rows, remove them from the server, and then remove them from the provider.

implementationupdate()methods

The update() method takes the same ContentValues argument as the Insert () method, and it takes the same selection and selectionArgs argument as the Query () and delete() methods, so we can reuse code between these methods.

Implement the content provider MIME type

In addition to implementing the add, delete, change and review methods above, subclasses of ContentProvider must also implement the getType() method, which any provider must implement. Also, if our content provider provides a file, we need to implement the getStreamType() method.

MIME type of the table

GetType () needs to return a String in MIME format that describes the data type returned by the content URI parameter. URI parameters can be schemas rather than specific URIs. In this case, we should return the data type associated with the content URI that matches the pattern.

For common data types such as text, HTML, or JPEG, getType() should return a standard MIME TYPE for the data. This standard TYPE information is available at the MIME TYPE List web site.

For content URIs that refer to one or more rows of table data, getType() should return the corresponding type in an Android vendor-specific MIME format:

Type part: VND

Subtype part: Android.cursor.item/is used if the URI pattern is used for a single row, and Android.cursor.dir/is used if the URI pattern is used for multiple rows

Provider-specific parts: VND.

.

. Where name should be globally unique and type should be unique in the corresponding URI schema. It is appropriate to use the package name as name and the identifier string of the URI-associated table as type.

If the provider of the authorization is com. Example. Project. The provider, and it has publicly named tables, Multiple lines of the MIME type for: in the table1 vnd.android.cursor.dir/vnd.com.example.project.table1, and MIME type for the vnd.android.cursor.item/vnd.com.exa line mple.project.table1.

The MIME type of the file

If our provider provides files, we need to implement the getStreamTypes() method, which returns a String array of MIME type for files that the provider can return for a given content URI. We should filter the MIME types we provide by the MIME type filter parameter, returning only the MIME types the client wants to process.

Join the provider can provide. JPG, PNG,. GIF format image files, if the client applications use the ContentResolver. When calling getStreams () using the filter string image / * (any image content), The ContentProvider. GetStreamTypes () method should return an array:

{"image/jpeg","image/png","image/gif"}
Copy the code

If the client application only want. JPG format file, you can in the calling ContentResolver. GetStreamTypes () is used when the filter string * \ / JPG, the ContentProvider. GetStreamTypes () should be returned:

{"image/jpeg"}
Copy the code

GetStreamTyps () should return NULL if the MIME type requested in any filter string is not provided in the application.

Implement the permissions of the content provider

Even though our application data is low-level, all programs can read or write to the provider’s data because no permissions have been added to our content provider. We can set permissions for the provider in the manifest file using properties or children of the element. We can set permissions that apply to the entire provider, to a specific table or even to a specific record, or to all three.

We can use one or more < Permission > elements in the manifest file to define permissions for the provider. For example, the following code defines permissions to read and write to the provider:

    <! MyContentProvider defines the permissions required to access MyContentProvider.
    <permission
        android:name="${applicationId}.provider.permission.READ_PERMISSION"
        android:protectionLevel="normal"
        />
    <permission
        android:name="${applicationId}.provider.permission.WRITE_PERMISSION"
        android:protectionLevel="normal"
        />
Copy the code

In general, we only need to specify read and write permissions, or specify read and write permissions separately, as shown below:

  • Specify read and write permissions
Android :permission=" permission name"Copy the code

By specifying the above permissions, external applications can have the ability to write and read data to the provider through this permission.

  • Specify read and write permissions separately
Android :readPermission=" permission name "android:writePermission=" permission name"Copy the code

By separating specified read and write permissions, external applications can declare corresponding permissions according to their own needs.

example

The following is a simple example of a ContentProvider. The main function is for an external application to modify the Student table in the current application.

Create a table

First we need to create a Student table with the following contents:

/ / Student data tables
const val TABLE_STUDENT = "Student"
//id
const val TABLE_STUDENT_ID = "id"
/ / student id
const val TABLE_STUDENT_NO = "no"
/ / name
const val TABLE_STUDENT_NAME = "name"
// Gender 0-male 1-female
const val TABLE_STUDENT_GENDER = "gender"
/ / grade
const val TABLE_STUDENT_GRADE = "grade"
/ / class
const val TABLE_STUDENT_CLASS = "studentClass"

// create the student table command
const val CREATE_TABLE_STUDENT = """
    CREATE TABLE IF NOT EXISTS $TABLE_STUDENT(
        $TABLE_STUDENT_ID INTEGER PRIMARY KEY,
        $TABLE_STUDENT_NO INTEGER PRIMARY KEY,
        $TABLE_STUDENT_NAME TEXT NOT NULL,
        $TABLE_STUDENT_GENDER INTEGER NOT NULL,
        $TABLE_STUDENT_GRADE INTEGER NOT NULL,
        $TABLE_STUDENT_CLASS INTEGER NOT NULL,
        UNIQUE($TABLE_STUDENT_NO)
    );
"""
Copy the code

Second, we need to create this table when we create the database:

    override fun onCreate(db: SQLiteDatabase?). {
        Log.i(tag,"will create table")
        // This method is called when the database is createddb? .execSQL(CREATE_TABLE_STUDENT) }Copy the code

To create aDBDaoClass to operate on the student table

/** * is used to manipulate the student table singleton */
object StudentDBDao {

    // A Helper class that performs database-related operations
    private var mDBHelper: MySqliteHelper? = null

    // Database connection for reading and writing
    private val mDataBase bylazy { mDBHelper!! .writableDatabase }// Create the database as needed
    fun openDB(context: Context) {
        if(mDBHelper ! =null) {return
        }
        mDBHelper = MySqliteHelper(context, DB_NAME, DB_VERSION)
    }
    
    /** * performs the query, if id is specified, then id is concatenated to the filter parameter */
    fun query(
        projection: Array<out String>? , selection:String? , selectionArgs:Array<out String>? , sortOrder:String?).: Cursor {
        return mDataBase.query(
            TABLE_STUDENT,
            projection,
            selection,
            selectionArgs,
            null.null,
            sortOrder
        )
    }

    
    // Insert data
    fun insert(values: ContentValues): Long{
        if(values.size() == 0) {// There is no new data to insert
            return -1
        }
        return mDataBase.insert(TABLE_STUDENT,null,values)
    }
    
    // Delete data
    fun delete(selection: String? , selectionArgs:Array<out String>?: Int{
        return mDataBase.delete(TABLE_STUDENT,selection,selectionArgs)
    }
    
    // Perform data update operations
    fun update(selection: String? ,selectionArgs:Array<out String>? ,contentValues:ContentValues): Int{
        if(contentValues.size() == 0) {return -1;
        }
        return mDataBase.update(TABLE_STUDENT,contentValues,selection,selectionArgs)
    }

}
Copy the code

In this class we have defined the add, delete, change and review methods, so we just need to call the methods defined here in the ContentProvider.

createContentProviderclass

class StudentProvider : ContentProvider() {

    / / authorization
    private val AUTHORITY = "com.project.mystudyproject.provider.StudentProvider"

    / / data sheet
    private val PATH = "/$TABLE_STUDENT"

    override fun onCreate(a): Boolean {
        if (context == null) {
            return false
        }
        // Prepare the operation database
        StudentDBDao.openDB(context!!)
        return true
    }

    override fun query(
        uri: Uri,
        projection: Array<out String>? , selection:String? , selectionArgs:Array<out String>? , sortOrder:String?).: Cursor {
        return StudentDBDao.query(
            projection,
            selection,
            selectionArgs,
            sortOrder
        )
    }

    override fun getType(uri: Uri): String? {
        return "vnd.android.cursor.dir/vnd.example.project.provider.$TABLE_STUDENT"
    }

    override fun insert(uri: Uri, values: ContentValues?).: Uri? {
        if (values == null) {
            return null
        }
        return ContentUris.withAppendedId(
            Uri.parse("content://$AUTHORITY/$PATH"),
            StudentDBDao.insert(values)
        )
    }
    override fun delete(uri: Uri, selection: String? , selectionArgs:Array<out String>?: Int {
        return StudentDBDao.delete(selection, selectionArgs)
    }

    override fun update(
        uri: Uri,
        values: ContentValues? , selection:String? , selectionArgs:Array<out String>?: Int {
        if(values == null) {return 0;
        }
        return StudentDBDao.update(selection,selectionArgs,values)
    }
}
Copy the code

In the above code we define a StudentProvider that provides student information to the outside world.

Set up the<provider>

    <! -- Define permissions to access student information -->
    <permission
        android:name="${applicationId}.provider.permission.READ_STUDENT"
        android:protectionLevel="normal"
        />
    
    <permission
        android:name="${applicationId}.provider.permission.WRITE_STUDENT"
        android:protectionLevel="normal"
        />
    
    <provider
    android:name=".uri.content_provider.provider.StudentProvider"
    android:authorities="${applicationId}.provider"
    android:exported="true"
    android:enabled="true"
    android:readPermission="${applicationId}.provider.permission.READ_STUDENT"
    android:writePermission="${applicationId}.provider.permission.WRITE_PERMISSION"
    />
Copy the code

Now that we have a content provider defined, we can create another APP to test whether it can read and write the content provider’s data.

Create another application to query data

Set the permissions

When we create a new APP, we can first set the required permissions:

    <uses-permission android:name="com.project.mystudyproject.provider.permission.READ_STUDENT"/>
    <uses-permission android:name="com.project.mystudyproject.provider.permission.WRITE_PERMISSION"/>
Copy the code

Query data

After obtaining the permission, we can perform the operation of query data:

    // Read data
    private fun queryData(a) {
        mHandler.post {
            try {
                val cursor = this.contentResolver.query(
                    Uri.withAppendedPath(
                        Uri.parse("content://com.project.mystudyproject.provider.StudentProvider"),
                        "Student"
                    ),
                    arrayOf("id"."name"),
                    null.null."no")? .apply {// Read data
                    val list = mutableListOf<StudentBean>()
                    while (this.moveToNext()) {
                        val id = this.getInt(this.getColumnIndex("id"))
                        val name = this.getString(this.getColumnIndex("name"))
                        list.add(StudentBean(id, null, name, null.null.null)) } mAdapter.clear() mAdapter.addStudentList(list) } cursor? .close() }catch (e: Exception) {
                Log.e("zyf"."queryData: exception is ${Log.getStackTraceString(e)}")}}}Copy the code

The code above sets the query to a RecyclerView.

Add data

We can add data to the content provider using the following methods

    // Add a student information
    private fun addStudent(a){
        val values = ContentValues()
        values.put("no",Integer.parseInt(noEdit.text.toString()))
        values.put("name",nameEdit.text.toString())
        values.put("gender".if(genderGroup.checkedRadioButtonId == R.id.rb_boy) 0 else 1)
        values.put("grade",Integer.parseInt(gradeEdit.text.toString()))
        values.put("studentClass",Integer.parseInt(classEdit.text.toString()))
        val result = this.contentResolver.insert(Uri.parse("content://com.project.mystudyproject.provider.StudentProvider/Student"),values)
    }
Copy the code

Perform the above method to add a piece of data to the content provider. Here is the data added by executing the above method

sqlite> select * from Student;
id          no          name        gende  grade  stude
---------- ---------- ---------- ----- ----- -----
1           123Zhang SAN0      1      2
2           124Li si0      1      2
3           125Cathy0      1      2
4           126bala1      1      2
5           127barbie1      1      2
6           128jingle0      1      2
Copy the code

This note code address: ContentProvider – create a ContentProvider [study] (source/app/SRC/main/Java/com/project/mystudyproject/uri/content_provide R/Provider · Zhang Yifan /AndroidStudyProject – Code Cloud – Open Source China (gitee.com)