您的位置:首页 > 移动开发 > Android开发

Android核心组件之ContentProvider(一)--初步学习

2014-07-21 11:20 423 查看
转载请注明出处/article/7809128.html

Content Provider是什么

今天我们来学习Android的另外一个核心组件---Content Provider。在前面学习《使用AIDL实现进程间的通信》这篇文章中我们提到,为了安全考虑,在Android系统中一般情况下一个进程是无法直接访问另外一个进程的内存的,原因是Android是一个多进程系统,在这个系统中,应用程序(或者系统的部分)会在自己的进程中运行。系统和应用之间的安全性通过Linux的facilities在进程级别来强制实现的,比如会给应用程序分配user
ID和Group ID。更细化的安全特性是通过"Permission"机制对特定的进程的特定的操作进行限制,而"per-URI permissions"可以对获取特定数据的access专门权限进行限制。 所以,应用程序之间的一般是不可以互相访问的,因此,Android中一般也是不允许一个进程直接访问另外一个进程的私有数据(SharedPreference文件中的数据,SQLite数据库中的数据)。但在有些情况,应用程序需要将自己的某些数据暴露出来以供其它应用程序访问使用(比如手机中的联系人信息,音视频、图片资源等等),那么Android为了满足此需求,于是创建了Content
Provider。在Android系统中,存在着许多 Content Provider集合,作为应用程序中的一部分,它们封装了数据,并为数据提供了安全的访问机制,但它们最主要的作用还是支持在多个应用中存储和读取数据,实现了应用程序间数据的共享。举个例子,。总之对Content Provider的定义一句话概括就是:Content
Provider是Android为实现应用程序间安全地共享和访问数据所提供的一个持久的标准接口。

Content Provider使用原理

在上一小节我们了解了Content Provider的基本概念,知道了Content Provider是为应用程序的数据访问提供了一个统一的接口,那么Content Provider到底以什么样的数据展现形式为外部访问对象(本应用或其它应用程序)提供数据的呢?其实很简单,类似于关系型数据库的表,Content Provider是以一个或多个表的形式将数据提供给外部访问对象。下面有一张展现用户字典
Provider的数据的图示,如下:







其实熟悉关系型数据数据库(MySql、Oracle等)的读者应该对上面这张图的应该很容易理解,它就类似于数据库中的表结构,每一行都表示一个英文单词的实例,每一列都显示了其所属单词实例的某项属性信息,比如local这个属性,就表示了该单词被用户首次遇到的语言环境,还有frequency表示该单词所出现的频率等等。在上面这张用户字典Provider中,_ID这个属性默认作为类似于关系型数据库表结构中的主键被Provider自动维护,当每次在其Provider增加一条数据,_ID属性值都会自动增加,该属性值为每个单词实例提供了在整个Provider中的唯一性。然而需要注意的是,Provider并不是像关系型数据库表中那样,都需要类似_ID这样一个主键的属性,这个得看在不同的环境中的不同需求,有的情况下必须要,但有些情况下却不是必须的。比如,当我们需要将从Provider中获取到的数据绑定到ListView控件中时,我们就必须需要_ID这个属性,以标识在列表中每一项的唯一性。
那么学习了Content Provider的基本概念以及Content Provider向外部访问对象展示数据的基本形式后,我们是不是应该进一步了解外部访问对象是如何访问Content Provider中的数据呢?好,下面我们来具体学习一下。为了方便外部对象访问Content Provider中的数据,Android提供了ContentResolver这个类,ContentResolver提供了CRUD(增删改查)的基本方法用于对Content
Provider中的数据进行基本操作。那么要使用ContentResolver,该如何得到ContentResolver对象呢?其实Android系统已经为实例化ContentResolver对象提供了良好的封装,我们可以通过上下文中的getContentResolver()方法获取实例对象。一般的,我们是在一个应用程序中调用ContentResolver对象的CRUD方法去访问另外一个应用程序中的Content Provider中的数据,那么这种跨进程通信会自动执行,我们不必去编写其它代码。

实现对Images Provider的CRUD操作

