这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战
概述
经过前三篇文章的学习,我们明白了如何通过ContentProvider操作其它应用的数据,同时了解了如何通过ContentProvider向其它应用提供数据,之后还学习了如何监控数据的变化以及通过LoaderManager在工作线程中加载数据。
任何操作我们都需要通过Uri去执行,这篇笔记主要是学习和URI相关的类和方法。
Uri
我们可以通过Uri中提供的部分方法来构造一个Uri。
parse(String uriString)
我们可以通过parse方法来构建一个Uri,如下所示:
Uri.parse("content://com.project.mystudyproject.provider/Student")
通过上面的方法我们就可以创建出一个Uri,一般情况下我们在使用ContentProvider的时候只需要提供scheme,authority和path就可以了,上面的方法就只提供了这三个字段。
withAppendedPath(Uri baseUri, String pathSegment)
我们可以通过这个方法将路径拼接到已有的Uri中。例如我们的Provider不仅会提供针对Student表的数据,还会提供针对Teacher表的数据,但是由于是同一个ContentProvider,所以scheme和authority都是一样的,此时我们就可以使用这个方法创建针对两个不同表的Uri。
//scheme
val scheme = "content://"
//authority
val authority = "com.project.mystudyproject.provider"
//baseUri
val baseUri = Uri.parse("$scheme$authority")
//学生表
val studentPath = "Student"
//教师表
val teacherPath = "Teacher"
//学生Uri
val studentUri = Uri.withAppendedPath(baseUri,studentPath) //content://com.project.mystudyproject.provider/Student
//教师Uri
val teacherUri = Uri.withAppendedPath(baseUri,teacherPath) //content://com.project.mystudyproject.provider/Teacher
encode(String s,[String allow]) 和 decode(String s)
由于Uri中的字符必须是ASCII码中允许的字符,所以针对部分字符需要进行编码,上面的这两个方法就是用于编码字符和反编码字符。encode方法中第二个参数表示不需要参与编码的字符串,如下所示:
val encode = Uri.encode("我是谁")//%E6%88%91%E6%98%AF%E8%B0%81
val encode1 = Uri.encode("我是谁","我")//我%E6%98%AF%E8%B0%81
val decode = Uri.decode("%E6%88%91%E6%98%AF%E8%B0%81")//我是谁
val decode1 = Uri.decode("我%E6%98%AF%E8%B0%81")//我是谁
fromFile(File file)
通过这个方法我们可将某一个文件/文件夹转换为对应Uri,如下所示:
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
fromParts(String scheme, String ssp,String fragment)
这个方法可以让我们定义Uri的各个部分,注意这里最后一个参数是fragment,path是包含在ssp中的。如下所示:
val uri = Uri.fromParts("content","//com.project.mystudyproject.provider/Student",null) //content:%2F%2Fcom.project.mystudyproject.provider%2FStudent
需要注意的是:这个方法创建的是一个不透明的Uri,也就是从模式符号:和Fragment符号#之间的字符将会全部被编码,所以看起来不是我们熟悉的content://ssp#fargment的形式。
Uri.Builder
如果上面看过withAppendedPath这个方法的源码,可以发现这个方法内部就是通过Uri.Builder类中的相关方法将path拼接进去的,通过这个类中的方法也能够创建我们需要的Uri。
通过Uri.Builder()构造Uri
下面的代码通过创建Uri.Builder()对象,并调用其中的方法创建了一个简单的Uri,如下所示:
//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
path(String path)和encodePath(String newSegment)
这两个方法用于设置Uri的path,从方法名称上也可以看出,一个是编码后的字符,一个是没有编码的字符,如下所示:
val builder = Uri.Builder()
val builderUri = builder
.scheme("content")
.authority("com.project.mystudyproject.provider")
.path("我是谁") //content://com.project.mystudyproject.provider/%E6%88%91%E6%98%AF%E8%B0%81
.encodedPath("我是谁") //content://com.project.mystudyproject.provider/我是谁
.encodedPath(Uri.encode("我是谁")) //content://com.project.mystudyproject.provider/%E6%88%91%E6%98%AF%E8%B0%81
.build()
从上面的结果可以看出,path()方法会将其参数中的特殊字符进行编码,而encodedPath()则直接使用其参数的值。
除此以外,authority()和encodedAuthority(),fragment()和encodedFragment(),query()和encodeQuery()均是这样,不做演示。
opaquePart(String s)和encodedOpaquePart(String s)
这两个方法用于设置一个不透明的Uri的ssp部分,如下所示:
val builder = Uri.Builder()
val builderUri = builder
.scheme("content")
.appendPath("我是谁")
.appendQueryParameter("name","张三")
.opaquePart("//com.project.mystudyproject.provider")
.fragment("fragment")
.encodedFragment("encodeFragment")
.build() //content:%2F%2Fcom.project.mystudyproject.provider#encodeFragment
我们需要明确的是:path和queryParams部分都是属于Uri中的ssp部分,而对于不透明Uri来说,由于无法分层,所以我们只能一次指定属性,后面不能再向其中添加属性,这也是上面的appendPath()和appendQueryParamter()没有添加到最终的Uri中的原因。
appendPath(String path)和 appendEncodedPath(String newSegment)
这两个方法用于将路径拼接到Uri中,从方法名可以看出,appendPath()可以接收任何字符串,拼接的时候会对,字符串进行编码操作。而appendEncodedPath()可以接收编码后的字符串,但是这个方法并不会校验字符串是否被编码,所以如果我们有特殊字符(包括汉字等)不想被编码直接使用,可以调用这个方法。如下所示:
val builder = Uri.Builder()
val builderUri = builder.scheme("content")
.authority("com.project.mystudyproject.provider")
//.appendEncodedPath(Uri.encode("我是谁")) //content://com.project.mystudyproject.provider/%E6%88%91%E6%98%AF%E8%B0%81
//.appendEncodedPath("我是谁") //content://com.project.mystudyproject.provider/我是谁
//.appendPath("我是谁") //content://com.project.mystudyproject.provider/%E6%88%91%E6%98%AF%E8%B0%81
.build()
appendQueryParameter(String key, String value)
这个方法用于将查询参数添加到Uri中,如下所示:
val builder = Uri.Builder()
val builderUri = builder
.scheme("content")
.authority("com.project.mystudyproject.provider")
.appendPath("我是谁")
.appendQueryParameter("name","张三")
.build() //content://com.project.mystudyproject.provider/%E6%88%91%E6%98%AF%E8%B0%81?name=%E5%BC%A0%E4%B8%89
可以看到,查询参数已经被加入带了Uri的末尾。
将Uri转换为Uri.Builder
通过调用Uri对象的uri.buildUpon()方法,我们可以获得一个Uri.Builder对象,使用这个对象我们就可以执行上面提到的各种操作:
builderUri.buildUpon().appendPath("test")
ContentUris
ContentUris中的方法主要是用于对Uri中的id进行操作。本身Uri中不存在id属性,使用ContentUris中的方法可以将id添加到Uri的path的末尾,这样可以很方便我们对指定的数据进行监听等。
withAppendedId(Uri,Long)
这个方法用于将id拼接到指定的Uri路径后面,如下所示:
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%89
可以看到,id为10的参数拼接到了路径的后面。
appendId(Uri.Builder,Long id)
这个方法也是将id拼接到指定Uri的路径后面,不同之处在于接收的是一个Uri.Builder,返回的也是一个Uri.Builder,如下所示:
val builder = builder
.scheme("content")
.authority("com.project.mystudyproject.provider")
.appendPath("Student")
.appendQueryParameter("name","张三")
val idUri = ContentUris.appendId(builder,10L).build() //content://com.project.mystudyproject.provider/Student/10?name=%E5%BC%A0%E4%B8%89
执行结果和上面的执行结果是一样的。
parseId(Uri)
这个方法用于从Uri中获取id,如下所示:
ContentUris.parseId(idUri) //10
ContentUris.parseId(studentUri)//catch Exception: java.lang.NumberFormatException: For input string: "Student"
可以看出,对于有id的Uri可以正常获取到id,对于没有id的Uri则会抛出异常。
removeId(Uri)
这个方法在Android 29及以上版本提供,由于移除Uri中的id.
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
}
UriMatcher
这个类可以帮助我们对Uri进行比较。当我们的ContentProvider可以提供多个Uri的时候使用这个类非常好用。我们可以使用add()方法将我们的Uri加入进去,然后提供一个id,这样当接收到和Uri相关的操作的时候,我们就可以使用id去进行比较,如下所示:
首先定义我们可以提供的Uri的相关信息:
//scheme
const val CONTENT_PROVIDER_SCHEME = "content://"
//authority
const val STUDENT_CONTENT_PROVIDER_AUTHORITY =
"com.project.mystudyproject.provider.StudentProvider"
//学生表的路径
const val STUDENT_CONTENT_PROVIDER_STUDENT_PATH = "/$TABLE_STUDENT"
针对这两个Uri我们再来定义其id
//学生表Uri id
const val STUDENT_URI_ID = 10
//单个学生的Uri id
const val STUDENT_ITEM_URI_ID = 11
接着我们创建UriMatcher并将这两个Uri加入进去
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
)
}
}
现在我们就可以使用match(Uri)方法进行比较了,如下所示演示了在query()方法中查询不同的Uri对应的数据:
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")
//首先对Uri进行比较
when(mUriMatcher.match(uri)){
StudentProviderConstant.STUDENT_URI_ID -> {
//需要获取学生数据表中的数据
return StudentDBDao.query(projection, selection, selectionArgs, sortOrder)
}
StudentProviderConstant.STUDENT_ITEM_URI_ID -> {
//需要获取学生表中指定id的数据
var select = selection ?: String()
select += if(selection == null )
" $TABLE_STUDENT_ID = ? "
else
" AND $TABLE_STUDENT_ID = ? "
//获取id
val id = ContentUris.parseId(uri)
var list = mutableListOf<String>()
selectionArgs?.let {
list.addAll(it.toList())
}
list.add(id.toString())
return StudentDBDao.query(projection, select, list.toTypedArray(), sortOrder)
}
}
return null
}
如上所示,我这边其实就是在获取到id之后直接将id加入到参数中,当然这个功能在查询方直接就可以设置进去,但是有时候我们并不知道对方表的id的列名,就无法在查询的时候将id直接设置进去。
另外,上面都是在同一个表中进行的操作,如果我们的ContentProvider可以对外提供多张表的数据的时候,UriMatcher可以简化我们判断的操作,使得我们的判断更加准确。
本篇学习笔记的代码位置:点击查看代码