Android四大组件之ContentProvider

一 ContentProvider简介

ContentProvider主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。

  • Android现在将常用的权限大致归成了两类:普通权限、危险权限
  • 普通权限:
    不会直接威胁到用户的隐私和安全的权限,对于这部分权限的申请,系统会自动帮我们进行授权,不需要用户手动操作,只需要在AndroidManifest.xml中声明。
  • 危险权限:
    可能会触及用户隐私或者对设备安全性造成影响的权限,如获取设备联系人信息、定位设备的地理位置等,对于这部分权限申请,必须由用户手动授权才可以,否则程序就无法使用相应的功能。大致危险权限如下:
    Android危险权限
    编号 权限组 权限
    1.CALENDAR
    READ_CALENDAR WRITE_CALENDAR
    2.CAMERA
    CAMERA
    3.CONTACTS
    READ_CONTACTSWRITE_CONTACTSGET_ACCOUNTS
    4.LOCATION
    ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION
    5.MICROPHONE
    RECORD_AUDIO
    6.PHONE
    READ_PHONE_STATE CALL_PHONE READ_CALL_LOGWRITE_CALL_LOG ADD_VOICEMAIL USE_SIPPROCESS_OUTGOING_CALLS
    7.SENSORS
    BODY_SENSORS
    8.SMS
    SEND_SMSRECEIVE_SMSREAD_SMSRECEIVE_WAP_PUSHRECEIVE_MMS
    9.STORAGE
    READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE

二 ContentResolve

  • URI

内容URI给Contentprovider中的数据建立了唯一标识符,它主要有两部分组成:authoritypath。authority是用于对不同的应用程序做区分的,一般为了避免冲突,会采用应用包名的方式命名。比如某个应用的包名是com.example.app,那么该应用对应的authority就可以命名为com.example.app.provider。path则是用于对同一应用程序中不同的表做区分的,通常会添加到authority后面。比如某个应用的数据库里面有两张表table1和table2,这时就可以将path分别命名为/table1和/table2,然后把authority和path进行组合,标准如下:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
很清楚地表达了想要访问哪个程序中哪张表里的数据

  • 得到内容URI字符串后,需要将它解析成Uri对象才可以作为参数传入
    val uri = Uri.parse("content://com.example.app.provider/table1")
  • 增删改查与sqlite类似
  • 读取系统联系人

创建自己的ContentProvider

  • 一个标准的URI写法:
    content://com.example.app.provider/table1
    表示调用方期望访问的是com.example.app这个应用的table1表中的数据。
    除此之外,我们还可以在这个内容uri后面加上一个id,例如:
    content://com.example.app.provider/table1/1:
    表示调用方期望访问的是com.example.app这个应用的table1表中id为1的数据
  • 我们还可以使用通配符分别匹配这两种格式的内容URI,规则如下:

(*)表示匹配任意长度的任意字符
(#)表示匹配任意长度的数字

一个能够匹配任意表的内容URI格式:
content://com.example.app.provider/*
一个能够匹配table1表中任意一行数据的内容URI格式:
content://com.example.app.provider/table1/*
接着,再借助UriMatcher这个类就可以轻松地实现匹配内容URI的功能。UriMatcher中提供了一个addURI()方法,它接收三个参数,可以分别把authority、path和一个自定义代码传进去。

class MyProvider : ContentProvider() {

    private val table1Dir = 0
    private val table1Item = 1
    private val table2Dir = 2
    private val table2Item = 3

    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)

    init {
        uriMatcher.addURI("com.example.kotlin_contentprovider.provider", "table1", table1Dir)
        uriMatcher.addURI("com.example.kotlin_contentprovider.provider", "table1/#", table1Item)
        uriMatcher.addURI("com.example.kotlin_contentprovider.provider", "table2", table2Dir)
        uriMatcher.addURI("com.example.kotlin_contentprovider.provider", "table2/#", table2Item)
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        TODO("Not yet implemented")
    }

    override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? {
        when(uriMatcher.match(uri)){
            table1Dir -> {
                //查询table1表中的所有数据
            }
            table1Item -> {
                //查询table1表中的单条数据
            }
            table2Dir -> {
                //查询table2表中的所有数据
            }
            table2Item -> {
                //查询table2表中的单条数据
            }
        }
    }

    override fun onCreate(): Boolean {
        TODO("Not yet implemented")
    }

    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
        TODO("Not yet implemented")
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        TODO("Not yet implemented")
    }

    override fun getType(uri: Uri): String? =
        when(uriMatcher.match(uri)){
            table1Dir -> "vnd.android.cursor.dir/vnd.com.example.kotlin_contentprovider.provider.table1"
            table1Item -> "vnd.android.cursor.item/vnd.com.example.kotlin_contentprovider.provider.table1"
            table2Dir -> "vnd.android.cursor.dir/vnd.com.example.kotlin_contentprovider.provider.table2"
            table2Item -> "vnd.android.cursor.item/vnd.com.example.kotlin_contentprovider.provider.table2"
            else -> null
        }
}

getType()方法,它是所有的ContentProvider都必须提供的一个方法,用于获取Uri对象所对应的MIME类型,一个内容URI所对应的MIME字符串主要由3部分组成,Android对这3个部分做了如下规定:

  • 必须以vnd开头
  • 如果内容URI以路径结尾,则后接android.cursor.dir/;如果内容URI以id结尾,则后接android.cursor.item/。
  • 最后接上vnd.<authority>.<path>
    所以,对于com.example.kotlin_contentprovider.provider.table1这个内容uri,它所对应的MIME类型就可以写成:
  • vnd.android.cursor.dir/vnd.com.example.kotlin_contentprovider.provider.table1

实现跨程序数据共享

class DatabaseProvider : ContentProvider() {

    private val bookDir = 0
    private val bookItem = 1
    private val categoryDir = 2
    private val categoryItem = 3
    private val authority = "com.example.kotlin_sqlite.provider"
    private var dbHelper : MyDatabaseHelper ?= null

    private val uriMatcher 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 onCreate() = context?.let {
        dbHelper = MyDatabaseHelper(it, "BookStore.db", 2)
        true
    } ?: false

    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 query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?)=
            dbHelper?.let {
                val db = it.writableDatabase
                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 update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?) = dbHelper?.let { 
        val db = it.writableDatabase
        val updateRows = 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
        }
        updateRows
    } ?: 0

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out 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.kotlin_sqlite.provider.book"
        bookItem -> "vnd.android.cursor.item/vnd.com.example.kotlin_sqlite.provider.book"
        categoryDir -> "vnd.android.cursor.dir/vnd.com.example.kotlin_sqlite.provider.category"
        categoryItem -> "vnd.android.cursor.item/vnd.com.example.kotlin_sqlite.provider.category"
        else -> null
    }
}
  • 综合利用Getter方法语法糖、?.操作符、let函数、?:操作符以及单行代码函数语法糖。
  • 访问自定义ContentProvider的程序
    注意:要在访问其他程序的ContentProvider的程序Manifest中加入访问哪个程序的包名
  • Manifest.xml
<queries>
        <package android:name="com.example.kotlin_sqlite"/>
</queries>
class MainActivity : AppCompatActivity() , View.OnClickListener{
    private lateinit var addData: Button
    private lateinit var queryData:Button
    private lateinit var updateData: Button
    private lateinit var deleteData:Button
    private val mAuthority = "content://com.example.kotlin_sqlite"
    private var bookId: String ?= null
    private val TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initView()
    }

    private fun initView() {
        addData = findViewById(R.id.addData)
        queryData = findViewById(R.id.queryData)
        updateData = findViewById(R.id.updateData)
        deleteData = findViewById(R.id.deleteData)
        addData.setOnClickListener(this)
        queryData.setOnClickListener(this)
        updateData.setOnClickListener(this)
        deleteData.setOnClickListener(this)
    }

    override fun onClick(v: View?) {
        when(v?.id){
            R.id.addData -> add()
            R.id.queryData -> select()
            R.id.updateData -> change()
            R.id.deleteData -> delete()
        }
    }

    private fun delete() {
        //删除数据
        bookId?.let {
            val uri = Uri.parse("$mAuthority/book/$it")
            contentResolver.delete(uri, null,null)
        }
    }

    private fun change() {
        //更新数据
        bookId?.let {
            val uri = Uri.parse("$mAuthority/book/$it")
            val values = contentValuesOf("name" to "Yan", "pages" to 1314520)
            contentResolver?.update(uri,values,null,null)
        }
    }

    private fun select() {
        //查询数据
        val uri = Uri.parse("$mAuthority/book")
        contentResolver.query(uri,null,"name = ?", arrayOf("Yan"),null,null)
            ?.apply {
                while (moveToNext()){
                    val name = getString(getColumnIndex("name"))
                    val author = getString(getColumnIndex("author"))
                    val pages = getString(getColumnIndex("pages"))
                    val price = getString(getColumnIndex("price"))
                    Log.e(TAG, "name is $name")
                    Log.e(TAG, "author is $author")
                    Log.e(TAG, "pages is $pages")
                    Log.e(TAG, "price is $price")
                }
                close()
            }
    }

    private fun add() {
        //添加数据
        val uri = Uri.parse("$mAuthority/book")
        val values = contentValuesOf("name" to "Yan", "author" to "Lvsu", "pages" to 1314, "price" to 1314)
        val newUri = contentResolver.insert(uri, values)
        bookId = newUri?.pathSegments?.get(1)
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,635评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,543评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,083评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,640评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,640评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,262评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,833评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,736评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,280评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,369评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,503评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,870评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,340评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,460评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,909评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,512评论 2 359

推荐阅读更多精彩内容