本模块共有四篇文章,参考郭神的《第一行代码》,对Content Provider的学习做一个详细的笔记,大家可以一起交流一下:
- 跨程序共享数据——Content Provider 之 运行时权限解析以及申请的实现(可完美解决java.lang.SecurityException:Permission Denial 问题)
- 跨程序共享数据——Content Provider 之 ContentResolver基本用法 & 一个读取系统联系人的Demo(即本文)
- 跨程序共享数据——Content Provider 之 创建自己的内容提供器
- Content Provider 之 最终弹 实战体验跨程序数据共享(结合SQLiteDemo)
**内容提供器的用法一般有两种,
一种是使用现有的内容
提供器来读取和操作相应程序中的数据,
另一种是创建自己的
内容提供器给我们程序的数据提供外部访问接口。**
那么接下来我们就一个一个开始学习吧,首先从使用现有的内容提供器
开始。
- 如果一个应用程序通过内容提供器对其数据提供了外部访问接口,那么任何其他的应用程序就都可以对这部分数据进行访问。
- Android系统中自带的电话簿、短信、媒体库等程序都提供了类似的访问接口,这就使得第三方应用程序可以充分地利用这部分数据来实现更好的功能。
下面我们就来看一看,内容提供器到底是如何使用的。
1.ContentResolver的基本用法
- 对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助
Content-Resolver
类,可以通过Context中的getContentResolver()
方法获取到该类的实例。 - **Content-Resolver中提供了一系列的方法用于对数据进行CRUD操作,
其中
insert()方法用于添加数据,
update()方法用于更新数据,
delete()方法用于删除数据,
query()方法用于查询数据。**
有没有似曾相识的感觉?没错,SQLiteDatabase中也是使用这几个方法来进行CRUD操作的,只不过它们在方法参数上稍微有一些区别。
内容URI
不同于SQLiteDatabase,ContentResolver中的增删改查方法都是不接收表名参数的,而是使用一个Uri参数代替,这个参数被称为内容URI。内容URI给内容提供器中的数据建立了唯一标识符,它主要由两部分组成:authority和path。
authority
authority是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名是com-example.app,那么该程序对应的authority就可以命名com.example.app.provider。
path
path则是用于对同一应用程序中不同的表做区分的,通常都会添加到authority的后面。比如某个程序的数据库里存在两张表:tablel和table2,这时就可以将path分别命名为/tablel和/tabIe2。
组合成内容URI
**然后把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可以非常清楚地表达出我们想要访问哪个程序中哪张表里的数据。
也正是因此,ContentResoIver中的增删改查方法才都接收Uri对象作为参数,因为如果使用表名的话,系统将无法得知我们期望访问的是哪个应用程序里的表。**
Uri.parse()解析URI字符串
在得到了内容URI字符串之后,我们还需要将它解析成Uri对象才可以作为参数传入。解析的方法也相当简单,代码如下所示:
Uri uri= Uri.parse("content://com.example.app.provider/tablel")
**只需要调用Uri.parse()方法,就可以将内容URI字符串解析成Uri对象了。
现在我们就可以使用这个Uri对象来查询tablel表中的数据了,代码如下所示:**
Cursor cursor =getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder);
**这些参数和SQLiteDatabase中query()方法里的参数很像,但总体来说要简单一些,毕竟这
是在访问其他程序中的数据,没必要构建过于复杂的查询语句。下表对使用到的这部分参数进行
了详细的解释。**
遍历数据:
注意这里的moveToNext:第一次调用moveToNext的时候,默认就是移动到了第一行的游标位置,和调用moveToFirst的效果是一样的,但是第二次调用moveToNext的时候,游标就会向下移动了。
inert应用
可以看到,是将待添加的数据组装到ContentValues中,然后调用ContentResolver的insert()方法,将Uri和ContentValues作为参数传入即可。
update()应用
delete()应用
2.读取联系人
下面运用上面所学的知识,看看如何读取系统电话簿中的联系人信息。
现在在模拟器中手动添加几个联系人,以便稍后进行读取:
可以看到一开始电话簿里是没有任何联系人的,下面进行创建:
那个,联系人名字就,高傲牛肉方便面、中原一点红、香辣火腿肠,咳咳。。。
**至此便可新建一个ContactsTest项目开始正式敲代码了。
首先编写一下布局文件,让读取出来的联系人信息显示在一个ListView中:**
<?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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.contactstest.MainActivity">
<ListView
android:id="@+id/contacts_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
MainActivity.java:
代码架构简析:
具体code:
public class MainActivity extends AppCompatActivity {
ArrayAdapter<String> adapter;
List<String> contactsList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//ListView实例化以及装载适配器
ListView contactsView = (ListView)findViewById(R.id.contacts_view);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,contactsList);
contactsView.setAdapter(adapter);
//申请权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.
READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this, new String[]{ Manifest.
permission.READ_CONTACTS }, 1);
}else{
readContacts();
}
}
//封装读通讯录的操作
private void readContacts(){
Cursor cursor = null;
try{
//查询联系人数据
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.
Phone.CONTENT_URI, null, null, null, null);
if(cursor != null){
while(cursor.moveToNext()){
//获取联系人姓名
String displayName = cursor.getString(cursor.getColumnIndex
(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//获取联系人手机号
String number = cursor.getString(cursor.getColumnIndex
(ContactsContract.CommonDataKinds.Phone.NUMBER));
//拼接数据,加入列表
contactsList.add(displayName + "\n" + number);
}
//通知刷新ListView
adapter.notifyDataSetChanged();
}
}catch(Exception e){
e.printStackTrace();
}finally {
if(cursor != null){
cursor.close();//!!!!!!!!!!!!!!!!!!!!!!1及一定要关掉
}
}
}
//权限申请的返回处理
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 1:
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
readContacts();
}else{
Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
}
break;
default:
}
}
}
代码简析见注释,下面详解一下:
**在onCreate()方法中,首先获取ListView的实例并给它设置适配器,
然后调用处理申请运行时权限,因为READ_CONTACTS权限是属于危险权限。**
在用户授权之后调用readContacts()方法来读取系统联系人信息。
**下面解析readContacts()方法:
首先是使用ContentResolver的query()方法来查询系统的联系人数据。**
注意这里传入的Uri参数不是来自调用Uri.parse()方法解析的内容URI字符串,因为ContactsContract.CommonDataKinds.Phone类已经帮我们做好了封装,提供了一个CONTENT_URI常量,这个常量就是使用Uri.parse()方法解析出来的结果。
**接着我们对Cursor对象进行遍历,将联系人姓名和手机号逐个取出,
联系人姓名这一列对应常量是ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
联系人手机号这一列对应的常量是ContactsContract.CommonDataKinds.Phone.NUMBER。
两列数据都取出后,将它们进行拼接并且在中间加上换行符,然后
将拼接后的数据add到ListView的数据源里,并通知刷新一下ListView。
最后记得将Cursor对象关闭掉。**
最后声明读取系统联系人的权限。修改AndroidManifest.xmI:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
运行结果: