android contentProvider 踩坑日记

写此笔记原因

学习《第一行代码》到第8章节实现provider时踩了一些坑,因此记录下来给后来人和自己一个提示,仅此而已。

包含内容

  1. Sqlite数据库CURD内容
  2. provider界面
  3. provider项目中书籍管理
  4. provider实现逻辑
  5. 用adb shell确认provider
  6. contentResolver接收项目
  7. contentProvider权限
  8. 生成Uri方法

Sqlite数据库CURD内容

  1. Android studio 创建一个Empty的空项目
  2. 不需要等项目同步完成,直接取消同步。因为从官网下载Gradle和java库太慢。
  3. 修改Gradle和java库为国内镜像后,再点击同步。应当就能快速的实现依赖下载
bash 复制代码
    # settings.gradle.kts 
    pluginManagement {
    repositories {
        maven { setUrl("https://maven.aliyun.com/repository/central") }
        maven { setUrl("https://maven.aliyun.com/repository/jcenter") }
        maven { setUrl("https://maven.aliyun.com/repository/google") }
        maven { setUrl("https://maven.aliyun.com/repository/gradle-plugin") }
        maven { setUrl("https://maven.aliyun.com/repository/public") }
        maven { setUrl("https://jitpack.io") }
        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
        gradlePluginPortal()
        }
    }
    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            maven { setUrl("https://maven.aliyun.com/repository/central") }
            maven { setUrl("https://maven.aliyun.com/repository/jcenter") }
            maven { setUrl("https://maven.aliyun.com/repository/google") }
            maven { setUrl("https://maven.aliyun.com/repository/gradle-plugin") }
            maven { setUrl("https://maven.aliyun.com/repository/public") }
            maven { setUrl("https://jitpack.io") }
            google()
            mavenCentral()
        }
    }

    rootProject.name = "BookProviderTest"
    include(":app")
bash 复制代码
	gradle/wrapper/gradle-wrapper.properties
	distributionBase=GRADLE_USER_HOME
	distributionPath=wrapper/dists
	# 此处gradle版本可以根据android相应的java版本自行修改,移植android项目也可以修改此处版本
	distributionUrl=https://mirrors.cloud.tencent.com/gradle/gradle-8.11.1-all.zip
	zipStoreBase=GRADLE_USER_HOME
	zipStorePath=wrapper/dists
  1. 创建一个类BookDatabaseHelper
bash 复制代码
class BookDatabaseHelper(val context: Context, val name: String, version: Int) :
    SQLiteOpenHelper(context, name, null, version) {
        private val createBookCMD = "create table Book (" +
                " id integer primary key autoincrement, " +
                "author text not null," +
                "price real," +
                "pages integer," +
                "name text not null)"
        private val createCategoryCMD = "create table category (" +
                "id integer primary key autoincrement," +
                "category_name text not null," +
                "category_code integer)"

    override fun onCreate(db: SQLiteDatabase?) {
        db?.execSQL(createBookCMD)
        db?.execSQL(createCategoryCMD)
//        Toast.makeText(context, "创建 $name 成功", Toast.LENGTH_SHORT).show()
    }

    override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
        db?.execSQL("drop table if exists Book")
        db?.execSQL("drop table if exists category")
        onCreate(db)
    }
}
  1. app/src/main/java/com.example.databasetest中创建一个空的MainActivity,同时选中"Generate a Layout File"和"Launcher Activity",就同时创建了MainActivity和res/layout/activity_main.xml
  2. 引入viewBinding
bash 复制代码
    # build.gradle.kts文件android的大括号中加入,再次同步
    buildFeatures {
        viewBinding = true
    }

provider界面

bash 复制代码
# 切换到图形界面,鼠标右键点击main,选择"Convert view",将布局换成LinearLayout
# 下面内容就是书籍管理界面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="书名"
            android:id="@+id/BookNameLabel"></TextView>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:hint="书名"
            android:id="@+id/bookName" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="作者"
            android:id="@+id/BookAuthorLabel"></TextView>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:hint="作者"
            android:id="@+id/bookAuthor" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="页数"
            android:id="@+id/BookPagesLabel"></TextView>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:inputType="number"
            android:hint="页数"
            android:id="@+id/bookPages" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="售价"
            android:id="@+id/BookPriceLabel"></TextView>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:hint="售价"
            android:inputType="number"
            android:id="@+id/bookPrice" />
    </LinearLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:id="@+id/bookSave"
            android:text="保存" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="20dp"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:id="@+id/bookUpdate"
            android:text="更新" />
    </LinearLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:id="@+id/bookDelete"
            android:text="删除" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="20dp"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:id="@+id/bookSearch"
            android:text="查询" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="20dp" >
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/createDatabase"
            android:layout_gravity="center"
            android:text="创建数据库" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:id="@+id/transictionTest"
            android:layout_gravity="center"
            android:text="事务测试" />
    </LinearLayout>
</LinearLayout>

provider项目中书籍管理

bash 复制代码
# MainActivity
class MainActivity : AppCompatActivity() {
    private lateinit var binding : ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        val dbHelper = BookDatabaseHelper(this, "Books.db", 2)

        /* 创建数据库 */
        binding.createDatabase.setOnClickListener {
            dbHelper.writableDatabase
        }

        /* 添加新书 */
        binding.bookSave.setOnClickListener {
            val db = dbHelper.writableDatabase
            val values = ContentValues().apply {
                put("name", binding.bookName.text.toString())
                put("author", binding.bookAuthor.text.toString())
                put("pages", binding.bookPages.text.toString().toInt())
                put("price", binding.bookPrice.text.toString().toFloat())
            }
            db.insert("Book", null, values);
            binding.bookName.setText("")
            binding.bookAuthor.setText("")
            binding.bookPages.setText("")
            binding.bookPrice.setText("")
        }

        /* 更新书籍信息 */
        binding.bookUpdate.setOnClickListener {
            var db = dbHelper.writableDatabase
            val values = ContentValues().apply{
                put("name", binding.bookName.text.toString())
                put("author", binding.bookAuthor.text.toString())
                put("pages", binding.bookPages.text.toString().toInt())
                put("price", binding.bookPrice.text.toString().toFloat())
            }
            db.update("Book", values, "name = ?", arrayOf("Book1"))
            binding.bookName.setText("")
            binding.bookAuthor.setText("")
            binding.bookPages.setText("")
            binding.bookPrice.setText("")
        }

        /* 按书名删除书籍信息 */
        binding.bookDelete.setOnClickListener {
            var db = dbHelper.writableDatabase
            db.delete("Book", "name = ?", arrayOf(binding.bookName.text.toString()))
            binding.bookName.setText("")
            binding.bookAuthor.setText("")
            binding.bookPages.setText("")
            binding.bookPrice.setText("")
        }

        binding.bookSearch.setOnClickListener {
            val db = dbHelper.writableDatabase
            val queryBookName = binding.bookName.text.toString()
//            val cursor = db.rawQuery("select name,author,pages,price from Book where name = ?"  , arrayOf(queryBookName))
            val cursor = db.query("Book",arrayOf("name","author","pages","price"), "name = ?", arrayOf(queryBookName), null, null, null)
            if(cursor.count > 0)
            {
                cursor.moveToFirst()
                binding.bookName.setText(cursor.getString(cursor.getColumnIndex("name") as Int))
                binding.bookAuthor.setText(cursor.getString(cursor.getColumnIndex("author") as Int))
                binding.bookPages.setText(cursor.getString(cursor.getColumnIndex("pages") as Int))
                binding.bookPrice.setText(cursor.getString(cursor.getColumnIndex("price") as Int))
            }
        }

        binding.transictionTest.setOnClickListener {
            val db = dbHelper.writableDatabase
            db.beginTransaction()
            try{
                db.delete("Book", null, null)
                if(true)
                {
//                    throw NullPointerException()
                }
                val values = ContentValues().apply {
                    put("name", "Game Of Thrones")
                    put("author", "George Martin")
                    put("pages", 720)
                    put("price", 20.85)
                }
                db.insert("Book", null, values)
                db.setTransactionSuccessful()   // 事务完成提交
            }
            catch (e: Exception)
            {
                e.printStackTrace()
            }
            finally {
                db.endTransaction()
            }
        }
    }
}

至此就可以向Books.db中加入书籍的内容了,保存是添加新书,查询可以查出已经加入的书籍。《第一行代码》第7章还推荐了一个"Database Navigator"可以查看导出的sqlite数据库,可以试试。

provider实现逻辑

bash 复制代码
# 新建一个BookDatabaseProvider,最好使用File->New->Other->Content Provider,会自动在`AndroidManifest.xml`文件中自动加入新建的provider
class BookDatabaseProvider : ContentProvider() {

    /* 自定义uri编号 */
    private val bookDir = 0
    private val bookItem = 1
    private val categoryDir = 2
    private val categoryItem = 3
    private val authority = "com.example.databasetest.provider"
    private var dbHelper: BookDatabaseHelper? = null

    private val uirMatcher by lazy {
        val matcher = UriMatcher(UriMatcher.NO_MATCH)
        matcher.addURI(authority, "book", bookDir)
        matcher.addURI(authority, "book/#", bookItem)
        matcher.addURI(authority,"category", categoryDir)
        matcher.addURI(authority, "category/#", categoryItem)
        matcher
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) = dbHelper?.let {
        val db = it.writableDatabase
        val deleteRows = when(uirMatcher.match(uri)) {
            bookDir -> db.delete("Book",  selection, selectionArgs)
            bookItem -> {
                /* uri 最后一个字段 */
                val bookId = uri.pathSegments[1]
                db.delete("Book", "id = ?", arrayOf(bookId))
            }
            categoryDir -> db.delete("Category", selection, selectionArgs)
            categoryItem -> {
                val categoryId = uri.pathSegments[1]
                db.delete("Category", "id = ?",arrayOf(categoryId))
            }
            else -> null
        }
        deleteRows
    } ?: 0

    override fun getType(uri: Uri) = when(uirMatcher.match(uri)) {
        bookDir -> "vnd.android.cursor.dir/vnd.$authority.book"
        bookItem -> "vnd.android.cursor.item/vnd.$authority.book"
        categoryDir -> "vnd.android.cursor.dir/vnd.$authority.category"
        categoryItem -> "vnd.android.cursor.item/vnd.$authority.category"
        else -> null
    }

    override fun insert(uri: Uri, values: ContentValues?) = dbHelper?.let {
        val db = it.writableDatabase
        val uriReturn = when(uirMatcher.match(uri)) {
            bookDir, bookItem -> {
                val newBookId = db.insert("Book", null, values)
                Uri.parse("content://$authority/book/$newBookId")
            }
            categoryDir, categoryItem -> {
                val newCategoryId = db.insert("Category", null, values)
                Uri.parse("content://$authority/category/$newCategoryId")
            }
            else -> null
        }
        uriReturn
    }

    override fun onCreate() = context?.let {
        dbHelper = BookDatabaseHelper(it, "Books.db", 2)
        true
    } ?: false

    override fun query(
        uri: Uri, projection: Array<String>?, selection: String?,
        selectionArgs: Array<String>?, sortOrder: String?
    ) = dbHelper?.let{
        val db = it.readableDatabase
        val cursor = when(uirMatcher.match(uri)){
            bookDir -> db.query("Book", projection, selection, selectionArgs, null, null, sortOrder)
            bookItem -> {
                val bookId = uri.pathSegments[1]
                db.query("Book", projection, "id = ?", arrayOf(bookId), null, null, sortOrder)
            }
            categoryDir -> db.query("Category", projection, selection, selectionArgs, null, null, sortOrder)
            categoryItem -> {
                val categoryId = uri.pathSegments[1]
                db.query("Category", projection, "id = ?", arrayOf(categoryId), null, null, sortOrder)
            }
            else -> null
        }
        cursor
    }

    override fun update(
        uri: Uri, values: ContentValues?, selection: String?,
        selectionArgs: Array<String>?
    ) = dbHelper?.let {
        val db = it.writableDatabase
        val updateRows = when(uirMatcher.match(uri)) {
            bookDir -> db.update("Book", values, selection, selectionArgs)
            bookItem -> {
                val bookId = uri.pathSegments[1]
                db.update("Book", values, "id = ?", arrayOf(bookId))
            }
            categoryDir -> db.update("Category", values, selection, selectionArgs)
            categoryItem -> {
                val categoryId = uri.pathSegments[1]
                db.update("Category", values, "id = ?", arrayOf(categoryId))
            }
            else -> null
        }
        updateRows
    } ?: 0
}

完成后就可以连接手机或模拟器点击运行安装了,加入些书籍信息

用adb shell确认provider

bash 复制代码
$ adb shell content query --uri content://com.example.databasetest.provider/book
Row: 0 id=1, author=Author1, price=1.0, pages=1, name=Book1
Row: 1 id=2, author=Author2, price=2.0, pages=2, name=Book2
# 可以看到输出了2本刚加入的书籍信息,这里还没有加入权限说明adb shell可以直接读取书籍信息
adb shell content query --uri content://com.example.databasetest.provider/book --where "name=\'Book1\'"
# 这里如果要找特定行内容需要以\' xxx \'这样的形式,什么name:s:xxx "name='xxx'" 都会报错

contentResolver接收项目界面

同样的方法再建立一个访问provider的项目"BookProviderTest",建立一个新的Activity,和activity_main.xml布局。

bash 复制代码
# 布局和上面的书籍管理类似
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="书名"
            android:id="@+id/BookNameLabel"></TextView>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:hint="书名"
            android:id="@+id/bookName" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="作者"
            android:id="@+id/BookAuthorLabel"></TextView>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:hint="作者"
            android:id="@+id/bookAuthor" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="页数"
            android:id="@+id/BookPagesLabel"></TextView>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:inputType="number"
            android:hint="页数"
            android:id="@+id/bookPages" />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="售价"
            android:id="@+id/BookPriceLabel"></TextView>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"
            android:hint="售价"
            android:inputType="number"
            android:id="@+id/bookPrice" />
    </LinearLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:id="@+id/bookSave"
            android:text="保存" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="20dp"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:id="@+id/bookUpdate"
            android:text="更新" />
    </LinearLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:id="@+id/bookDelete"
            android:text="删除" />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_margin="20dp"
            android:layout_weight="1"
            android:layout_gravity="center"
            android:id="@+id/bookSearch"
            android:text="查询" />
    </LinearLayout>
