-
- 内容提供者简介
- 访问其他应用中的数据
- ContentResolver 的基本用法
- 实例读取系统联系人
- 创建自己的内容提供器
- 创建内容提供器的步骤
- 创建新类继承ContentProvider并且重写其中6个方法
- 内容URI参数
- 使用UriMatcher实现匹配内容URI的功能
- getType函数介绍
- 实现跨程序数据共享
- 内容提供者
- 访问自己创建的内容提供者数据
- 遇到的问题
- 创建内容提供器的步骤
内容提供者简介
- 使用场景:
比如:电话薄,短信,媒体库
- 简介
主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供器是 Android实现跨程序共享数据的标准方式。不同于文件存储和 SharedPreferences 存储中的两种全局可读写操作模式,内容提供器可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险。
- 用法
一种是使用现有的内容提供器来读取和操作相应程序中的数据。
另一种是创建自己的内容提供器给我们程序的数据提供外部访问接口。
访问其他应用中的数据
ContentResolver 的基本用法
1、获取
通过 Context 中的 getContentResolver()方法获取到该类的实例。
2、函数
ContentResolver 中提供了一系列的方法用于对数据进行 CRUD 操作,其中 insert()方法用于
添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数
据。
3、内容URL
内容 URI给内容提供器中的数据建立了唯一标识符,它主要由两部分组成,权限(authority)和路径(path)。权限是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。
比如某个
程序的包名是 com.example.app,那么该程序对应的权限就可以命名为 com.example.app.
provider。路径则是用于对同一应用程序中不同的表做区分的,通常都会添加到权限的后面。
比如某个程序的数据库里存在两张表,table1和 table2,这时就可以将路径分别命名为/table1
和/table2,然后把权限和路径进行组合,内容 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
4、解析内容URL,解析成Uri对象。
Uri uri = Uri.parse("content://com.example.app.provider/table1")
5、查询query()
5.1参数
Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder);
5.2 返回值
一个 Cursor 对象,通过移动游标的位置来遍历 Cursor 的所有行,然后再取出
每一行中相应列的数据。
!!!注意,用完cursor要关闭。
if (cursor != null) {
while (cursor.moveToNext()) {
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
6、增加 insert()
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri, values);
7、修改 update()
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new
String[] {"text", "1"});
8、删除delete()
getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });
实例——读取系统联系人
ContactsContract.CommonDataKinds.Phone类已经帮我们做好了封装,提供了一个CONTENT_URI
常量,而这个常量就是使用 Uri.parse()方法解析出来的结果。
public class MainActivity extends Activity {
private ListView contactListView;
private ArrayAdapter<String> adapter;
private List<String> contactList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initList();
readContact();
}
private void initList(){
contactList = new ArrayList<String>();
contactListView = (ListView)findViewById(R.id.contacts_view);
adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, contactList);
contactListView.setAdapter(adapter);
}
private void readContact(){
Cursor cursor = null;
try{
cursor = getContentResolver().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));
contactList.add(displayName + "\n" + number);
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(cursor != null){
cursor.close();
}
}
}
}
布局文件
<ListView
android:id="@+id/contacts_view"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
最后,千万别忘记加权限
<uses-permission android:name="android.permission.READ_CONTACTS" />
创建自己的内容提供器
创建内容提供器的步骤
创建新类继承ContentProvider,并且重写其中6个方法
public class MyProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return 0;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public String getType(Uri uri) {
return null;
}
}
- onCreate()
初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作,返回 true 表示内容提供器初始化成功,返回 false 则表示失败。注意,只有当存在ContentResolver 尝试访问我们程序中的数据时,内容提供器才会被初始化。
- query()
从内容提供器中查询数据。使用 uri 参数来确定查询哪张表,projection参数用于确定查询哪些列,selection和 selectionArgs参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序,查询的结果存放在 Cursor 对象中返回。
- insert()
向内容提供器中添加一条数据。使用 uri 参数来确定要添加到的表,待添加的数据保存在 values 参数中。添加完成后,返回一个用于表示这条新记录的 URI。
- update()
更新内容提供器中已有的数据。使用 uri 参数来确定更新哪一张表中的数据,新数据保存在 values 参数中,selection 和 selectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。
- delete()
从内容提供器中删除数据。使用 uri 参数来确定删除哪一张表中的数据,selection和 selectionArgs参数用于约束删除哪些行,被删除的行数将作为返回值返回。
- getType()
根据传入的内容 URI来返回相应的 MIME类型。
内容URI参数
每个方法都带一个uri参数,一个标准的内容 URI写法是这样的:
content://com.example.app.provider/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格式就可以写成:
content://com.example.app.provider/table1/#
使用UriMatcher实现匹配内容URI的功能
使用方式:UriMatcher
中提供了一个 addURI()方法,这个方法接收三个参数,可以分别把权限、路径和一个自定义代码传进去。这样,当调用 UriMatcher 的 match()方法时,就可以将一个 Uri 对象传入,返回值是某个能够匹配这个 Uri 对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。下面是书上示例:
public class MyProvider extends ContentProvider {
public static final int TABLE1_DIR = 0;
public static final int TABLE1_ITEM = 1;
public static final int TABLE2_DIR = 2;
public static final int TABLE2_ITEM = 3;
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
uriMatcher.addURI("com.example.app.provider ", "table1/#", TABLE1_ITEM);
uriMatcher.addURI("com.example.app.provider ", "table2", TABLE2_ITEM);
uriMatcher.addURI("com.example.app.provider ", "table2/#", TABLE2_ITEM);
}
……
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
// 查询table1 表中的所有数据
break;
case TABLE1_ITEM:
// 查询table1 表中的单条数据
break;
case TABLE2_DIR:
// 查询table2 表中的所有数据
break;
case TABLE2_ITEM:
// 查询table2 表中的单条数据
break;
default:
break;
}
……
}
……
}
getType()函数介绍
它是所有的内容提供器都必须提供的一个方法,用于获取 Uri 对象所对应的 MIME类型。一个内容 URI所对应的 MIME字符串主要由三部分组分,Android对这三个部分做了如下格式规定。
1. 必须以 vnd 开头。
2. 如果内容 URI 以路径结尾,则后接 android.cursor.dir/,如果内容 URI 以 id 结尾,则后接 android.cursor.item/。
3. 最后接上 vnd..。
对于 content://com.example.app.provider/table1 这个内容 URI,对应的 MIME类型:
vnd.android.cursor.dir/vnd.com.example.app.provider.table1
对于 content://com.example.app.provider/table1/1 这个内容 URI,对应的 MIME类型
vnd.android.cursor.item/vnd. com.example.app.provider.table1
实现跨程序数据共享
根据例子自己改编了一下,做了简单一点的。
内容提供者
先是内容提供者自身程序。结合上面的获取电话号码和数据库。
//内容提供者
public class MyContentProvider extends ContentProvider{
private SQLiteHelper dbHelper;
private SQLiteDatabase db;
public static final int PHONE_DIR = 0;
public static final int PHONE_ITEM = 1;
public static final String AUTHORITY = "com.zzz.readcontacttest";
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "phone", PHONE_DIR);
uriMatcher.addURI(AUTHORITY, "phone/#", PHONE_ITEM);
}
@Override
public boolean onCreate() {
dbHelper = new SQLiteHelper(getContext());
db = dbHelper.getReadableDatabase();
return true;
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Cursor cursor = null;
Log.e("zhx", "i have been called");
switch (uriMatcher.match(uri)){
case PHONE_DIR:
cursor = db.query("TLB_CONTACT", projection, selection, selectionArgs, null, null, sortOrder);
break;
case PHONE_ITEM:
String phoneId = uri.getPathSegments().get(1);
cursor = db.query("TLB_CONTACT", projection, "id = ?", new String[]{phoneId}, null, null, sortOrder);
break;
default:
break;
}
return cursor;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
Uri uriReturn = null;
switch (uriMatcher.match(uri)) {
case PHONE_DIR:
case PHONE_ITEM:
long newBookId = db.insert("TLB_CONTACT", null, values);
uriReturn = Uri.parse("content://" + AUTHORITY + "/phone/" +
newBookId);
break;
default:
break;
}
return uriReturn;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int deleteRow = 0;
switch (uriMatcher.match(uri)) {
case PHONE_DIR:
deleteRow = db.delete("TLB_CONTACT", selection, selectionArgs);
break;
case PHONE_ITEM:
String bookId = uri.getPathSegments().get(1);
deleteRow = db.delete("TLB_CONTACT", "id = ?", new String[]
{bookId});
break;
default:
break;
}
return deleteRow;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int updatedRows = 0;
switch (uriMatcher.match(uri)) {
case PHONE_DIR:
updatedRows = db.update("TLB_CONTACT", values, selection, selectionArgs);
break;
case PHONE_ITEM:
String bookId = uri.getPathSegments().get(1);
updatedRows = db.update("TLB_CONTACT", values, "id = ?", new String[]
{ bookId });
break;
default:
break;
}
return updatedRows;
}
@Nullable
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case PHONE_DIR:
return "vnd.android.cursor.dir/vnd.com.zzz.readcontacttest.phone ";
case PHONE_ITEM:
return "vnd.android.cursor.item/vnd.com.zzz.readcontacttest.book ";
}
return null;
}
}
query()进行查询,并将 Cursor 对象返回。注意当访问单条数据的时候有一个细节,这里调用了 Uri 对象的getPathSegments()方法,它会将内容 URI权限之后的部分以“/”符号进行分割,并把分割后的结果放入到一个字符串列表中,那这个列表的第 0 个位置存放的就是路径,第 1个位置存放的就是 id 了。得到了 id 之后,再通过 selection 和 selectionArgs 参数进行约束,就实现了查询单条数据的功能。
insert()方法,同样它也是先获取到了 SQLiteDatabase 的实例,然后根据传入的 Uri参数判断出用户想要往哪张表里添加数据,再调用 SQLiteDatabase 的 insert()方法进行添加就可以了。注意 insert()方法要求返回一个能够表示这条新增数据的 URI,所以我们还需要调用 Uri.parse()方法来将一个内容 URI解析成 Uri对象,当然这个内容 URI是以新增数据的 id 结尾的。
update()方法了,先获取SQLiteDatabase 的实例,然后根据传入的 Uri 参数判断出用户想要更新哪张表里的数据,再调用 SQLiteDatabase的 update()方法进行更新就好了,受影响的行数将作为返回值返回。
delete()方法,先获取到 SQLiteDatabase 的实例,然后根据传入的 Uri 参数判断出用
户想要删除哪张表里的数据,再调用 SQLiteDatabase 的 delete()方法进行删除就好了,被删
除的行数将作为返回值返回
//获取电话薄信息并且存入数据库
public class MainActivity extends Activity {
private ListView contactListView;
private ArrayAdapter<String> adapter;
private List<String> contactList;
private SQLiteHelper dbHelper;
private SQLiteDatabase db;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new SQLiteHelper(this);
db = dbHelper.getReadableDatabase();
initList();
readContact();
}
private void initList(){
contactList = new ArrayList<String>();
contactListView = (ListView)findViewById(R.id.contacts_view);
adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, contactList);
contactListView.setAdapter(adapter);
}
private void readContact(){
Cursor cursor = null;
try{
cursor = getContentResolver().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));
contactList.add(displayName + "\n" + number);
ContentValues values = new ContentValues();
values.put("NAME", displayName);
values.put("CONTEXT", number);
db.insert("TLB_CONTACT", null, values);
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(cursor != null){
cursor.close();
}
}
}
}
//SQLInfo数据库信息
public class SQLInfo {
private static String TableNames[] = {
"TLB_CONTACT"
};
private static String FieldNames[][] = {
{"ID","NAME","CONTEXT"}
};
private static String FieldTypes[][] = {
{"INTEGER PRIMARY KEY AUTOINCREMENT","TEXT","TEXT"},
};
public static String[] getTableNames() {
return TableNames;
}
public static String[][] getFieldNames() {
return FieldNames;
}
public static String[][] getFieldTypes() {
return FieldTypes;
}
}
//数据库帮助类,简单的,之后可以考虑单例模式以及增加增删改查等对数据库的便捷操作
public class SQLiteHelper extends SQLiteOpenHelper {
private String TableNames[];
private String FieldNames[][];
private String FieldTypes[][];
private static int version = 1;//数据库版本号
private static String myDBName = "mydb";
@Override
public void onCreate(SQLiteDatabase db) {
TableNames = SQLInfo.getTableNames();
FieldNames = SQLInfo.getFieldNames();
FieldTypes = SQLInfo.getFieldTypes();
for(int i = 0; i < TableNames.length; i++){
String sql = "CREATE TABLE " + TableNames[i] + " (";
for (int j = 0; j < FieldNames[i].length; j++)
{
sql += FieldNames[i][j] + " " + FieldTypes[i][j] + ",";
}
sql = sql.substring(0, sql.length() - 1);
sql += ")";
db.execSQL(sql);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public SQLiteHelper(Context context) {
super(context, myDBName, null, version);
}
}
//最后,千万别忘记了,内容提供者也是四大组件之一,也是要在清单文件中注册的。
<provider
android:exported="true"
android:authorities="com.zzz.readcontacttest"
android:name="com.zzz.readcontacttest.MyContentProvider"/>
访问自己创建的内容提供者数据
public class MainActivity extends Activity {
Button addBtn;
Button queryBtn;
Button updateBtn;
Button deteleBtn;
private TextView text;
Uri uri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
text = (TextView) findViewById(R.id.data);
addBtn = (Button) findViewById(R.id.add_data);
queryBtn = (Button) findViewById(R.id.query_data);
updateBtn = (Button) findViewById(R.id.update_data);
deteleBtn = (Button) findViewById(R.id.delete_data);
addBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
uri = Uri.parse("content://com.zzz.readcontacttest/phone");
ContentValues values = new ContentValues();
values.put("NAME", "bld");
values.put("CONTEXT", "111112244");
getContentResolver().insert(uri, values);
}
});
queryBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
uri = Uri.parse("content://com.zzz.readcontacttest/phone");
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
String ss = "";
if (cursor != null) {
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("NAME"));
String data = cursor.getString(cursor.getColumnIndex("CONTEXT"));
ss = ss + name + data;
}
text.setText(ss);
cursor.close();
}
}
});
updateBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
uri = Uri.parse("content://com.zzz.readcontacttest/phone/1");
ContentValues values = new ContentValues();
values.put("NAME", "hf");
text.setText(getContentResolver().update(uri, values, "id = ?", new String[]{"1"}) + "");
}
});
deteleBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
uri = Uri.parse("content://com.zzz.readcontacttest/phone/1");
text.setText(getContentResolver().delete(uri, "id = ?", new String[]{"1"}) + "");
}
});
}
}
遇到的问题
1、访问不了自己创建的contentprovider
出现错误信息:opening provider from that is not exported from
解决方式:android:exported=”true”,之前上面清单文件只是注册了一下,后面出错了才加上的。
解决之后查了一个文章:http://blog.csdn.net/guoxiao20sun/article/details/8646024。写的比较详细一点。
在AndroidManifest.xml 中 Android:exported 属性,这个属性用于指示该服务是否能被其他程序应用组件调用或跟他交互; 取值为(true | false),如果设置成true,则能够被调用或交互,否则不能;设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。
重:它的默认值是依赖于该服务所包含的过滤器
(1)如果没有过滤器则意味着该服务只能通过指定明确的类名来调用,也就是说该服务只能在应用程序内部使用(因为其他外部使用者不会知道该服务的类名),此时它的默认值是false
(2)如果至少包含了一个过滤器,则意味着该服务可以给外部的其他应用提供服务,因此默认值是true。
附: 这个属性不是限制把服务暴露给其他应用程序的唯一方法。还可以使用权限来限制能够跟该服务交互的外部实体