Android:ContentProvider 数据存取
ContentProvider 机制
Android 系统对应用程序的数据文件设置了读写权限,为了实现跨应用访问数据的功能,Android 提供了 ContentProvider 机制。ContentProvider 是 Android 的四大组件之一,用于对外共享数据,实现跨应用数据共享。ContentProvider 应用程序组件结合文件权限机制,有保护地开放自己的数据给其他应用程序使用。
ContentProvider 的功能
- 访问系统资源:开发者利用 ContentProvider 可以访问系统提供的数据,如通讯录、音频、视频、图片等,相当于是中间人,真正的数据源是文件或者 SQLite 等。
- 共享自定义资源:开发者可以在应用中编写自定义的 ContentProvider,把自己的数据提供给其他应用访问,也可以在获取写入权限后将数据添加到一个已存在的 ContentProvider。
数据模型
ContentProvider 将其存储的数据以数据表的形式提供给访问者,数据表中每一行为一条记录,每一列为具有特定类型和意义的数据。每一条数据记录都包括一个“ID”数值字段,唯一标识一条数据。ContentProvider 返回的数据结构是 Cursor 对象,即结果集合,类似于 JDBC 中的 ResultSet。
URL
URL 的组成
Universal Resource Identifier(通用资源标志符,URI)代表要操作的数据,Android的每种资源(如图像、视频等)都可以用 URI 来表示。每一个 Content Provider 都对外提供一个能够唯一标识自己数据集(data set)的公开 URI,一个数据源含有多个内容(如多个表),就需要用不同的 URI 进行区分。
URI 由三部分组成:访问资源的命名机制(schema)、存放资源的主机名和资源自身的名称。例如:
content://com.example.provider.NoteProvider/notes/3
组成部分 | 字符串 | 说明 |
---|---|---|
访问资源的命名机制 | content:// | 表示由 ContentProvider 控制数据 |
存放资源的主机名 | com.example.provider.NoteProvider | 用来定位 ContentProvider 的唯一标识符 |
资源自身的名称 | /notes/3 | 为每个 ContentProvider 内部的路径部分,指向一个对象集合,通常是表的名字 |
如“:/notes”表示一个笔记集合,返回表中的全部记录,如果后续还有路径,则指向特定的记录,如“:/notes/3表”示 id 为 3 的笔记。
创建 URL
URL 的创建使用 Uri.parse()方法,直接给定字符串即可。例如:
Uri uri = Uri.parse("content://com.example.mycontact /people");
操作 URL
Android 系统提供了两个用于操作 URI 的工具类,分别为 UriMatcher 和 ContentUris。
UriMatcher 类
UriMatcher 类的作用是提取数据表,进行下一步的数据操作。比起手动过滤字符串来,使用 UriMatcher 不仅简单,而且可维护性较好。
初始化 UriMatcher 类的构造方法如下,其中参数匹配码是一个大于零的整数(表示匹配根路径),或常量 UriMatcher.NO_MATCH(整数 -1,表示不匹配根路径)。
UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
注册需要的 URI,增加其他 URI 匹配路径的方法如下,第一个参数为 AUTHORITY 字符串,第二个参数为需要匹配的路径,第三个参数必须为一个大于零的匹配码。
URI_MATCHER.addURI(AUTHORITY, TABLE_A, TABLE_A_MSG);
ContentUris 类
ContentUris 类的作用是获取 URI 路径后面的 ID 部分,通过 withAppendedId() 方法,为该 Uri 加上 ID:
Uri resultUri = ContentUris.withAppendedId(uri, 10);
也可以通过 parseId(uri) 从路径中获取 ID,例如:
Uri uri = Uri.parse("content://com.example.mycontact/people/10")
long personid = ContentUris.parseId(uri);
ContentProvider
ContentProvider 类实现了一组标准的方法接口,让其他的应用通过该接口来操作该应用程序的内部数据。ContentProvider 的主要方法如下:
public boolean onCreate(); //在创建 ContentProvider 时调用
public Cursor query(Uri,String[], String, String[], String); //查询指定 Uri 的 ContentProvider,返回一个 Cursor
public Uri insert(Uri,Content Values); //添加数据到指定 Uri 的 ContentProvider 中
public int update(Uri,ContentValues,String,String[]); //更新指定 Uri 的 ContentProvider 中的数据
public int delete(Uri,String,String[]); //从指定Uri 的ContentProvider 中删除数据
public String getType(Uri); //返回指定的 Uri 中的数据的 MIME 类型
大多数 ContentProvider 定义了契约类来包含它们所用到的 MIME 类型,例如联系人 Provider 的契约类 ContactsContract.RawContacts 定义了常量 CONTENT_ITEM_TYPE,它对应于一行原始的联系人数据。
URI | 说明 |
---|---|
ContactsContract.Contacts.CONTENT_URI | 管理联系人 |
ContactsContract.CommonDataKinds.Phone.CONTENT_URI | 管理联系人的电话 |
ContactsContract.CommonDataKinds.Email.CONTENT_URI | 管理联系人的电子邮件 |
ContactsContract.Contacts.DISPLAY NAME | 联系人中显示的姓名 |
ContactsContract.Contacts.HAS_PHONE_NUMBER | 联系人中是否存在电话 |
ContactsContract.Data.CONTACT_ID | 联系人中显示的 ID |
ContactsContract.CommonDataKinds.Phone.NUMBER | 联系人中显示的电话号码 |
ContactsContract.CommonDataKinds.Phone.TYPE | 联系人中显示的电话号码类型(办公、家庭等) |
ContactsContract.CommonDataKinds.Email.DATA | 联系人中显示的电子邮件 |
ContactsContract.CommonDataKinds.Email.TYPE | 联系人中显示的电子邮件类型(个人、机构等) |
ContentResolver
ContentProvider 的工作原理是单例模式,系统只有一个 ContentProvider 实例,ContentProvider 的用户不能直接访问。想要访问这个实例,必须通过 ContentResolver 来实现对 ContentProvider的操作,令它可以和处于多个程序、多个进程中的 ContentResolver 对象进行通信。
为了实现对数据的操作,ContentResolver 提供的接口与 ContentProvider 提供的接口是一一对应的,主要包括以下方法。
的方法是 getContentResolver();:
ContentResolver cr = getContentResolver(); //获取一个 ContentResolver 的实例
query(Uri uri,String[] projection, String selection,String[] selectionArgs, String sortOrder)
//通过 Uri 进行查询,返回一个 Cursor
insert(Uri uri,Content Values values); //将一组数据插入到 Uri 指定位置。
update(Uri uri,ContentValues values,String where,Stringll selectionArgs); //更新 Uri 指定位置的数据。
delete(Uri uri,String where,String[] selectionArgs); //删除指定 Uri 并且符合一定条件的数据。
ContentProvider 样例
程序需求
输入要拨打的电话号码,根据电话号码查询姓名,如果此号码在通讯录中,则显示电话主人的姓名,否则显示陌生人字样。
代码编写
activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:text="拨出电话:" />
<EditText
android:id="@+id/editText1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="11" >
</EditText>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拨号" />
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textSize="20dp"
android:text="" />
</LinearLayout>
</LinearLayout>
MainActivity
当用户输入电话的时候,首先需要用正则表达式判断电话号码是否合法。如果合法则通过 ContentProvider 获取联系人信息,通过返回的 Cursor 对象查找是否有这个联系人的名字。如果有就显示这个联系人,没有就显示陌生人,最后用 intent 机制拨打出电话。
package com.example.liaison;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract.Data;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
public class MainActivity extends AppCompatActivity {
private EditText editText1;
private TextView textView2;
private Button button1;
String phoneNumber = "";
String displayName = "";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText1 = (EditText) findViewById(R.id.editText1);
textView2 = (TextView) findViewById(R.id.textView2);
button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
phoneNumber = editText1.getText().toString();
// 输入的电话号码合法
if (isPhoneLegal(phoneNumber)) {
// 根据电话号码查找联系人
Uri uri = Uri.parse("content://com.android.contacts/data/phones/filter/" + phoneNumber);
ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(uri, new String[] {Data.DISPLAY_NAME}, null, null, null);
// 判断号码是否在通讯录
if (cursor.moveToFirst()) {
textView2.setText("你将打给:" + cursor.getString(0)); // 显示name
} else {
textView2.setText("你将打给:陌生人");
}
// 使用intent拨打电话
Intent intent = new Intent();
intent.setAction(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:" + phoneNumber));
startActivity(intent);
}
// 输入的电话号码合法
else{
textView2.setText("请输入正确的电话号码!");
}
}
});
}
// 判断
public static boolean isPhoneLegal(String str) throws PatternSyntaxException {
String regExp = "^((13[0-9])|(14[5,7,9])|(15[0-3,5-9])|(166)|(17[3,5,6,7,8])|(18[0-9])|(19[8,9]))\\d{8}$";
Pattern p = Pattern.compile(regExp);
Matcher m = p.matcher(str);
return m.matches();
}
}
申请权限
需要申请读取联系人的权限和打电话的权限:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
运行效果
首先在联系人当中添加测试数据:
电话号码在联系人中:
电话不在联系人列表中:
输入非法的字符串:
参考资料
《Android 移动应用开发》,杨谊 主编、喻德旷 副主编,人民邮电出版社