Android系统本身提供一些 Content Providers用来管理设备中的一些诸如音视频、图片以及用户联系人等数据,那么接下来我们将通过一个访问系统提供的Images Provider来读取设备中图片的实例以便深入地学习一下Content Provider的使用方法。下面我们所需要实现的功能就是完成对设备中的Images的CRUD操作,具体代码如下:
public class ImagesProviderActivity extends ListActivity {

ImagesProviderItemAdapter mAdapter = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_images_provider);
init();
}

/** ------------------检索ImagesProvider中的数据,初始化数据列表 ----------------------*/
public void init() {
mAdapter = new ImagesProviderItemAdapter(getApplicationContext(),
query(), 0);
// 绑定适配器
setListAdapter(mAdapter);
// 将ListView对象注册到上下文中
this.registerForContextMenu(getListView());
}

/**
* 检索系统提供的Images Provider中的图片信息
*/
@SuppressLint("NewApi")
public Cursor query() {
Cursor mCursor = getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Images.Media._ID,
MediaStore.Images.Media.TITLE,
MediaStore.Images.Media.DATE_TAKEN }, null, null,
"_id desc");
return mCursor;
}

/** ----------------------根据_ID删除ImagesProvider中的数据 ---------------------- */

/**
* 删除系统提供的Images Provider中的图片信息
*/
public void deleteData(final String _id) {
AlertDialog.Builder mDialog = new AlertDialog.Builder(
ImagesProviderActivity.this).setMessage("是否删除数据?")
.setPositiveButton("确定", new DialogInterface.OnClickListener() {

@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
// 执行删除操作
int numberOfRows = delete(_id);
// 重新查询Images Provider中的数据并为Adapter重新绑定数据
mAdapter.changeCursor(query());
// 更新数据列表
mAdapter.notifyDataSetChanged();
Toast.makeText(ImagesProviderActivity.this,
"删除了 " + numberOfRows + "条数据!!",
Toast.LENGTH_LONG).show();
}
}).setNegativeButton("取消", null);
mDialog.show();
}

/**
* 根据_id属性删除数据
*
* @param _id
*            Primary Key(主键值)
* @return numberOfRows 删除数据的数量
*/
public int delete(String _id) {
String where = MediaStore.Images.Media._ID + "= ?";
String[] selectionArgs = { _id };
int numberOfRows = getContentResolver().delete(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, where,
selectionArgs);
return numberOfRows;
}

/** ----------------------根据_ID更新Images Provider中的数据---------------------- */

/**
* 更新系统提供的Images Provider中的图片信息,这里修改图片的名称
*/
public void updateData(final String _id) {
final View mView = LayoutInflater.from(ImagesProviderActivity.this)
.inflate(R.layout.images_provider_update, null);
AlertDialog.Builder mDialog = new AlertDialog.Builder(
ImagesProviderActivity.this).setTitle("更新操作").setView(mView)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {

@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
EditText mEditText_name = (EditText) mView
.findViewById(R.id.images_name_edittext);
int numberOfRows = 0;
if (!TextUtils.isEmpty(mEditText_name.getText())) {
// 执行更新操作
numberOfRows = update(_id, mEditText_name.getText()
.toString());
} else {
Toast.makeText(ImagesProviderActivity.this,
"名称不能为空!!", Toast.LENGTH_LONG).show();
}
// 重新查询Images Provider中的数据并为Adapter重新绑定数据
mAdapter.changeCursor(query());
// 更新数据列表
mAdapter.notifyDataSetChanged();
Toast.makeText(ImagesProviderActivity.this,
"更改了 " + numberOfRows + "条数据!!",
Toast.LENGTH_LONG).show();
}
}).setNegativeButton("取消", null);
mDialog.show();
}

/**
* 执行更新数据操作
*
* @param _id
* @param title
* @return numberOfRows
*/
public int update(String _id, String title) {
String where = MediaStore.Images.Media._ID + "= ?";
String[] selectionArgs = { _id };
// 定义需要修改的数据集合
ContentValues mValues = new ContentValues();
mValues.put(MediaStore.Images.Media.TITLE, title);
int numberOfRows = getContentResolver().update(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mValues, where,
selectionArgs);
return numberOfRows;
}

