一、基本概念
内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,
它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据
它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据
的安全性。
二、自定义内容提供者实现增删改查
1、创建一PersonProvider继承ContentProvider,实现六个方法,增删改查以及onCreate和getType。(因篇幅限制,这里只贴出了具有代表性的几个方法)
public class PersonProvider extends ContentProvider {
private MyOpenHelper oh;
SQLiteDatabase db;
//内容提供者创建时调用
@Override
public boolean onCreate() {
oh = new MyOpenHelper(getContext());
db = oh.getWritableDatabase();
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Cursor cursor = db.query("person", projection, selection, selectionArgs, null, null, sortOrder, null);
return cursor;
}
@Override
public String getType(Uri uri) {
return null;
}
//此方法供其他应用调用,用于往people数据库里插数据
//values:由其他应用传入,用于封装要插入的数据
//uri:内容提供者的主机名,也就是地址
@Override
public Uri insert(Uri uri, ContentValues values) {
//使用uri匹配器匹配传入的uri
db.insert("person", null, values);
return uri;
}
2、 因为我们的内容提供者主要是在数据库中进行增删改查,所以我们再创建一个MyOpenHelper继承SQLiteOpenHelper,创建一个
people.db
然后创建一个安卓Junit测试类,创建出我们的数据库文件。因为代码很简单,这里就不再贴出。
3、在AndroidManifest中配置我们的内容提供者
authorities属性必须加。是自定义的一个主机名,用来给外部程序访问时传入的Uri参数
exported=“true”代表是外部程序可以修改我们内容提供者提供的数据,也必须加。
<provider android:name="com.itheima.customcontentprovider.provider.PersonProvider"
android:authorities="com.itheima.people"
android:exported="true"
></provider>
4、然后我们再重新创建一个工程,来读取我们的内容提供者的数据。
public class MainActivity extends Activity {
private ContentResolver cResolver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cResolver = getContentResolver();
}
public void insert(View view) {
ContentValues values = new ContentValues();
values.put("name", "lizhenquan");
cResolver.insert(Uri.parse("content://com.zhenquan.myprovider/teacher"), values);
}
public void myquery(View view) {
Cursor cursor = cResolver.query(Uri.parse("content://com.zhenquan.myprovider"), null, null, null, null);
while(cursor.moveToNext()){
String nameString = cursor.getString(1);
String moneString = cursor.getString(2);
Log.d("TAG", nameString+","+moneString);
}
}
}
这里我们需要注意的就是一个标准的Uri的格式:
一个标准的内容 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 的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以 id 结尾就表示期望访问该表中拥有相应 id 的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容 URI,规则如下。
1. *:表示匹配任意长度的任意字符
2. #:表示匹配任意长度的数字
所以,一个能够匹配任意表的内容 URI 格式就可以写成:
content://com.example.app.provider/*
而一个能够匹配 table1 表中任意一行数据的内容 URI 格式就可以写成:
2. #:表示匹配任意长度的数字
所以,一个能够匹配任意表的内容 URI 格式就可以写成:
content://com.example.app.provider/*
而一个能够匹配 table1 表中任意一行数据的内容 URI 格式就可以写成:
content://com.example.app.provider/table1/#
三、Uri匹配器
上面我们的一个案例是只创建了一个person表,但是在实际开发中,数据库肯定不会只有一个表,那么面对多个 表的时候,内容提供者如何区分要查询的是哪张表,或者要插入的是哪张表呢?
这就引出了我们的Uri匹配器的概念。
先将数据库升级,增加一个teacher表,然后修改我们的PersonProvider如下(因篇幅限制,所以我们只贴出insert和query):
public class PersonProvider extends ContentProvider {
private MyOpenHelper oh;
SQLiteDatabase db;
//创建uri匹配器对象
static UriMatcher um = new UriMatcher(UriMatcher.NO_MATCH);
//检测其他用户传入的uri与匹配器定义好的uri中,哪条匹配
static {
um.addURI("com.itheima.people", "person", 1);//content://com.itheima.people/person
um.addURI("com.itheima.people", "teacher", 2);//content://com.itheima.people/teacher
um.addURI("com.itheima.people", "person/#", 3);//content://com.itheima.people/person/4
}
//内容提供者创建时调用
@Override
public boolean onCreate() {
oh = new MyOpenHelper(getContext());
db = oh.getWritableDatabase();
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Cursor cursor = null;
if(um.match(uri) == 1){
cursor = db.query("person", projection, selection, selectionArgs, null, null, sortOrder, null);
}
else if(um.match(uri) == 2){
cursor = db.query("teacher", projection, selection, selectionArgs, null, null, sortOrder, null);
}
else if(um.match(uri) == 3){
//把uri末尾携带的数字取出来
long id = ContentUris.parseId(uri);
cursor = db.query("person", projection, "_id = ?", new String[]{id + ""}, null, null, sortOrder, null);
}
else{
throw new IllegalArgumentException("uri又有问题哟亲么么哒");
}
return cursor;
}
@Override
public String getType(Uri uri) {
if(um.match(uri) == 1){
return "vnd.android.cursor.dir/person";
}
else if(um.match(uri) == 3){
return "vnd.android.cursor.item/person";
}
return null;
}
//此方法供其他应用调用,用于往people数据库里插数据
//values:由其他应用传入,用于封装要插入的数据
//uri:内容提供者的主机名,也就是地址
@Override
public Uri insert(Uri uri, ContentValues values) {
//使用uri匹配器匹配传入的uri
if(um.match(uri) == 1){
db.insert("person", null, values);
//发送数据改变的通知
//uri:通知发送到哪一个uri上,所有注册在这个uri上的内容观察者都可以收到这个通知
getContext().getContentResolver().notifyChange(uri, null);
}
else if(um.match(uri) == 2){
db.insert("teacher", null, values);
getContext().getContentResolver().notifyChange(uri, null);
}
else{
throw new IllegalArgumentException("uri有问题哟亲么么哒");
}
return uri;
}
借助 UriMatcher这个类就可以轻松地实现匹配内容 URI的功能。 UriMatcher中提供了一个 addURI()方法,这个方法接收三个参数,可以分别把权限、路径和一个自定义
代码传进去。这样,当调用 UriMatcher 的 match()方法时,就可以将一个 Uri 对象传入,返回值是某个能够匹配这个 Uri 对象所对应的自定义代码,利用这个代码,我们就可以判断出
调用方期望访问的是哪张表中的数据了。官方推荐用static,所以我们就用的static对象。
可以发现这里我们还对getType方法增加的逻辑,那么这个方法是用来干嘛的呢?
它是所有的内容提供器都必须提供的一个方法,用于获取 Uri 对象所对应的 MIME 类型。一个内容 URI 所对应的 MIME字符串主要由三部分组分,Android 对这三个部分做了如下格式规定。
1. 必须以 vnd 开头。
2. 如果内容 URI 以路径结尾,则后接 android.cursor.dir/,如果内容 URI 以 id 结尾,则后接 android.cursor.item/。
3. 最后接上 vnd.<authority>.<path>。
所以,对于 content://com.example.app.provider/table1 这个内容 URI,它所对应的 MIME类型就可以写成:
vnd.android.cursor.dir/vnd.com.example.app.provider.table1 也可以简写vnd.android.cursor.dir/table1
对于 content://com.example.app.provider/table1/1 这个内容 URI,它所对应的 MIME 类型就可以写成:
vnd.android.cursor.item/vnd. com.example.app.provider.table1 也可以简写vnd.android.cursor.item /table1
四、获取系统短信案例
其实在实际开发中,一般我们并不会将自己应用的数据暴露给其他应用,内容提供者大多数的时候都是获取系统应用的数据,所以我们接下来做一个获取系统短信的案例
第一步:查看安卓系统源码,找到短信的应用的AndroidManifest,找到内容提供者的主机名,以及权限。实现当我们点击Button,Log打印出短信的“地址,时间,内容,类型(发送的还是接收的)”
public class MainActivity extends Activity {
List<Message> smsList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
smsList = new ArrayList<Message>();
}
public void click(View v){
//访问内容提供者获取短信
ContentResolver cr = getContentResolver();
// 短信内容提供者的主机名
Cursor cursor = cr.query(Uri.parse("content://sms"), new String[]{"address", "date", "body", "type"},
null, null, null);
while(cursor.moveToNext()){
String address = cursor.getString(0);
long date = cursor.getLong(1);
String body = cursor.getString(2);
String type = cursor.getString(3);
Message sms = new Message(body, type, address, date);
smsList.add(sms);
}
}
第二步:添加权限(因为我们读取系统短信,属于侵犯用户隐私,所以肯定是要添加读取短信的权限的)
<
uses-permission
android:name
=
"android.permission.READ_SMS"
/>
五、获取系统联系人案例
第一步:实现需求逻辑:点击按钮获取联系人姓名和手机号
public void click1(View v){
ContentResolver cResolver = getContentResolver();
Cursor cursor = cResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, null, null, 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));
System.out.println("name="+displayName+"number="+number);
}
}
咦,大家可能又要迷糊了,这里为什么Uri那么奇怪。 为 什 么 没 有 调 用 Uri.parse() 方 法 去 解 析 一 个 内 容 URI 字 符 串 呢 ? 这 是 因 为ContactsContract.CommonDataKinds.Phone类已经帮我们做好了封装,提供了一个CONTENT_URI常量,而这个常量就是使用 Uri.parse()方法解析出来的结果。接着我们对 Cursor 对象进行遍历,将联系人姓名和手机号这些数据逐个取出,
联系人姓名这一列对应的常量是
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
联系人手机号这一列对应的常量是
ContactsContract.CommonDataKinds.Phone.NUMBER。
第二步:添加权限
<
uses-permission
android:name
=
"android.permission.READ_CONTACTS"
/>