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

An overview of the

After the first three articles, we have understood how to use ContentProvider to manipulate data of other applications, and how to use ContentProvider to provide data to other applications. Then you learned how to monitor data changes and load data from a worker thread through the LoaderManager.

Anything we do needs to be done through urIs. This note is about classes and methods associated with URIs.

Uri

We can construct a Uri from the partial methods provided in the Uri.

parse(String uriString)

We can build a Uri using the parse method, as follows:


    Uri.parse("content://com.project.mystudyproject.provider/Student")
    
Copy the code

To create a Uri, you need to provide scheme, Authority, and PATH. This method only provides these three fields.

withAppendedPath(Uri baseUri, String pathSegment)

We can use this method to concatenate paths into existing URIs. For example, our Provider will not only provide data for Student table, but also provide data for Teacher table, but because it is the same ContentProvider, scheme and authority are the same. At this point we can use this method to create URIs for two different tables.

        //scheme
        val scheme = "content://"
        //authority
        val authority = "com.project.mystudyproject.provider"
        //baseUri
        val baseUri = Uri.parse("$scheme$authority")
        / / the student table
        val studentPath = "Student"
        / / the teacher table
        val teacherPath = "Teacher"
        / / student Uri
        val studentUri = Uri.withAppendedPath(baseUri,studentPath) //content://com.project.mystudyproject.provider/Student
        / / the teacher Uri
        val teacherUri = Uri.withAppendedPath(baseUri,teacherPath) //content://com.project.mystudyproject.provider/Teacher
Copy the code

encode(String s,[String allow])decode(String s)

Because the characters in the Uri must be allowed in ASCII, some characters need to be encoded. The above two methods are used for encoding and unencoding characters. The second parameter in the encode method represents strings that do not need to participate in encoding, as shown below:


        val encode = Uri.encode("Who am I?")//%E6%88%91%E6%98%AF%E8%B0%81
        val encode1 = Uri.encode("Who am I?"."我")/ / I am B0 E8 AF % % % % E6%98%, 81
        val decode = Uri.decode("%E6%88%91%E6%98%AF%E8%B0%81")/ / who I am
        val decode1 = Uri.decode("I'm 98% B0 E8 AF % % % % % E6 81")/ / who I am
Copy the code

fromFile(File file)

Using this method we can convert a file/folder to the corresponding Uri, as shown below:


        val file = this.externalCacheDir // /storage/emulated/0/Android/data/com.project.mystudyproject/cache
        val fileUri = Uri.fromFile(file) // file:///storage/emulated/0/Android/data/com.project.mystudyproject/cache

Copy the code

fromParts(String scheme, String ssp,String fragment)

This method allows us to define parts of the Uri. Note that the last parameter here is fragment and path is included in the SSP. As follows:


val uri = Uri.fromParts("content"."//com.project.mystudyproject.provider/Student".null) //content:%2F%2Fcom.project.mystudyproject.provider%2FStudent

Copy the code

Note that this method creates an opaque Uri, meaning that the characters between the mode: and Fragment # will all be encoded, so it doesn’t look like the familiar content://ssp#fargment form.

Uri.Builder

If you look at the source code of the withAppendedPath method above, you can see that the path is internally concatenated with a method in the uri.Builder class, which also creates the Uri we need.

throughUri.Builder()structureUri

The following code creates a simple Uri by creating the uri.Builder () object and calling its methods, as shown below:

        //Uri.Builder
        val builder = Uri.Builder()
        val builderUri = builder.scheme("content")
            .authority("com.project.mystudyproject.provider")
            .path("Student")
            .build() //content://com.project.mystudyproject.provider/Student
Copy the code

path(String path)andencodePath(String newSegment)

These two methods are used to set the path of the Uri. As can be seen from the name of the method, one is the encoded character, and the other is the unencoded character, as shown below:

        val builder = Uri.Builder()
        val builderUri = builder
            .scheme("content")
            .authority("com.project.mystudyproject.provider")
            .path("Who am I?")                                          //content://com.project.mystudyproject.provider/%E6%88%91%E6%98%AF%E8%B0%81
            .encodedPath("Who am I?")                                   / / the content: / / com. Project. Mystudyproject. The provider/who I am
            .encodedPath(Uri.encode("Who am I?"))                       //content://com.project.mystudyproject.provider/%E6%88%91%E6%98%AF%E8%B0%81
            .build()
Copy the code

As you can see from the above results, the path() method encodes special characters in its arguments, whereas encodedPath() uses the values of its arguments directly.

In addition, authority() and encodedAuthority(), Fragment() and encodedFragment(), query() and encodeQuery() are all the same, without demo.

opaquePart(String s)andencodedOpaquePart(String s)

These two methods are used to set the SSP portion of an opaque Uri, as shown below:

Construct = uri.builder () val builderUri = Builder.scheme ("content").appendPath(" who am I ") AppendQueryParameter (" name ", "zhang"). The opaquePart (". / / com project. Mystudyproject. The provider "). The fragments (" fragments ") .encodedFragment("encodeFragment") .build() //content:%2F%2Fcom.project.mystudyproject.provider#encodeFragmentCopy the code

We need to be clear: both the Path and queryParams parts are part of the SSP part of the Uri, and for opaque URIs, because there is no layering, we can only specify attributes once and cannot add attributes to them later. This is why the appendPath() and appendQueryParamter() above were not added to the final Uri.

appendPath(String path)andappendEncodedPath(String newSegment)

These two methods are used to concatenate the path into the Uri, and as you can see from the name of the method appendPath() can receive any string and encode the string while concatenating. AppendEncodedPath () accepts the encoded string, but this method does not verify that the string is encoded, so we can call it if we have special characters (including Chinese characters) that we do not want to use directly. As follows:

        val builder = Uri.Builder()
        val builderUri = builder.scheme("content")
            .authority("com.project.mystudyproject.provider")
            / /. AppendEncodedPath (Uri) encode (" who am I ")) / / the content: / / com. Project. Mystudyproject. The provider / % E6%88% 91% E6 B0 E8 AF % % % % 98% 81
            / /. AppendEncodedPath (" who am I ") / / the content: / / com. Project. Mystudyproject. The provider/who I am
            / /. AppendPath (" who am I ") / / the content: / / com. Project. Mystudyproject. The provider / % E6%88% 91% E6 B0 E8 AF % % % % 98% 81
            .build()
Copy the code

appendQueryParameter(String key, String value)

This method is used to add query parameters to the Uri as follows:

        val builder = Uri.Builder()
        val builderUri = builder
            .scheme("content")
            .authority("com.project.mystudyproject.provider")
            .appendPath("Who am I?")
            .appendQueryParameter("name"."Zhang")
            .build()                                              //content://com.project.mystudyproject.provider/%E6%88%91%E6%98%AF%E8%B0%81? name=%E5%BC%A0%E4%B8%89
Copy the code

As you can see, the query parameters have been added to the end with the Uri.

willUriconvertUri.Builder

By calling the uri.buildupon () method of the Uri object, we get a uri.Builder object, with which we can perform the various operations mentioned above:


builderUri.buildUpon().appendPath("test")

Copy the code

ContentUris

The methods in ContentUris are primarily used to manipulate ids in urIs. There is no id attribute in the Uri itself. The method in ContentUris adds the ID to the end of the PATH of the Uri, which makes it easy to listen for specified data, etc.

withAppendedId(Uri,Long)

This method is used to concatenate the ID to the specified Uri path, as follows:

val idUri = ContentUris.withAppendedId(builderUri,10L) //builder uri is:content://com.project.mystudyproject.provider/Student? name=%E5%BC%A0%E4%B8%89 //idUri is: content://com.project.mystudyproject.provider/Student/10? name=%E5%BC%A0%E4%B8%89Copy the code

As you can see, the parameter with ID 10 is concatenated to the end of the path.

appendId(Uri.Builder,Long id)

This method also concatenates the ID to the path of the specified Uri. The difference is that it receives a URi. Builder and returns a uri. Builder, as shown below:

val builder = builder
            .scheme("content")
            .authority("com.project.mystudyproject.provider")
            .appendPath("Student")
            .appendQueryParameter("name"."Zhang")

val idUri = ContentUris.appendId(builder,10L).build()  //content://com.project.mystudyproject.provider/Student/10? name=%E5%BC%A0%E4%B8%89
Copy the code

The execution result is the same as above.

parseId(Uri)

This method is used to get the ID from the Uri as follows:

        ContentUris.parseId(idUri)     //10
        ContentUris.parseId(studentUri)//catch Exception: java.lang.NumberFormatException: For input string: "Student"
Copy the code

As you can see, the ID is normally obtained for a Uri with an ID, and an exception is thrown for a Uri without an ID.

removeId(Uri)

This method is provided in Android 29 and above due to removing the ID from the Uri.

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val removeResult = ContentUris.removeId(idUri) Log.i(TAG, "initUI: removeIdResultUri is:$removeResult") //content://com.project.mystudyproject.provider/Student? name=%E5%BC%A0%E4%B8%89 }Copy the code

UriMatcher

This class helps us compare URIs. This class is useful when our ContentProvider can provide multiple URIs. We can use the add() method to add our Uri and provide an ID, so that when we receive urI-related operations, we can use the ID to compare them, as follows:

Start by defining the information about the URIs we can provide:

    //scheme
    const val CONTENT_PROVIDER_SCHEME = "content://"

    //authority
    const val STUDENT_CONTENT_PROVIDER_AUTHORITY =
        "com.project.mystudyproject.provider.StudentProvider"

    // Path to the student table
    const val STUDENT_CONTENT_PROVIDER_STUDENT_PATH = "/$TABLE_STUDENT"
Copy the code

For these two URIs, let’s define their ids

    // Student table Uri ID
    const val STUDENT_URI_ID = 10
    // The Uri ID of a single student
    const val STUDENT_ITEM_URI_ID = 11
Copy the code

Next we create UriMatcher and add these two URIs to it

    private val mUriMatcher by lazy {
        UriMatcher(UriMatcher.NO_MATCH).apply {
            this.addURI(
                StudentProviderConstant.STUDENT_CONTENT_PROVIDER_AUTHORITY,
                StudentProviderConstant.STUDENT_CONTENT_PROVIDER_STUDENT_PATH,
                StudentProviderConstant.STUDENT_URI_ID
            )
            this.addURI(
                StudentProviderConstant.STUDENT_CONTENT_PROVIDER_AUTHORITY,
                "${StudentProviderConstant.STUDENT_CONTENT_PROVIDER_STUDENT_PATH}/#",
                StudentProviderConstant.STUDENT_ITEM_URI_ID
            )
        }
    }
Copy the code

Now we can use the match(Uri) method for comparison, as shown in the following example to query the data corresponding to different URIs in the query() method:

    override fun query(
        uri: Uri,
        projection: Array<out String>? , selection:String? , selectionArgs:Array<out String>? , sortOrder:String?).: Cursor? {
        Log.i("zyf"."query: uri is:$uri")
        // Compare urIs first
        when(mUriMatcher.match(uri)){
            StudentProviderConstant.STUDENT_URI_ID -> {
                // Get the data from the student data table
                return StudentDBDao.query(projection, selection, selectionArgs, sortOrder)
            }
            StudentProviderConstant.STUDENT_ITEM_URI_ID -> {
                Select * from student table where id = 1
                varselect = selection ? : String() select +=if(selection == null )
                    " $TABLE_STUDENT_ID=? "
                    else
                    " AND $TABLE_STUDENT_ID=? "
                / / to get id
                val id = ContentUris.parseId(uri)
                varlist = mutableListOf<String>() selectionArgs? .let { list.addAll(it.toList()) } list.add(id.toString())return StudentDBDao.query(projection, select, list.toTypedArray(), sortOrder)
            }
        }
        return null
    }

Copy the code

As shown above, I actually add THE ID directly to the parameter after obtaining the ID. Of course, this function can be set directly in the query side, but sometimes we do not know the column name of the id of the other table, so we cannot set the ID directly in the query.

In addition, all the operations above are carried out in the same table. If our ContentProvider can provide data of multiple tables externally, UriMatcher can simplify our judgment operation and make our judgment more accurate.

Code location for this study note: Click to view the code