Android 四大组件 ContentProvider 的使用
Android 四大组件 ContentProvider 的使用
ContentProvider一般用于跨进程数据通信。他是一种抽象,不关心底层数据如何实现。可以使用如下方式存储数据。
- 文件
- 数据库
- 网络
简单实现
private const val TAG: String = "HelloProvider"
internal class HelloProvider internal constructor() : ContentProvider() {
internal companion object {
private const val AUTHORITY: String = "edu.tyut.webviewlearn.helloProvider"
internal val CONTENT_URI: Uri = "content://$AUTHORITY/hello".toUri()
private const val HELLO: Int = 1
private const val HELLO_ID: Int = 2
private val uriMatcher: UriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(AUTHORITY, "hello", HELLO)
addURI(AUTHORITY, "hello/#", HELLO_ID)
}
private const val CONTENT_TYPE: String = "vnd.android.cursor.dir/hello"
private const val CONTENT_ITEM_TYPE: String = "vnd.android.cursor.item/hello"
private const val TABLE_NAME: String = "hello"
internal const val COLUMN_ID: String = "id"
internal const val COLUMN_CONTENT: String = "content"
}
private val database: SupportSQLiteDatabase by lazy {
EntryPointAccessors.fromApplication(context = context!!, DatabaseEntryPoint::class.java)
.getDatabase().openHelper.writableDatabase
}
override fun attachInfo(context: Context?, info: ProviderInfo?) {
Log.i(TAG, "attachInfo...")
super.attachInfo(context, info)
}
override fun onCreate(): Boolean {
Log.i(TAG, "onCreate...")
database.execSQL("""
create table if not exists $TABLE_NAME (
$COLUMN_ID integer primary key autoincrement,
$COLUMN_CONTENT text
)
""".trimIndent())
return true
}
override fun insert(
uri: Uri,
values: ContentValues?
): Uri? {
Log.i(TAG, "insert -> uri: $uri, values: $values")
val id: Long =
database.insert(
table = TABLE_NAME,
conflictAlgorithm = SQLiteDatabase.CONFLICT_IGNORE,
values = values ?: contentValuesOf()
)
Log.i(TAG, "insert -> id: $id")
if (id > 0) {
return ContentUris.withAppendedId(CONTENT_URI, id).apply {
context?.contentResolver?.notifyChange(this, null) //
}
}
return null
}
override fun delete(
uri: Uri,
selection: String?,
selectionArgs: Array<out String?>?
): Int {
Log.i(
TAG,
"delete -> uri: $uri, selection: $selection, selectionArgs: ${selectionArgs?.joinToString()}"
)
val rows: Int = when (uriMatcher.match(uri)) {
HELLO -> {
val rows: Int = database.delete(
table = TABLE_NAME,
whereClause = selection,
whereArgs = selectionArgs
)
Log.i(TAG, "delete -> hello rows: $rows")
rows
}
HELLO_ID -> {
val rows: Int =
database.delete(
table = TABLE_NAME,
whereClause = "${COLUMN_ID}=?",
whereArgs = arrayOf(ContentUris.parseId(uri))
)
Log.i(TAG, "delete -> hello item rows: $rows")
rows
}
else -> {
Log.w(TAG, "delete -> unknow uri: $uri")
0
}
}
if (rows > 0) {
context?.contentResolver?.notifyChange(uri, null)
}
return rows
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String?>?
): Int {
Log.i(
TAG,
"update -> uri: $uri, selection: $selection, selectionArgs: ${selectionArgs?.joinToString()}"
)
val rows: Int = when (uriMatcher.match(uri)) {
HELLO -> {
val rows: Int = database.update(
table = TABLE_NAME,
conflictAlgorithm = SQLiteDatabase.CONFLICT_REPLACE,
values = values ?: contentValuesOf(),
whereClause = selection,
whereArgs = selectionArgs
)
Log.i(TAG, "update -> hello rows: $rows")
rows
}
HELLO_ID -> {
val rows: Int = database.update(
table = TABLE_NAME,
conflictAlgorithm = SQLiteDatabase.CONFLICT_REPLACE,
values = values ?: contentValuesOf(),
whereClause = "${COLUMN_ID}=?",
whereArgs = arrayOf(ContentUris.parseId(uri))
)
Log.i(TAG, "update -> hello item rows: $rows")
rows
}
else -> {
Log.w(TAG, "update -> unknow uri: $uri")
0
}
}
if (rows > 0) {
context?.contentResolver?.notifyChange(uri, null)
}
return rows
}
override fun query(
uri: Uri,
projection: Array<out String?>?,
selection: String?,
selectionArgs: Array<out String?>?,
sortOrder: String?
): Cursor? {
Log.i(
TAG,
"query -> uri: $uri, projection: ${projection?.joinToString()}, selection: $selection, selectionArgs: ${selectionArgs?.joinToString()}, sortOrder: $sortOrder"
)
return when (uriMatcher.match(uri)) {
HELLO -> {
val query: SupportSQLiteQuery = SupportSQLiteQueryBuilder.builder(tableName = TABLE_NAME)
.columns(columns = projection?.filterNotNull()?.toTypedArray())
.selection(
selection = selection,
bindArgs = selectionArgs?.filterNotNull()?.toTypedArray()
)
.orderBy(orderBy = sortOrder)
.create()
database.query(query)
}
HELLO_ID -> {
val query: SupportSQLiteQuery = SupportSQLiteQueryBuilder.builder(tableName = TABLE_NAME)
.columns(columns = projection?.filterNotNull()?.toTypedArray())
.selection(
selection = "${COLUMN_ID}=?",
bindArgs = arrayOf(ContentUris.parseId(uri))
)
.orderBy(orderBy = sortOrder)
.create()
database.query(query)
}
else -> {
Log.w(TAG, "query -> unknow uri: $uri")
null
}
}?.apply {
context?.let { context: Context ->
setNotificationUri(context.contentResolver, uri)
}
}
}
override fun getType(uri: Uri): String? {
Log.i(TAG, "getType...")
return when (uriMatcher.match(uri)) {
HELLO -> {
CONTENT_TYPE
}
HELLO_ID -> {
CONTENT_ITEM_TYPE
}
else -> {
Log.w(TAG, "getType -> Unknow Uri: $uri")
null
}
}
}
override fun shutdown() {
super.shutdown()
Log.i(TAG, "shutdown...")
}
}
/**
* 生命周期交给Root和Hilt管理
*/
@EntryPoint
@InstallIn(value = [SingletonComponent::class])
private interface DatabaseEntryPoint {
fun getDatabase(): AppDatabase
}
声明ContentProvider
<provider
android:authorities="edu.tyut.webviewlearn.helloProvider"
android:name=".provider.HelloProvider"
android:exported="false"
/>
注意: 如果exported为true,小心sql注入
应用内使用
package edu.tyut.webviewlearn.ui.screen
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.content.contentValuesOf
import androidx.core.net.toUri
import edu.tyut.webviewlearn.provider.HelloProvider
import edu.tyut.webviewlearn.ui.theme.RoundedCornerShape10
import kotlinx.coroutines.CoroutineScope
import kotlin.random.Random
private const val TAG: String = "ProviderScreen"
@Composable
internal fun ProviderScreen(
modifier: Modifier,
snackBarHostState: SnackbarHostState
) {
val context: Context = LocalContext.current
val coroutineScope: CoroutineScope = rememberCoroutineScope()
Column(
modifier = modifier.fillMaxSize()
) {
Text(
text = "插入数据",
Modifier
.padding(top = 10.dp)
.background(color = Color.Black, shape = RoundedCornerShape10)
.padding(all = 5.dp)
.clickable {
val uri: Uri? = context.contentResolver.insert(
HelloProvider.CONTENT_URI, contentValuesOf(
HelloProvider.COLUMN_CONTENT to "hello: ${Random.nextInt(until = 100000)}"
)
)
Log.i(TAG, "ProviderScreen -> uri: $uri")
},
color = Color.White
)
Text(
text = "获取数据",
Modifier
.padding(top = 10.dp)
.background(color = Color.Black, shape = RoundedCornerShape10)
.padding(all = 5.dp)
.clickable {
context.contentResolver.query(HelloProvider.CONTENT_URI, null, null, null, null)
?.use { cursor ->
val idColumn: Int = cursor.getColumnIndex(HelloProvider.COLUMN_ID)
val contentColumn: Int =
cursor.getColumnIndex(HelloProvider.COLUMN_CONTENT)
while (cursor.moveToNext()) {
val id: Long = cursor.getLong(idColumn)
val content: String = cursor.getString(contentColumn)
Log.i(TAG, "ProviderScreen -> id: $id, content: $content")
}
}
},
color = Color.White
)
Text(
text = "删除数据",
Modifier
.padding(top = 10.dp)
.background(color = Color.Black, shape = RoundedCornerShape10)
.padding(all = 5.dp)
.clickable {
// val rows = context.contentResolver.delete(
// HelloProvider.CONTENT_URI, "${HelloProvider.COLUMN_ID}=?", arrayOf("100")
// )
val rows = context.contentResolver.delete(
"${HelloProvider.CONTENT_URI}/101".toUri(), null, null
)
Log.i(TAG, "ProviderScreen delete -> rows: $rows")
},
color = Color.White
)
Text(
text = "修改数据",
Modifier
.padding(top = 10.dp)
.background(color = Color.Black, shape = RoundedCornerShape10)
.padding(all = 5.dp)
.clickable {
// val rows: Int = context.contentResolver.update(
// HelloProvider.CONTENT_URI, contentValuesOf(
// HelloProvider.COLUMN_CONTENT to "random: ${Random.nextInt(until = 10000)}",
// ), "${HelloProvider.COLUMN_ID}=?", arrayOf("100")
// )
val rows: Int = context.contentResolver.update(
"${HelloProvider.CONTENT_URI}/101".toUri(), contentValuesOf(
HelloProvider.COLUMN_CONTENT to "random: ${Random.nextInt(until = 10000)}",
), null, null
)
Log.i(TAG, "ProviderScreen update -> rows: $rows")
},
color = Color.White
)
}
}
应用外使用
数据提供APP
<permission android:name="edu.tyut.webviewlearn.permission.hello.WRITE"/>
<permission android:name="edu.tyut.webviewlearn.permission.hello.READ"/>
<application>
<provider
android:authorities="edu.tyut.webviewlearn.helloProvider"
android:name=".provider.HelloProvider"
android:exported="true"
android:writePermission="edu.tyut.webviewlearn.permission.hello.WRITE"
android:readPermission="edu.tyut.webviewlearn.permission.hello.READ"
/>
</application>
数据访问APP
<permission android:name="edu.tyut.webviewlearn.permission.hello.WRITE"/>
<permission android:name="edu.tyut.webviewlearn.permission.hello.READ"/>
吐槽:
realme Q3i 5G RMX3042 这台机型没有生效,不知道为什么,天杀的害我搞半天0.0!;
结束喽,有什么不足请大家指出来,还请大家多多支持😊!

Android 四大组件 ContentProvider 的使用
浙公网安备 33010602011771号