本模块共有四篇文章,参考郭神的《第一行代码》,对Content Provider的学习做一个详细的笔记,大家可以一起交流一下:
内容提供器的用法一般有两种,
一种是使用现有的内容
提供器来读取和操作相应程序中的数据,
另一种是创建自己的
内容提供器给我们程序的数据提供外部访问接口。
那么接下来我们就一个一个开始学习吧,首先从使用现有的内容提供器
开始。
下面我们就来看一看,内容提供器到底是如何使用的。
对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助Content-Resolver
类,可以通过Context中的 getContentResolver()
方法获取到该类的实例。
Content-Resolver中提供了一系列的方法用于对数据进行CRUD操作,
其中
insert()方法用于添加数据,
update()方法用于更新数据,
delete()方法用于删除数据,
query()方法用于查询数据。
有没有似曾相识的感觉?没错,SQLiteDatabase中也是使用这几个方法来进行CRUD操作的,只不过它们在方法参数上稍微有一些区别。
不同于SQLiteDatabase,ContentResolver中的增删改查方法都是不接收表名参数的,而是使用一个Uri参数代替,这个参数被称为内容URI。内容URI给内容提供器中的数据建立了唯一标识符,它主要由两部分组成:authority和path。
authority是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名是com-example.app,那么该程序对应的authority就可以命名com.example.app.provider。
path则是用于对同一应用程序中不同的表做区分的,通常都会添加到authority的后面。比如某个程序的数据库里存在两张表:tablel和table2,这时就可以将path分别命名为/tablel和/tabIe2。
然后把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字符串之后,我们还需要将它解析成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的时候,游标就会向下移动了。
可以看到,是将待添加的数据组装到ContentValues中,然后调用ContentResolver的insert()方法,将Uri和ContentValues作为参数传入即可。
下面运用上面所学的知识,看看如何读取系统电话簿中的联系人信息。
现在在模拟器中手动添加几个联系人,以便稍后进行读取:
至此便可新建一个ContactsTest项目开始正式敲代码了。
首先编写一下布局文件,让读取出来的联系人信息显示在一个ListView中:
MainActivity.java:
代码架构简析:
具体code:
public class MainActivity extends AppCompatActivity {
ArrayAdapter adapter;
List 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(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:
运行结果: