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最标准的格式如下:
content://com.example.app.provider/table1 content://com.example.app.provider/table2
有没有发现,内容URI可以非常清楚地表达我们想要访问哪个程序中哪张表里的数据。也正是因此,ContentResolver 中的增删改查方法才都接收Uri对象作为参数。如果使用表名的话,系统将无法得知我们期望访问的是哪个应用程序里的表。 在得到了内容URI字符串之后,我们还需要将它解析成Uri对象才可以作为参数传入。解析的方 法也相当简单,代码如下所示:
val uri = Uri.parse("content://com.example.app.provider/table1")
只需要调用Uri.parse()方法,就可以将内容URI字符串解析成Uri对象了。
现在我们就可以使用这个Uri对象查询table1 表中的数据了,代码如下所示:
val cursor = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder)
这些参数和SQLiteDatabase 中query()方法里的参数很像,但总体来说要简单一些,毕竟这是在访问其他程序中的数据,没必要构建过于复杂的查询语句。
query()方法的参数说明
查询完成后返回的仍然是一个Cursor对象,这时我们就可以将数据从Cursor对象中逐个读取出来了。读取的思路仍然是通过移动游标的位置遍历Cursor的所有行,然后取出每一行中相应列的数据,代码如下所示:
while (cursor.moveToNext()) { val column1 = cursor.getString(cursor.getColumnIndex("column1")) val column2 = cursor.getInt(cursor.getColumnIndex("column2")) } cursor.close()
向 table1 表中添加一条数据,代码如下所示:
val values = contentValuesOf("column1" to "text", "column2" to 1) contentResolver.insert(uri, values)
将待添加的数据组装到ContentValues中,然后调用ContentResolver 的 insert()方法,将Uri和ContentValues作为参数传入即可。
更新这条新添加的数据,把column1的值清空,可以借助ContentResolver 的 update()方法实现,代码如下所示:
val values = contentValuesOf("column1" to "") contentResolver.update(uri, values, "column1 = ? and column2 = ?", arrayOf("text", "1"))
调用ContentResolver 的delete()方法将这条数据删除掉,代码如下所示:
contentResolver.delete(uri, "column2 = ?", arrayOf("1"))
读取系统联系人
修改activity_main.xml 中的代码,如下所示:
<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 中的代码,如下所示:
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 中的代码,如下所示:
<?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
...