</LinearLayout>
bash 复制代码
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    var bookId: String? = null
    val providerUri = "content://com.example.databasetest.provider"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        supportActionBar?.hide()

        binding.bookSave.setOnClickListener {
            var uri = Uri.parse("$providerUri/book")
            val values = contentValuesOf("name" to binding.bookName.text.toString(),
                "author" to binding.bookAuthor.text.toString(),
                "pages" to binding.bookPages.text.toString().toInt(),
                "price" to binding.bookPrice.text.toString().toDouble())
            val newUri = contentResolver.insert(uri, values)
            bookId = newUri?.pathSegments?.get(1)
        }
        binding.bookSearch.setOnClickListener {
            val uri = Uri.parse("content://com.example.databasetest.provider/book")
            val bookName = binding.bookName.text.toString()
            val cursor = contentResolver.query(uri, arrayOf("name","author","pages","price"), "name = ?", arrayOf(bookName), null)
                cursor?.apply{
                while(moveToNext())
                {
                    val nameId = getColumnIndex("name")
                    val name = getString(nameId)
                    val authorId = getColumnIndex("author")
                    val author = getString(authorId)
                    val pagesId = getColumnIndex("pages")
                    val pages = getInt(pagesId)
                    val priceId = getColumnIndex("price")
                    val price = getDouble(priceId)

                    binding.bookName.setText(name)
                    binding.bookAuthor.setText(author)
                    binding.bookPages.setText(pages.toString())
                    binding.bookPrice.setText(price.toString())
                }
                close()
            }
        }
        binding.bookUpdate.setOnClickListener {
            val uri = Uri.parse("$providerUri/book")
            val values = contentValuesOf("name" to binding.bookName.text.toString(),
                "author" to binding.bookAuthor.text.toString(),
                "pages" to binding.bookPages.text.toString().toInt(),
                "price" to binding.bookPrice.text.toString().toDouble())
            contentResolver.update(uri, values, "name = ?", arrayOf(binding.bookName.text.toString()))
        }
        binding.bookDelete.setOnClickListener {
            val uri = Uri.parse("$providerUri/book")
            contentResolver.delete(uri, "name = ?", arrayOf(binding.bookName.text.toString()))
        }
    }
}

此项目直接运行是不能获取到provider项目的书籍信息的,因为provider没有指定权限,此项目还没有权限读写provider内容。因此接下来就是要给provider项目和本项目添加权限说明

contentProvider权限

  1. 打开第一个项目的AndroidManifest.xml
bash 复制代码
# 加入权限声名
    <permission android:name="com.example.databasetest.READ_PERMISSION"
        android:protectionLevel="normal" /  >
    <permission android:name="com.example.databasetest.WRITE_PERMISSION"
        android:protectionLevel="normal" />
# provider信息改成如下内容
        <provider
            android:name=".BookDatabaseProvider"
            android:authorities="com.example.databasetest.provider"
            android:enabled="true"
            android:readPermission="com.example.databasetest.READ_PERMISSION"
            android:writePermission="com.example.databasetest.WRITE_PERMISSION"
            android:exported="true">
        </provider>
# 删除应用后重新安装,安装后就不能使用adb shell content获取内容了
  1. 修改第二个项目的AndroidManifest.xml
bash 复制代码
    <uses-permission android:name="com.example.databasetest.READ_PERMISSION" />
    <uses-permission android:name="com.example.databasetest.WRITE_PERMISSION" />

再次运行就可以在第二个项目中获取和编辑第一个项目的provider内容了

生成Uri方法

刚开始没有发现是权限问题,怎么都不能获取到内容,还以为是因为Uri不正确。因此发现Uri生成方式有几种,这里也记录一下。

bash 复制代码
val uri = Uri.parse("content://authority/path")
# 发现此方法生成的Uri,authority和path没有解析到相应字段中,但程序还是可以正常运行,获取到内容
val uri = Uri.Builder().apply{
    scheme("content")
    authority("com.example.authority")
    path("path")
}.build()
# 使用此方法,authority和path都能正确解析到相应字段中,后者应当是比较推荐的方法,就是有点啰嗦
# 系统自带的联系人等provider的uri也和后者生成的uri基本一样
相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone1 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui