【Android 开发】:Content Provider (内容提供者) 详解

时间:2022-07-16 09:19:46

1. Content Providers 介绍

    Content Providers 管理访问结构化的数据集。它们可以封装这些数据,并且为定义安全的数据提供机制。Content providers 是标准的接口,它能将一个线程中的数据与其他线程中的运行的代码进行连接。也就是说 Content providers 支持跨应用间的访问。
    当你想要在 Content provider[内容提供者] 中访问数据,你要在你的应用程序的 Context 中使用 ContentResolver[内容解析者] 对象与 provider 进行连接作为客户端[通过 Cotext中getContentResolver()方法] 。这个ContentResolver 会与provider对象进行连接,provider 它是一个继承Content provider的类的实例。provide这个对象会接受从客户端请求的数据,执行请求动作,返回结果。     如果你不打算将自己的数据分享给其他应用程序,就不必要开发自己的 内容提供者[提供者], 然而你想要在你的应用程序中提供自定义的搜索建议你需要开发内容提供者,如果你想要复制数据或者文件到其它应用程序,你就需要拥有自己的 provider 。     Android自己提供了一个 content providers 来管理数据,如果 音频,视频,或者个人信息等。你可以在 android.provider 包中查看,这些提供者可以给任何应用程序所访问。     Calendar Providerhttp://developer.android.com/guide/topics/providers/calendar-provider.html 
    Contacts Provider http://developer.android.com/guide/topics/providers/contacts-provider.html

2. 如何创建一个内容提供者

1) 什么时候决定创建一个内容提供者呢?

1). 你需要提供完整的数据和文件给其他的应用程序. 2). 你想要允许用户从你的应用程序复制完整的数据到其他的应用程序 3). 你想要使用搜索框架来提供自定义的搜索建议。

2) 创建内容提供者需要掌握的知识

1). 自定义一个内容提供者供别的应用程序去访问,Content provider 是基于数据库的,它在外层封装了有一套非常规范的增删查改的操作数据库的对外接口,如下图所示
【Android 开发】:Content Provider (内容提供者) 详解
2). 查看API文档中 ContentProvider 类的说明,可以发现这是一个抽象类  
   Content providers是Android应用程序中主要的组成部分之一,为应用程序提供对外的内容,它封装了数据然后提供给那些通过简单 ContentResolver接口的应用程序,这些数据是可以跨应用访问的。
   对与解析者来说,它会通过 ContentResolver 会发出一个请求,这个请求是否通过是通过去检查由系统给予的 URI 的授权,授权是由 content provider 注册的,在AndroidMainifest.xml 中去注册这种授权。UriMatcher 类可以帮助解析 URI.[也就是通过这个类去匹配URI,详细参考第5点 ]

   在这里为什么要注册 URI授权呢?
   原因是一个应用程序中可以有多个 content provider, 但是每一个content provider 的授权都是唯一的,所以内容解析者可以通过这种URL的授权来找到自己想要的 content provider. 这里的 URI 的授权也可以理解为content provider对外的一个访问的路径
3). 注意定义好一个内容提供者之后需要在 AndroidMainifest.xml 中注册
   android:authorities 授权属性,表示外部应用程序访问当前内容提供者的标示符,它是自定义的,一般我们是以 "包名 + 类名" 的形式来定义的。有些人可以理解为 URI 路径
4). 查看 UriMatcher 类的概要描述  
   这是一个在 content provider 中帮助匹配  URIs 的实用类。
   查看 public void addURI (String authority, String path, int code)方法
   这个方法是用来表示在  content provider 里面添加外部对其的匹配的规则,当 URI 被匹配的时候,就会返回 code 码, URI节点可以精确的匹配字符串, "*" 号匹配任何的字符串 "#" 号只能匹配数字。[比如删除一条记录中 ID 往往是数字]
   参数说明
   authority : 授权, 就是 AndroidMainifest.xml 中的授权    path : 匹配路径(通常是一个表名)[* 可以作为匹配任意字符的通配符, # 可以作为匹配数字的通配符]。【注意这里如果是单条记录,需要添加 /# 标示符】
5). 查看 content provider 中的 public abstract String getType (Uri uri) 方法
    根据给定的 URI 来实现处理 MIME类型的请求, 对于单条记录返回的 MIME 类型是以  vnd.android.cursor.item 开始的, 对于多条记录返回的MIME类型是以vnd.android.cursor.dir/ 开始的. 这个方法可以在多线程环境下被调用。 详细参考Processes
and Threads.
6). 查看 content provider 中 public abstract Uri insert (Uri uri, ContentValues values) 方法
    实现这个方法来处理插入一个新行的请求, 在插入后可以友好的调用 notifyChange() 方法。
    参数说明: 
uri : 这种格式 "content:// URI" 的插入请求[后续会认真的讲解这一部分内容]。 此处不为空 values : 添加到数据库的 ContentValues 类型集合。博客前面章节讲过次内容,详情可以去参考。此处不为空
返回
   新插入选项的URI,可以给其他用户去使用。

7). 删除操作    查看 content provider 的 public abstract int delete (Uri uri, String selection, String[] selectionArgs) 方法    实现这个方法主要是用来处理删除一行或者多行的请求操作,实现这个删除操作需要 有 selection 语句,你可以在删除之后调用 notifyDelete() 方法来做友好的提示    实现这个方法还需要在 URI的末尾解析出行的ID,如果一个指定的行被删除之后,例如,客户端在创建SQL语句的时候会通过 content://contacts/people/22 的这个URI,来解析出末尾的ID号,确定删除ID为 22 的这个记录。

参数说明:
URI : 完整的URI路径,包括行ID selection : 可选项,在删除行的时候适用
返回 :
影响数据库的行数

3. 代码实现

1) actvity_main.xml 程序界面的布局文件, 这里只是定义了5个按钮,不贴出来了
2) DBOpenHelper.java 主要是用来做数据库的创建使用

package com.android.contentproviderdemo;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class DBOpenHelper extends SQLiteOpenHelper {

private static final String TAG = "DBOpenHelper";
private static String name = "mydb.db";
private static int version = 1; // 初始版本号是一

public DBOpenHelper(Context context) {
super(context, name, null, version);
// TODO Auto-generated constructor stub
}

@Override
public void onCreate(SQLiteDatabase database) {
// TODO Auto-generated method stub
String sql = "create table student (id integer primary key autoincrement, name varchar(64), address varchar(64))";
database.execSQL(sql); //对数据库的表的创建
Log.i(TAG, "ahuier--> SQLite create succeed!");
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub

}

}
3) StudentProvider.java 也就是本例子中最重要的代码,实现 Content Provider中增删查改的操作
package com.android.contentproviderdemo;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;

public class StudentProvider extends ContentProvider {

private final String TAG = "StudentProvider";
private DBOpenHelper helper = null;

private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
/*
* 这里为何要做这种 操作单条记录 或者 操作多条记录的标志位呢?
* 原因是因为 外部程序 操作 ContentProvider 的方法,仅仅只能通过一个 URI 来访问,
* 所以要定义两个标志位来识别外部需要操作的是单条记录还是多条记录 (比如删除单条记录或者删除多条记录)
*/
private static final int STUDENT = 1; // 操作单条记录
private static final int STUDENTS = 2; // 操作多条记录
// 添加对外部的匹配规则
static {
URI_MATCHER.addURI("com.android.contentproviderdemo.StudentProvider",
"student", STUDENTS);
URI_MATCHER.addURI("com.android.contentproviderdemo.StudentProvider",
"student/#", STUDENT);
}

public StudentProvider() {
// TODO Auto-generated constructor stub
}

@Override
public boolean onCreate() {
// 初始化的时候实例化 helper 对象
helper = new DBOpenHelper(getContext());
return true;
}

// query() 方法返回的是一个 Cursor 的游标,详细方法参考 Android API文档
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor cursor = null;
try {
SQLiteDatabase databse = helper.getReadableDatabase();
int flag = URI_MATCHER.match(uri);
switch (flag) {
case STUDENT:
long id = ContentUris.parseId(uri);
String where_value = " id = " + id;
if (selection != null && !selection.equals("")) {
where_value += " and " + selection;
}
// 这边具体查询方式如果有不懂,可以参考前面即将 SQLite的查询。
cursor = databse.query("student", null, where_value, selectionArgs, null, null,
null, null);
break;

case STUDENTS:
cursor = databse.query("student", null, selection, selectionArgs, null, null,
null, null);
break;
}
} catch (Exception e) {
// TODO: handle exception
}
return cursor;
}

/*
* 它的作用是根据URI返回该URI所对应的数据的MIME类型字符串。
* 这个MIME类型字符串的作用是要匹配AndroidManifest.xml文件
* <activity>标签下<intent-filter>标签的子标签<data>的属性 android:mimeType。
* 如果不一致,则会导致对应的Activity无法启动。
*/
@Override
public String getType(Uri uri) {
int flag = URI_MATCHER.match(uri);
switch (flag) {
case STUDENT:
return "vnd.android.cursor.item";
case STUDENTS:
return "vnd.android.cursor.dir/";
}
return null;
}

@Override
public Uri insert(Uri uri, ContentValues values) {
Uri resultUri = null;
/*
* URI_MATCHER.match(uri) 上面已经在定义了匹配规则,所以这里是用外部传过来的URI匹配内部定义好的规则,
* 如果匹配成功则进行操作,反之不进行操作。
*/
int flag = URI_MATCHER.match(uri);
switch (flag) {
case STUDENTS:
SQLiteDatabase database = helper.getWritableDatabase();
// 注意这边我们使用的是数据库的 database.insert()方法,所以要用 withAppendedId()
// 这种方式来返回URI
long id = database.insert("student", null, values); // 插入当前行的行号
resultUri = ContentUris.withAppendedId(uri, id);
break;
}
Log.i(TAG, "ahuier----->" + resultUri.toString());
// 返回新插入选项的URI,可以给其他用户去使用
return resultUri;
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = -1; // count作为影响数据库的行数
try {
int flag = URI_MATCHER.match(uri);
SQLiteDatabase database = helper.getWritableDatabase();
switch (flag) {
case STUDENT:
/*
* 传递过来的URI的格式:content://com.android.contentproviderdemo.StudentProvider/student/1
* 以下代码其实就是用来构建 SQL 中删除单挑记录的语句
* delete from student where id = ? // id 通过客户端传递过来的。
*/
long id = ContentUris.parseId(uri); // 解析出 URI 末尾的ID号
String where_value = " id = " + id;
if (selection != null && !selection.equals("")) {
where_value += " and " + selection;
}
// 注意这边的count的是SQLite中的delete()方法,返回的是一个影响数据库的数目
count = database.delete("student", where_value, selectionArgs);
break;

case STUDENTS:
// 传递过来的 URI格式:content://com.android.contentproviderdemo.StudentProvider/student
// 删除多条记录
count = database.delete("studuent", selection, selectionArgs);
break;
}
} catch (Exception e) {
// TODO: handle exception
}
return count;
}

// 更新的方法与删除方法类似,读者可以自己去查 Android官方文档
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = -1;
try {
// 更新数据库的语句 : update table set name = ?, address = ? where id = ?
SQLiteDatabase database = helper.getWritableDatabase();
long id = ContentUris.parseId(uri);
int flag = URI_MATCHER.match(uri);
switch (flag) {
case STUDENT:
String where_value = " id = " + id;
if (selection != null && !selection.equals("")) {
where_value += " and " + selection;
}
count = database.update("student", values, where_value, selectionArgs);
break;

case STUDENTS:
// TODO 这里一般情况下, 不会去更新全部表格
break;
}
} catch (Exception e) {
// TODO: handle exception
}
return count;
}

}
4) MainActivity.java 界面程序代码,主要是作为 Content Resolver 代码解析者。跨应用操作数据库也可以用这个代码来使用的
package com.android.contentproviderdemo;

import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends Activity {

private static final String TAG = "MainActivity";
private Button button1, button2, button3, button4, button5;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initComponent();
// 创建数据库
button1.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
DBOpenHelper helper = new DBOpenHelper(MainActivity.this);
// 调用 getWritableDatabase()或者 getReadableDatabase()其中一个方法将数据库建立
helper.getWritableDatabase();
}
});

// 插入数据
button2.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
MainActivity.this.insert();
}
});

// 删除数据
button3.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
MainActivity.this.delete();
}
});

// 修改数据
button4.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
MainActivity.this.update();
}
});

// 查询数据
button5.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
MainActivity.this.query();
}
});
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

private void initComponent(){
button1 = (Button) findViewById(R.id.button1);
button2 = (Button) findViewById(R.id.button2);
button3 = (Button) findViewById(R.id.button3);
button4 = (Button) findViewById(R.id.button4);
button5 = (Button) findViewById(R.id.button5);
}

private void insert(){
// 访问内容提供者的步骤:
// 1. 需要一个内容解析者
ContentResolver contentResolver = MainActivity.this.getContentResolver();
/*
* URI的构成
* 它是通过 Uri.parse("content://")方法来构成的
* 构成结构是 :"content://" + "授权路径" + "/" + "标识符 "
*/
Uri uri = Uri.parse("content://com.android.contentproviderdemo.StudentProvider/student");
ContentValues values = new ContentValues();
values.put("name", "AHuier");
values.put("address", "XIAMEN");
contentResolver.insert(uri, values);
}

private void delete() {
ContentResolver contentResolver = MainActivity.this.getContentResolver();
// 删除单条记录,如果要删除多行记录 :content://com.android.contentproviderdemo.StudentProvider/student
/*
* 1 表示当前删除 id = 1的这条记录,这个 id = 1 会传递到 StudentProvider 中去匹配URI规则后解析出 ID ,然后对其进行执行SQL的语句
*/
Uri uri = Uri.
parse("content://com.android.contentproviderdemo.StudentProvider/student/1");
contentResolver.delete(uri, null, null);
}

private void update() {
ContentResolver contentResolver = MainActivity.this.getContentResolver();
Uri uri = Uri.
parse("content://com.android.contentproviderdemo.StudentProvider/student/2");
ContentValues values = new ContentValues();
values.put("name", "HUI");
values.put("address", "Beijing");
contentResolver.update(uri, values, null, null);
}

//查询的结果是一个游标,也就是返回的查询记录,查询可能返回一条记录,也可能返回多条记录
private void query() {
ContentResolver contentResolver = MainActivity.this.getContentResolver();
// 查询单条记录 : content://com.android.contentproviderdemo.StudentProvider/student/2
// 查询多条记录 : content://com.android.contentproviderdemo.StudentProvider/student
Uri uri = Uri.parse("content://com.android.contentproviderdemo.StudentProvider/student/2");
// select * from student where id = 2;
Cursor cursor = contentResolver.query(uri, null, null, null, null);
while(cursor.moveToNext()){
Log.i(TAG, "ahuier---->" + cursor.getString(cursor.getColumnIndex("name")));
}
}
}

4. 程序实现结果

1). 程序主界面

【Android 开发】:Content Provider (内容提供者) 详解

2). 往数据插入3个数据
【Android 开发】:Content Provider (内容提供者) 详解


【Android 开发】:Content Provider (内容提供者) 详解
3). 删除 id = 1 的数据
【Android 开发】:Content Provider (内容提供者) 详解
4). 修改 id = 2 的数据
【Android 开发】:Content Provider (内容提供者) 详解
3). 查询 id = 2 的名称
【Android 开发】:Content Provider (内容提供者) 详解

5. 后续

  如何来证明其实跨应用进行通信呢?
另外再写一个应用,操作数据库的方式与上述代码的中的 MainActivity.java 方式类似,也是作为 Content Resolver 解析者的使用

详情参考 : http://developer.android.com/guide/topics/providers/content-providers.html

源代码下载 : http://download.csdn.net/my/uploads