android基础02-广播、持久化、权限、ContentProvider

广播

Android中的每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会收到自己所关心的广播内容,这些广播可能是来自于系统的,也可能是来自于其他应用程序的。

  1. 标准广播(normal broadcasts)
    完全异步执行的广播,发出后所有 BroadcastReceiver 几乎同时受到,效率较高也无法截断。
  2. 有序广播(ordered broadcasts)
    同步执行的广播,广播发出后 BroadcastReceiver 按照优先级一个一个接受。将广播截断后后面的就无法接收到了。

接收广播

注册BroadcastReceiver的方式一般有两种:在代码中注册和在AndroidManifest.xml中注册。

  1. 动态注册监听时间变化
    必须在程序启动之后才能接收广播
    class MainActivity : AppCompatActivity() {
    
        inner class TimeChangeReceiver : BroadcastReceiver() {
            // 重写了接收到广播时执行的方法
            override fun onReceive(context: Context, intent: Intent) {
                Toast.makeText(context, "Time has changed", Toast.LENGTH_SHORT).show()
            }
        }
    
        lateinit var timeChangeReceiver: TimeChangeReceiver // 继承自 BroadcastReceiver
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            // 指定系统时间变化的事件:android.intent.action.TIME_TICK
            // 查看完整的广播列表
            val intentFilter = IntentFilter()
            intentFilter.addAction("android.intent.action.TIME_TICK")
            // 指定处理事件的动作
            timeChangeReceiver = TimeChangeReceiver()
            registerReceiver(timeChangeReceiver, intentFilter)
        }
        override fun onDestroy() {
            super.onDestroy()
            // 记得销毁时取消注册
            unregisterReceiver(timeChangeReceiver)
        }
    }
  2. 静态注册实现开机启动
    BroadcastReceiver中是不允许开启线程的,长时间执行不结束会导致程序异常
    在Android 8.0系统之后,所有隐式广播都不允许使用静态注册的方式来接收了。隐式广播指的是那些没有具体指定发送给哪个应用程序的广播,大多数系统广播属于隐式广播,但是少数特殊的系统广播目前仍然允许使用静态注册的方式来接收。 https://developer.android.google.cn/guide/components/broadcast-exceptions.html
    一个特殊的广播为 android.intent.action.BOOT_COMPLETED
    用静态注册的方式来接收开机广播,然后在onReceive()方法里执行相应的逻辑,这样就可以实现开机启动的功能了。


    class BootCompleteReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show()
        }
    }
    // 生成BroadcastReceiver时会在AndroidManifest.xml 中自动注册,需要修改
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    
        // 敏感操作要进行权限声明
        <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    
        <application
            android:allowBackup="true"
            // ....
            tools:targetApi="31">
            <receiver
                android:name=".BootCompleteReceiver"
                android:enabled="true"
                android:exported="true">
                // 指定监听的 action
                <intent-filter>
                    <action android:name="android.intent.action.BOOT_COMPLETED" />
                </intent-filter>
            </receiver>

发送自定义广播

先自定义一个广播接收器:

class MyBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Toast.makeText(context, "received in MyBroadcastReceiver",
            Toast.LENGTH_SHORT).show()
    }
}

<receiver
          android:name=".MyBroadcastReceiver"
          android:enabled="true"
          android:exported="true">
    <intent-filter>
        // 接收自定义广播
        <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
    </intent-filter>
</receiver>
  1. 标准广播
    button.setOnClickListener {
        val intent = Intent("com.example.broadcasttest.MY_BROADCAST")
        // packageName是getPackageName()的语法糖写法,用于获取当前应用程序的包名。
        intent.setPackage(packageName)
        // 默认发出是隐式广播,为了静态注册的广播能接收到必须指定发送给哪个程序
        sendBroadcast(intent)
    }
  2. 有序广播
    在发送广播时使用另一个重载的方法
    button.setOnClickListener {
        val intent = Intent("com.example.broadcasttest.MY_BROADCAST")
        intent.setPackage(packageName)
        // 第二个参数是一个与权限相关的字符串,传入null即可
        sendOrderedBroadcast(intent, null)
    }

    注册其他的 BroadcastReceiver,并指定权重
    <receiver
              android:name=".MyBroadcastReceiver"
              android:enabled="true"
              android:exported="true">
        <intent-filter android:priority="100"> // 权重为100,越大越有限
            <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
        </intent-filter>
    </receiver>
    class MyBroadcastReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show()
            // 拦截广播,后面的无法接收到
            abortBroadcast()
        }
    }


持久化

文件存储

写文件时默认存储到 /data/data/<package name>/files/ 路径下,默认两种模式 MODE_PRIVATE(默认操作,覆盖原文件) 和 MODE_APPEND
Context类中提供的 openFileInput() 和 openFileOutput() 方法

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val inputText = load()
        if (inputText.isNotEmpty()) {
            editText.setText(inputText)
            editText.setSelection(inputText.length)
            Toast.makeText(this, "Restoring succeeded", Toast.LENGTH_SHORT).show()
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        val inputText = editText.text.toString()
        save(inputText)
    }
    // 读文件
    private fun load(): String {
        val content = StringBuilder()
        try {
            val input = openFileInput("data")
            val reader = BufferedReader(InputStreamReader(input))
            reader.use {
                reader.forEachLine {
                    content.append(it)
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return content.toString()
    }
    // 写文件
    private fun save(inputText: String) {
        try {
            val output = openFileOutput("data", Context.MODE_PRIVATE) // 返回 FileOutputStream对象
            val writer = BufferedWriter(OutputStreamWriter(output))   // 构建出一个BufferedWriter对象
            writer.use {  // 内置函数,保证代码执行玩后自动关闭流
                it.write(inputText)
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}

SharedPreferences存储

用键值对的方式来存储数据

两种方法获取 SharedPreferences 对象:

  1. Context类中的getSharedPreferences()方法
    参数1 指定文件名称,存放在 /data/data/<package_name>/shared_prefs/ 目录
    参数2 指定操作模式,现在只有 MODE_PRIVATE 一种,与传入0相同,表示可以读写
  2. Activity类中的getPreferences()方法
    将Activity名作为文件名,只接收操作模式
saveButton.setOnClickListener {
    // 1. 获取一个 SharedPreferences.Editor 对象
    val editor = getSharedPreferences("fileName", Context.MODE_PRIVATE).edit()
    // 2. 添加数据
    editor.putString("name", "Tom")
    editor.putInt("age", 28)
    editor.putBoolean("married", false)
    // 3. 提交
    editor.apply()
}
restoreButton.setOnClickListener {
    // 1. 获取对象
    val prefs = getSharedPreferences("data", Context.MODE_PRIVATE)
    // 2. 读数据
    val name = prefs.getString("name", "")
    val age = prefs.getInt("age", 0)
    val married = prefs.getBoolean("married", false)
    Log.d("MainActivity", "name is $name")
    Log.d("MainActivity", "age is $age")
    Log.d("MainActivity", "married is $married")
}

SQLite数据库存储

抽象类 SQLiteOpenHelper 有两个抽象方法 onCreate() onUpgrade()getReadableDatabase() getWritableDatabase() 用于打开或新建数据库
一般使用参数较少的构造方法:

  1. 参数1,Context
  2. 参数2,数据库名
  3. 参数3,自定义Cursor,一般传null
  4. 参数4,当前数据库的版本号,可用于对数据库进行升级操作
class MyDatabaseHelper(val context: Context, name: String, version: Int) :
    SQLiteOpenHelper(context, name, null, version) {
    private val createBook = "create table Book (" +
            " id integer primary key autoincrement," +
            "author text," +
            "price real," +
            "pages integer," +
            "name text)"
    // 调用 val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1) 时创建数据库
    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(createBook)
        Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
    }
    // 调用 val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2) 时更新数据库
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        db.execSQL("drop table if exists Book")
        onCreate(db)
    }
}

数据库放在 /data/data/<package_name>/databases/ 目录下


  1. insert() 方法,有4个参数:
    参数1 表名
    参数2 在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般直接传null
    参数3 一个 ContentValues 对象,它提供了一系列的put()方法重载,用于添加数据
    addData.setOnClickListener {
        val db = dbHelper.writableDatabase
        val values1 = ContentValues().apply {
            // 开始组装第一条数据
            put("name", "The Da Vinci Code")
            put("author", "Dan Brown")
            put("pages", 454)
            put("price", 16.96)
        }
        db.insert("Book", null, values1) // 插入第一条数据
        val values2 = ContentValues().apply {
            // 开始组装第二条数据
            put("name", "The Lost Symbol")
            put("author", "Dan Brown")
            put("pages", 510)
            put("price", 19.95)
        }
        db.insert("Book", null, values2) // 插入第二条数据
    }

  2. update(表名, 存储数据的ContentValues对象, 约束更新某几行 默认更新全部)
    updateData.setOnClickListener {
        val db = dbHelper.writableDatabase
        val values = ContentValues()
        values.put("price", 10.99)
        db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
    }

  3. deleteData.setOnClickListener {
        val db = dbHelper.writableDatabase
        db.delete("Book", "pages > ?", arrayOf("500"))
    }


  4. queryData.setOnClickListener {
        val db = dbHelper.writableDatabase
        // 查询Book表中所有的数据
        val cursor = db.query(
            "Book", // 1. 表名
            null,   // 2. 指定查哪几列,默认所有
            null,   // 3. 指定行
            null,   // 4. 搭配3
            null,   // 5. group by
            null,   // 6. having
            null)   // 7. orderBy
        if (cursor.moveToFirst()) {
            do {
                // 遍历Cursor对象,取出数据并打印
                val name = cursor.getString(cursor.getColumnIndex("name"))
                val author = cursor.getString(cursor.getColumnIndex("author"))
                val pages = cursor.getInt(cursor.getColumnIndex("pages"))
                val price = cursor.getDouble(cursor.getColumnIndex("price"))
                Log.d("MainActivity", "book name is $name")
                Log.d("MainActivity", "book author is $author")
                Log.d("MainActivity", "book pages is $pages")
                Log.d("MainActivity", "book price is $price")
            } while (cursor.moveToNext())
        }
        cursor.close()
    }
  5. 使用sql
    db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
        arrayOf("The Da Vinci Code", "Dan Brown", "454", "16.96")
    )
    db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
        arrayOf("The Lost Symbol", "Dan Brown", "510", "19.95")
    )
    db.execSQL("update Book set price = ? where name = ?", arrayOf("10.99", "The Da Vinci Code"))
    db.execSQL("delete from Book where pages > ?", arrayOf("500"))
    val cursor = db.rawQuery("select * from Book", null)

事务

val dbHelper = MyDatabaseHelper(this, "BookStore.db", 2)
replaceData.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() // 结束事务
    }
}

数据库版本升级

新安装的软件使用onCreate方法,没有其他注意点。升级软件使用onUpgrade,要对每一种旧版本进行检测

  1. 版本1 只有一个book
    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(createBook)
        db.execSQL(createCategory)
    }
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        if (oldVersion <= 1) {
            db.execSQL(createCategory)
        }
    }
  2. 版本2 有 book 和 category 两张表
    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(createBook)
        db.execSQL(createCategory)
    }
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        if (oldVersion <= 1) {
            db.execSQL(createCategory)
        }
    }
  3. 版本3 为 book 表添加字段
    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(createBook)
        db.execSQL(createCategory)
    }
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        if (oldVersion <= 1) {
            db.execSQL(createCategory)
        }
        if (oldVersion <= 2) {
            db.execSQL("alter table Book add column category_id integer")
        }
    }

Android运行时权限

  1. 普通权限
    不会影响用户,系统会自动帮我们进行授权,如接收开启启动权限
  2. 危险权限
    可能影响用户(较少,android10共11组30个)
    原则上用户同意某个权限后同组权限也被同意,但安卓系统随时可能跳转权限组
  3. 特殊权限(少见)

静态申请权限

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.broadcasttest">
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    ...
</manifest>

动态申请权限

Intent.ACTION_DIAL 是打开拨号界面,是普通权限
Intent.ACTION_CALL 是拨打电话

// 拨打电话消耗电话费,是危险操作。这里拨打前申请权限
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        makeCall.setOnClickListener {
            // 1. 检查是否被授权
            if (ContextCompat.checkSelfPermission(this,
                    Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                // 2. 申请权限
                ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), 1)
            } else {
                call()
            }
        }
    }
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            1 -> {
                // 3. 判断是否获得权限
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    call()
                } else {
                    Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
    private fun call() {
        // 拨打电话
        try {
            val intent = Intent(Intent.ACTION_CALL)
            intent.data = Uri.parse("tel:10086")
            startActivity(intent)
        } catch (e: SecurityException) {
            e.printStackTrace()
        }
    }
}

ContentProvider

跨程序共享数据

使用现有的ContentProvider

一个应用程序通过ContentProvider对其数据提供了外部访问接口,那么任何其他的应用程序都可以对这部分数据进行访问。如通信录、短信、媒体库等提供的接口
使用 ContentResolver 类,它提供了 insert update delete query 等方法,不接收表名而是接收内容URI。内容URI由 content:// 协议声明、包名.provider、用于区分表的路径:content://com.example.app.provider/path1

//查
val uri = Uri.parse("content://com.example.app.provider/table1")
val cursor = contentResolver.query(
    uri,
    projection,
    selection,
    selectionArgs,
    sortOrder)
while (cursor.moveToNext()) {
    val column1 = cursor.getString(cursor.getColumnIndex("column1"))
    val column2 = cursor.getInt(cursor.getColumnIndex("column2"))
}
cursor.close()
//增
val values = contentValuesOf("column1" to "text", "column2" to 1)
contentResolver.insert(uri, values)
//改
val values = contentValuesOf("column1" to "")
contentResolver.update(uri, values, "column1 = ? and column2 = ?", arrayOf("text", "1"))
//删
contentResolver.delete(uri, "column2 = ?", arrayOf("1"))
/**
 * 申请权限访问通讯录并将访问的数据放入 ListView 控件
 * 要在 AndroidManifest.xml 中声明权限:<uses-permission android:name="android.permission.READ_CONTACTS" />
 */
class MainActivity : AppCompatActivity() {
    private val contactsList = ArrayList<String>()
    private lateinit var adapter: ArrayAdapter<String>
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, contactsList)
        contactsView.adapter = adapter
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,arrayOf(Manifest.permission.READ_CONTACTS), 1)
        } else {
            readContacts()
        }
    }
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            1 -> {
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    readContacts()
                } else {
                    Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
    private fun readContacts() {
        // 查询联系人数据
        contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null)?.apply {
            while (moveToNext()) {
                // 获取联系人姓名
                val displayName = getString(getColumnIndex(
                    ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
                // 获取联系人手机号
                val number = getString(getColumnIndex(
                    ContactsContract.CommonDataKinds.Phone.NUMBER))
                contactsList.add("$displayName\n$number")
            }
            adapter.notifyDataSetChanged()
            close()
        }
    }
}

创建自己的ContentProvider

URI 路径:

content://com.example.app.provider/table1    // 指定表
content://com.example.app.provider/table1/1  // 指定表中id为1的数据
content://com.example.app.provider/*         // *匹配任意长度字符串
content://com.example.app.provider/table1/#  // #匹配任意长度数字

andoid的 MIME 规定:
1. 以 vnd 开头
2. URI以路径结尾接上 android.cursor.dir/
      以id结尾则接上 android.cursor.item/
3. 最后接上 vnd.<authority>.<path>

content://com.example.app.provider/table1   对应的MIME为 vnd.android.cursor.dir/vnd.com.example.app.provider.table1
content://com.example.app.provider/table1/1 对应的MIME为 vnd.android.cursor.item/vnd.com.example.app.provider.table1

创建ContentProvider,创建java或kt文件,并在 AndroidManifest.xml 中注册:

通过继承抽象类:

class MyProvider : ContentProvider() {
    // 初始化 ContentProvider 时调用,完成对数据库的创建和升级等操作,返回true成功,false失败
    override fun onCreate(): Boolean {
        return false
    }
    override fun query(uri: Uri,                   // 指定表
                       projection: Array<String>?, // 指定列
                       selection: String?,         // 指定行
                       selectionArgs: Array<String>?,
                       sortOrder: String?
    ): Cursor? {
        return null
    }
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        return null
    }
    // 返回受影响的行数
    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
        return 0
    }
    // 返回被删除的行数
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
        return 0
    }
    // 根据 URI 返回 MIME 类型
    override fun getType(uri: Uri) = when (uriMatcher.match(uri)) {
        table1Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table1"
        table1Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table1"
        table2Dir -> "vnd.android.cursor.dir/vnd.com.example.app.provider.table2"
        table2Item -> "vnd.android.cursor.item/vnd.com.example.app.provider.table2"
        else -> null
    }
}

通过ContentProvider对外提供操作Sqlite的接口

ContentProvider 包装 Sqlite 操作
 class DatabaseProvider : ContentProvider() {

    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: MyDatabaseHelper? = null

    private val uriMatcher by later {
        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 onCreate() = context?.let {
        dbHelper = MyDatabaseHelper(it, "BookStore.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 (uriMatcher.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 insert(uri: Uri, values: ContentValues?) = dbHelper?.let {
        // 添加数据
        val db = it.writableDatabase
        val uriReturn = when (uriMatcher.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 update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?) =
        dbHelper?.let {
            // 更新数据
            val db = it.writableDatabase
            val updatedRows = when (uriMatcher.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 -> 0
            }
            updatedRows
        } ?: 0

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) = dbHelper?.let {
        // 删除数据
        val db = it.writableDatabase
        val deletedRows = when (uriMatcher.match(uri)) {
            bookDir -> db.delete("Book", selection, selectionArgs)
            bookItem -> {
                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 -> 0
        }
        deletedRows
    } ?: 0

    override fun getType(uri: Uri) = when (uriMatcher.match(uri)) {
        bookDir -> "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book"
        bookItem -> "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book"
        categoryDir -> "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category"
        categoryItem -> "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category"
        else -> null
    }
}

 

posted @ 2023-01-04 16:46  某某人8265  阅读(19)  评论(0编辑  收藏  举报