/** ----------------------向ImagesProvider增加数据---------------------- */

/**
* 添加系统提供的Images Provider中的图片信息
*/
public void insertData() {
final View mView = LayoutInflater.from(ImagesProviderActivity.this)
.inflate(R.layout.images_provider_update, null);
AlertDialog.Builder mDialog = new AlertDialog.Builder(
ImagesProviderActivity.this).setTitle("添加操作").setView(mView)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {

@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
EditText mEditText_name = (EditText) mView
.findViewById(R.id.images_name_edittext);
String savePath = null;
if (!TextUtils.isEmpty(mEditText_name.getText())) {
// 执行添加操作
savePath = insert(mEditText_name.getText()
.toString());
} else {
Toast.makeText(ImagesProviderActivity.this,
"名称不能为空!!", Toast.LENGTH_LONG).show();
}
// 重新查询Images Provider中的数据并为Adapter重新绑定数据
mAdapter.changeCursor(query());
// 更新数据列表
mAdapter.notifyDataSetChanged();
Toast.makeText(ImagesProviderActivity.this,
"保存成功,路径: " + savePath + "。", Toast.LENGTH_LONG)
.show();
}
}).setNegativeButton("取消", null);
mDialog.show();
}

/**
* 执行添加图片操作
*
* @param title
*            图片名称
* @return savePath 图片存储路径
*/
@SuppressLint("NewApi")
public String insert(String title) {
// 得到本地图片的Bitmap对象
Bitmap mBitmap = ((BitmapDrawable) getResources().getDrawable(
R.drawable.stevejobs)).getBitmap();
// 插入图片
String savePath = MediaStore.Images.Media.insertImage(
getContentResolver(), mBitmap, title, "插入的图片");

ContentValues mContentValues = new ContentValues();
mContentValues.put(MediaStore.Images.Media.DATE_TAKEN,
System.currentTimeMillis());
String _id = savePath.substring(savePath.lastIndexOf("/") + 1);
String where = MediaStore.Images.Media._ID + "= ?";
String[] selectionArgs = { _id };
getContentResolver().update(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, mContentValues,
where, selectionArgs);
return savePath;
}

/** ----------------------这里是分割线 ---------------------- */

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
menu.add(0, 1, Menu.NONE, "添加");
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
insertData();
return super.onOptionsItemSelected(item);
}

@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
// TODO Auto-generated method stub
menu.setHeaderTitle("文件操作");
menu.add(0, 1, Menu.NONE, "删除");
menu.add(0, 2, Menu.NONE, "修改");
}

@Override
public boolean onContextItemSelected(MenuItem item) {
// TODO Auto-generated method stub
// 得到当前被选中的item信息
AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo) item
.getMenuInfo();
String _id = null;
if (mAdapter != null) {
Cursor mCursor = (Cursor) mAdapter.getItem(menuInfo.position);
_id = mCursor.getString(mCursor
.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
}
switch (item.getItemId()) {
case 1:
deleteData(_id);
break;
case 2:
updateData(_id);
break;
default:
return super.onContextItemSelected(item);
}
return true;
}
}

布局文件activity_images_provider.xml文件的具体代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.androidleaf.contentprovider.activity.ImagesProviderActivity">

<ListView
android:id="@id/android:list"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
还有就是用于绑定图片数据列表的ImagesProviderItemAdapter.java的具体代码如下:
public class ImagesProviderItemAdapter extends CursorAdapter {

private ViewHolder mViewHolder;
@SuppressLint("NewApi")
public ImagesProviderItemAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
// TODO Auto-generated constructor stub
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
// TODO Auto-generated method stub
mViewHolder = new ViewHolder();
View mConvertView = LayoutInflater.from(context).inflate(R.layout.images_provider_query_item, null);
mViewHolder.mTextView_Id = (TextView)mConvertView.findViewById(R.id.app_id);
mViewHolder.mImageView = (ImageView)mConvertView.findViewById(R.id.image);
mViewHolder.mTextView_image_name = (TextView)mConvertView.findViewById(R.id.image_name);
mViewHolder.mTextView_dateadd = (TextView)mConvertView.findViewById(R.id.add_date);
mConvertView.setTag(mViewHolder);
return mConvertView;
}

@Override
public void bindView(View view, Context context, Cursor cursor) {
// TODO Auto-generated method stub
ViewHolder holder = (ViewHolder) view.getTag();
if(holder != null){
long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));
if(id != -1){
holder.mTextView_Id.setText(String.valueOf(id));
try {
holder.mImageView.setImageBitmap(MediaStore.Images.Media.getBitmap(
context.getContentResolver(),
ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
long dateAdd = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATE_TAKEN));
if(dateAdd != -1){
holder.mTextView_dateadd.setText(DateUtils.formatDateTime(context, dateAdd, -1));
}
String title = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE));
if(!TextUtils.isEmpty(title)){
holder.mTextView_image_name.setText(title.toString());
}
}
}

class ViewHolder{
TextView mTextView_Id;
ImageView mImageView;
TextView mTextView_image_name;
TextView mTextView_dateadd;
}
}
由于涉及到对外部设备的图片数据的读取和修改,因此还需在AndroidManifest.xml文件中添加修改权限,所应添加的代码如下:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
基本的编码已经完成,我们来运行并操作一下,运行的结果如图:



以上程序基本实现了通过访问系统提供的Images Provider对设备中图片数据的增删改查操作,下面我们来分析一下上面所编写的程序所设计到的知识点:
(1)在程序中执行查询操作使用的是getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder)方法,通过传递一系列条件参数来查询我们所需要的图片数据。细心的读者有没有发现,方法所执行的查询方式比较像标准SQL语句的查询方式,下面就是一条数据库的查询语句:
select col1,col2,col3 from tablename where col1 like ? order by col1,col2 [desc]
前面提到Content Provider是以类似于关系型数据库表的形式将数据提供给用户的,其实数据在内部的存储实现上,数据可以存储在SQLite数据库,也可以存储在文件或数组集合中,Content Provider只是对数据的访问提供了一层封装,让数据更具安全性,并提供一个统一的接口方便用户访问。为了更好地使用Content Provider提供的方法,下面提供一张方法和标准SQL查询语句的参数的对比图:



(2)上图,方法中的Content Uri参数对应于SQL语句中的表名(Table Name),它的作用是用于指定所需访问的Providers中的数据的唯一标识。上面程序中,访问图片数据的Uri是MediaStore.Images.Media.EXTERNAL_CONTENT_URI,深入程序源码,我们可知其对应的Uri字符串为:

content://media/external/images/media


同样,查询用户联系人 Provider(ContactsContract Provider)和用户字典 Provider (User Dictionary Provider)所以对应的Uri字符串分别为:

content://com.android.contacts/contacts


content://user_dictionary/words

通过查看和比较上面的不同Content Provider的Uri,我们可以发现Uri的组成是有一定规律的。其实Content Uri的组成一般分为三部分:
<1>content:// :作为 content Uri的特殊标识(必须);
<2>授权(authority) :用于唯一标识这个Content Provider,外部访问者可以根据这个标识找到它;
<3>路径(path) : 所需要访问数据的路径,根据业务而定。
就用户字典 Provider的Uri为例,可以划分成三部分,如下图所示:



想要进一步了解如何自定义和使用Content Uri,可以学习下一篇博文,《Android四大组件之Content Provider(二)--创建自己的Content Provider》,敬请期待!

小结:通过本文我们对Content Provider组件有了一个初步的认识,并学习了如何访问系统提供的Content Provider,大致总结一下学习的知识点:(1)Content Provider的概念;(2)通过访问系统提供的Content Provider,执行数据的CRUD操作;(3)Content Uri的组成方式。作为Android系统的四大组件之一,Content
Provider的重要性不言而喻,从学习角度看,也比较难以理解。因此,接下来我们会通过多篇文章来进一步学习Content Provider。

源代码下载,请戳这里!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: