Android 访问其他程序中的数据
ContentProvider 的用法一般有两种:
一种是使用现有的ContentProvider 读取和操作相应程序中的数据;
另一种是创建自己的ContentProvider ,给程序的数据提供外部访问接口。
ContentResolver的基本用法
对于每一个应用程序来说,如果想要访问ContentProvider 中共享的数据,就一定要借助 ContentResolver 类,可以通过Context 中的getContentResolver()方法获取该类的实例。
ContentResolver 中提供了一系列的方法用于对数据进行增删改查操作,其中insert() 方法用于添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方 法用于查询数据。
有没有似曾相识的感觉?没错,SQLiteDatabase 中也是使用这几个方法进 行增删改查操作的,只不过它们在方法参数上稍微有一些区别。
不同于SQLiteDatabase ,ContentResolver 中的增删改查方法都是不接收表名参数的,而是使用一个Uri参数代替,这个参数被称为内容URI。内容URI给ContentProvider 中的数据建立了唯一标识符,它主要由两部分组成:authority 和path 。
- authority 是用于对不同的应用程序做区分的,一般为了避免冲突,会采用应用包名的方式进行命名。比如某个应用的包名是 com.example.app ,那么该应用对应的authority 就可以命名为 com.example.app.provider 。
- path 则是用于对同一应用程序中不同的表做区分的,通常会添 加到authority 的后面。比如某个应用的数据库里存在两张表table1 和table2 ,这时就可以将 path 分别命名为/table1 和/table2 ,然后把authority 和path 进行组合,内容URI就变成了 com.example.app.provider/table1 和com.example.app.provider/table2 。
不过,目前还 很难辨认出这两个字符串就是两个内容URI,我们还需要在字符串的头部加上协议声明。因此, 内容URI最标准的格式如下:
1 2 3 | content: //com.example.app.provider/table1 content: //com.example.app.provider/table2 |
有没有发现,内容URI可以非常清楚地表达我们想要访问哪个程序中哪张表里的数据。也正是因此,ContentResolver 中的增删改查方法才都接收Uri对象作为参数。如果使用表名的话,系统将无法得知我们期望访问的是哪个应用程序里的表。 在得到了内容URI字符串之后,我们还需要将它解析成Uri对象才可以作为参数传入。解析的方 法也相当简单,代码如下所示:
1 | val uri = Uri.parse( "content://com.example.app.provider/table1" ) |
只需要调用Uri.parse()方法,就可以将内容URI字符串解析成Uri对象了。
现在我们就可以使用这个Uri对象查询table1 表中的数据了,代码如下所示:
1 | val cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder) |
这些参数和SQLiteDatabase 中query()方法里的参数很像,但总体来说要简单一些,毕竟这是在访问其他程序中的数据,没必要构建过于复杂的查询语句。
query()方法的参数说明
查询完成后返回的仍然是一个Cursor对象,这时我们就可以将数据从Cursor对象中逐个读取出来了。读取的思路仍然是通过移动游标的位置遍历Cursor的所有行,然后取出每一行中相应列的数据,代码如下所示:
1 2 3 4 5 | while (cursor.moveToNext()) { val column1 = cursor.getString(cursor.getColumnIndex( "column1" )) val column2 = cursor.getInt(cursor.getColumnIndex( "column2" )) } cursor.close() |
向 table1 表中添加一条数据,代码如下所示:
1 2 | val values = contentValuesOf( "column1" to "text" , "column2" to 1 ) contentResolver.insert(uri, values) |
将待添加的数据组装到ContentValues中,然后调用ContentResolver 的 insert()方法,将Uri和ContentValues作为参数传入即可。
更新这条新添加的数据,把column1的值清空,可以借助ContentResolver 的 update()方法实现,代码如下所示:
1 2 | val values = contentValuesOf( "column1" to "" ) contentResolver.update(uri, values, "column1 = ? and column2 = ?" , arrayOf( "text" , "1" )) |
调用ContentResolver 的delete()方法将这条数据删除掉,代码如下所示:
1 | contentResolver.delete(uri, "column2 = ?" , arrayOf( "1" )) |
读取系统联系人
修改activity_main.xml 中的代码,如下所示:
1 2 3 4 5 6 7 8 9 10 | <LinearLayout xmlns:android= "http://schemas.android.com/apk/res/android" android:layout_width= "match_parent" android:layout_height= "match_parent" android:orientation= "vertical" > <ListView android:id= "@+id/contactsView" android:layout_width= "match_parent" android:layout_height= "match_parent" ></ListView> </LinearLayout> |
修改MainActivity 中的代码,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | package com.xc.helloworld import android.Manifest import android.annotation.SuppressLint import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import android.provider.ContactsContract import android.util.Log import android.widget.ArrayAdapter import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import kotlinx.android.synthetic.main.activity_main.* import java.util.* 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() } } } } @SuppressLint ( "Range" ) 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() } } } |
修改 AndroidManifest.xml 中的代码,如下所示:
1 2 3 4 5 | <?xml version= "1.0" encoding= "utf-8" ?> <manifest xmlns:android= "http://schemas.android.com/apk/res/android" xmlns:tools= "http://schemas.android.com/tools" > <uses-permission android:name= "android.permission.READ_CONTACTS" /> |
创建自己的ContentProvider
...
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
2022-01-19 欧几里得算法(又称辗转相除法)
2022-01-19 数字证书
2022-01-19 数字签名
2022-01-19 消息认证码
2022-01-19 迪菲-赫尔曼密钥交换
2022-01-19 混合加密
2022-01-19 公开密钥加密