1 提供程序定义
ContentResolver 方法可提供持续存储的基本“CRUD”(创建、检索、更新和删除)功能。
客户端应用进程中的 ContentResolver 对象和拥有提供程序的应用中的 ContentProvider 对象可自动处理跨进程通信。
注:要访问提供程序,您的应用通常需要在其清单文件中请求特定权限。
2 访问提供程序
通过query(Uri,projection,selection,selectionArgs,sortOrder)来获取字词及列表。 客户端的 ContentResolver.query()会调用提供程序定义的ContentProvider.query()。
- 内容Uri : 内容 URI 是用于在提供程序中标识数据的 URI。内容 URI 包括整个提供程序的符号名称(其权限)和一个指向表的名称(路径)。
Note: Uri和Uri.Buillder类 包含构造URI对象的便利方法。ContentUris包括一些将ID值追加到URI后的方法。例如:
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4); //从用户字典中检索 _ID 为 4 的行
在检索到一组行后想要更新或删除其中某一行时通常会用到 ID 值。
例子:字词表的完整URI是content://user_dictionary/words,协定类表达是 UserDictionary.Words.CONTENT_URI
3 提供程序数据类型
- 整型,长整型(长),浮点型,长浮点型(双倍)
- 二进制大型对象(BLOB)
提供程序还会维护其定义的每个内容URI的MIME。我们可以通过MINE类型信息选择处理类型。例如:联系人提供程序中的 ContactsContract.Data 表会使用 MIME 类型来标记每行中存储的联系人数据类型
3.1 协定类 :
帮助应用使用内容 URI、列名称、 Intent 操作以及内容提供程序的其他功能的常量。例如:联系人提供程序的 ContactsContract 也是一个协定类
3.2 MIME 类型引用 : 内容提供程序可以返回标准 MIME 媒体类型和/或自定义 MIME 类型字符串。
MIME (Multipurpose Internet Mail Extensions) : 多用途互联网邮件扩展
格式:type/subtype : 例如:众所周知的 MIME 类型 text/html 具有 text 类型和 html 子类型。
- 自定义MIME类型
自定义MIME类型字符串的类型值始终是:vnd.android.cursor.dir(多行)或vnd.android.cursor.item(单行)
例如,当联系人应用为电话号码创建行时,它会在行中设置以下 MIME 类型: vnd.android.cursor.item/phone_v2(子类型值是 phone_v2)
假设提供程序包括列车时刻表。提供程序的授权是com.example.trains,并包含表Line1,Line2,Line3。
在响应表Line1的内容URI content://com.example.trains/Line1 时,提供程序会返回MIME类型是 vnd.android.cursor.dir/vnd.example.line1
在响应表Line2中第5行的内容URI content://com.example.trains/Line2/5 时,提供程序会返回MIME类型是 vnd.android.cursor.item/vnd.example.line2
4 提供程序访问的替代形式
- 批量访问:您可以通过 ContentProviderOperation 类中的方法创建一批访问调用,然后通过 ContentResolver.applyBatch() 应用它们。
- 异步查询:您应该在单独线程中执行查询。执行此操作的方式之一是使用 CursorLoader 对象。 加载器指南中的示例展示了如何执行此操作。
- 通过 Intent 访问数据:尽管您无法直接向提供程序发送 Intent,但可以向提供程序的应用发送 Intent,后者通常具有修改提供程序数据的最佳配置。
Demo: 通过Intent修改通讯录
5 创建内容提供程序
5.1 设计数据存储
- 储存结构化数据: Android 系统包括一个 SQLite 数据库 API。SQLiteOpenHelper 类可帮助您创建数据库,SQLiteDatabase 类是用于访问数据库的基类。
- 存储文件数据:
- 使用基于网络的数据,请使用 java.net 和 android.net 中的类
Note:使用二进制大型对象 (BLOB) 数据类型存储大小或结构会发生变化的数据。 例如,您可以使用 BLOB 列来存储协议缓冲区或 JSON 结构。
5.2 设计和实现权限
该权限充当其 Android 内部名称。例如,如果您的 Android 软件包名称为 com.example.<appname>,则应为提供程序授予权限 com.example.<appname>.provider 。
如下描述提供程序权限的作用域,从使用整个程序开始,然后细化。细化的权限优先作用域较大的权限:
- 统一读写提供程序级别权限
一个同时控制对整个提供程序读取和写入访问的权限,通过 <provider> 元素的 android:permission 属性指定。
- 单独的读取和写入提供程序级别权限
针对整个提供程序的读取权限和写入权限。您可以通过 <provider> 元素的 android:readPermission 属性和 android:writePermission 属性 指定它们。它们优先于 android:permission 所需的权限。
- 路径级别权限
针对提供程序中内容 URI 的读取、写入或读取/写入权限。您可以通过 <provider> 元素的 <path-permission> 子元素指定您想控制的每个 URI。
- 临时权限
要想启用临时权限,请设置 <provider> 元素的 android:grantUriPermissions 属性,或者向您的 <provider> 元素添加一个或多个 <grant-uri-permission> 子元素。
要向应用授予临时访问权限, Intent 必须包含 FLAG_GRANT_READ_URI_PERMISSION 和/或 FLAG_GRANT_WRITE_URI_PERMISSION 标志。它们通过 setFlags() 方法进行设置。
5.3 元素
与 Activity 和 Service 组件类似,必须使用 <provider> 元素在清单文件中为其应用定义 ContentProvider 的子类。
5.4 Demo:创建一个自己的内容提供程序
UI 界面:
- 截图如下:
- 功能介绍:
Demo 1:通过Intent方式和CotentResolver接口访问和更新日历提供程序。
Demo 2:通过ContentResolver接口查询/插入/更新/删除自定义的提供程序,提供程序使用的是google Robotium里面提供的RobotiumNotepad项目。 - 代码:
public class ContentProviderActivity extends Activity {
Button mIntentGetButton, mContentP_GetButton, mContentP_SetButton;
Button mQueryButton,mInsertButton,mUpdateButton,mDeleteButton;
EditText mEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contentprovider);
mIntentGetButton = (Button)findViewById(R.id.intentgetbutton);
mIntentGetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//通过Intent方式获取数据,不需要增加权限
Calendar beginTime = Calendar.getInstance();
beginTime.set(2012, 1, 19, 7, 30);
long startMillis = beginTime.getTimeInMillis();
Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
builder.appendPath("time");
ContentUris.appendId(builder, startMillis);
Intent intent = new Intent(Intent.ACTION_VIEW).setData(builder.build());
startActivity(intent);
}
});
mContentP_GetButton = (Button)findViewById(R.id.contentR_getbutton);
mContentP_GetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//通过contentResolver方式增加数据,需要在Manifest里面增加WRITE_CALENDAR权限
QueryCalenderData();
}
});
mContentP_SetButton = (Button)findViewById(R.id.contentR_setbutton);
mContentP_SetButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//通过contentResolver方式增加数据,需要在Manifest里面增加WRITE_CALENDAR权限
updateCalenderData();
}
});
//访问NotePad中定义的Content Provider
mEditText = (EditText)findViewById(R.id.editText);
mQueryButton = (Button)findViewById(R.id.contentprovider_query_button);
mQueryButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//通过contentResolver方式访问,需要在Manifest里面增加权限
QueryNotepadData();
}
});
mInsertButton = (Button)findViewById(R.id.contentprovider_insert_button);
mInsertButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Uri mUri = NoteColumns.CONTENT_URI;
String title = mEditText.getText().toString();
String note = "Use this note for working items";
long now = System.currentTimeMillis();
long createDate = now;
long updateData = now;
InsertNotepadData(mUri,title,note,createDate,updateData);
}
});
mUpdateButton = (Button)findViewById(R.id.contentprovider_update_button);
mUpdateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String title =mEditText.getText().toString();
UpdateNotepadData(title);
}
});
mDeleteButton = (Button)findViewById(R.id.contentprovider_delete_button);
mDeleteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String title =mEditText.getText().toString();
DeleteNotepadData(title);
}
});
}
//---------------------------using in Notepad Demo
private static final String[] NOTEPAD_PROJECTION = new String[] {
NoteColumns._ID, // 0
NoteColumns.TITLE, // 1
};
private void DeleteNotepadData(String title){
Cursor cur = null;
ContentResolver cr = getContentResolver();
Uri uri = NoteColumns.CONTENT_URI;
String selection = "TITLE=?";
String[] selectionArgs = new String[]{title};
cur = cr.query(uri, NOTEPAD_PROJECTION, selection, selectionArgs, null);
long updateID;
int n = 0;
while(cur.moveToNext()){
updateID = cur.getLong(0);
Uri uriDelete = ContentUris.withAppendedId(NoteColumns.CONTENT_URI, updateID);
getContentResolver().delete(uriDelete, null, null);
n++;
}
Toast.makeText(ContentProviderActivity.this, "Delete items : " + n, Toast.LENGTH_SHORT).show();
}
private void UpdateNotepadData(String title){
Cursor cur = null;
ContentResolver cr = getContentResolver();
Uri uri = NoteColumns.CONTENT_URI;
String selection = "TITLE=?";
String[] selectionArgs = new String[]{title};
cur = cr.query(uri, NOTEPAD_PROJECTION, selection, selectionArgs, null);
long updateID;
if(cur.moveToFirst()){
updateID = cur.getLong(0);
}else{
Toast.makeText(ContentProviderActivity.this, "Do not find item with title : " + title, Toast.LENGTH_SHORT).show();
return;
}
ContentValues values = new ContentValues();
values.put(NoteColumns.TITLE, "unkown");
Uri uriUpdate = ContentUris.withAppendedId(uri, updateID);
int row = getContentResolver().update(uriUpdate, values, null, null);
Toast.makeText(ContentProviderActivity.this, "Update row :" + updateID + "\n id = " + updateID, Toast.LENGTH_SHORT).show();
}
private void InsertNotepadData(Uri uri,String title, String note, long createDate, long updateDate){
ContentValues values = new ContentValues();
values.put(NoteColumns.TITLE, title);
values.put(NoteColumns.NOTE, note);
values.put(NoteColumns.CREATED_DATE, createDate);
values.put(NoteColumns.MODIFIED_DATE, updateDate);
getContentResolver().insert(uri, values);
Toast.makeText(ContentProviderActivity.this, "Insert item that title is : " + title + " successfully", Toast.LENGTH_SHORT).show();
}
private void QueryNotepadData(){
Cursor cur = null;
ContentResolver cr = getContentResolver();
Uri uri = NoteColumns.CONTENT_URI;
String selection = "";
String[] selectionArgs = new String[]{};
cur = cr.query(uri, NOTEPAD_PROJECTION, selection, selectionArgs, null);
String output = "";
while(cur.moveToNext()){
long mID = cur.getLong(0);
String mTitle = cur.getString(1);
output = output + "\n mTitle : " + mTitle + " mID : " + mID;
}
Toast.makeText(this, "Total number is" + cur.getCount() + "\n" + output , Toast.LENGTH_SHORT).show();
}
//----------------------------
public static final String[] EVENT_PROJECTION = new String[]{
Calendars._ID, //0
Calendars.ACCOUNT_NAME, //1
Calendars.CALENDAR_DISPLAY_NAME, //2
Calendars.OWNER_ACCOUNT //3
};
private void QueryCalenderData(){
Cursor cur = null;
ContentResolver cr = getContentResolver();
Uri uri = Calendars.CONTENT_URI;
String selection = "";
String[] selectionArgs = new String[]{};
cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
String output = null;
while(cur.moveToNext()){
long calID = cur.getLong(0);
String accountName = cur.getString(1);
String calendarDisplayName = cur.getString(2);
String ownerAccount = cur.getString(3);
output = output + "\ncalendarDisplayName : " + calendarDisplayName + " calID : " + calID;
}
Toast.makeText(this, "Total number is" + cur.getCount() + "\n" + output , Toast.LENGTH_SHORT).show();
}
private void updateCalenderData(){
long calID = 2;
ContentValues values = new ContentValues();
values.put(Calendars.CALENDAR_DISPLAY_NAME, "lewi's Calendar");
Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID);
int rows = getContentResolver().update(updateUri, values, null, null);
Toast.makeText(this, "Rows updated :" + rows, Toast.LENGTH_SHORT).show();
}
}
代码分析:
自定义的NotePadProvider通过SQLiteDatabase来保存数据。
通过在NotePadProvider中加入打印日志分析,自定义的ContentProvider的启动时机是当有client去调用的时候。第一次会初始化DatabaseHelper类,之后直接和数据库交互。问题描述:
希望NotePadProvider能够提供不同权限的接口,但是访问时, 提供权限不够。在csdn论坛里面发帖询问,暂时无人回答:http://bbs.csdn.net/topics/391843256
6 Android自带提供程序
- Calendar Provider : http://developer.android.com/intl/zh-cn/guide/topics/providers/calendar-provider.html
- Contacts Provider: http://developer.android.com/intl/zh-cn/guide/topics/providers/contacts-provider.html
- Storage Access Framework(SAF) :http://developer.android.com/intl/zh-cn/guide/topics/providers/document-provider.html
7 Reference:
http://developer.android.com/intl/zh-cn/reference/android/content/ContentProvider.html 官方api文档
http://developer.android.com/intl/zh-cn/guide/topics/providers/content-providers.html 中文文档
http://www.android-doc.com/reference/android/provider/package-summary.html 访问Android提供的 content provider的类接口(协定类)
http://developer.android.com/intl/zh-cn/guide/topics/providers/contacts-provider.html 联系人提供程序
http://developer.android.com/intl/zh-cn/reference/android/Manifest.permission.html permission类(提供了具体权限的常量)
http://developer.android.com/intl/zh-cn/guide/topics/manifest/provider-element.html Provider元素