ContentProvider是Android中提供的专门用于进程间通信,它底层也是Binder,系统已经为我们做了封装。系统预置了很多ContentProvider,比如通讯录,日程表信息等应用。访问它们都是跨进程。
ContentProvider除了onCreate之外,其他五个方法都是在Binder的线程池中执行的。
ContentProvidr主要以表格形式组织数据,还支持文件数据等。
至于ContentProvider原理,以后学习了会分析
要实现自定义ContentProvider,只需要继承ContentProvider类实现下面6个方法就可以了;
//实现的六个方法都运行在ContentProvier所在进程中
public class BookProvider extends ContentProvider {
//初始化的时候调用,可以进行数据库的创建和升级等操作
//返回true,表示内容提供器初始化成功,
//返回false,表示初始化失败
//这是唯一一个运行在主线程里面,下面5个都运行在Binder的线程池中
@Override
public boolean onCreate() {
return false;
}
//从内容提供器中查询数据,和Sqlite中的query差不多,除了第一个参数
//uri用来确定查询那张表
//projection用来确定查询那些列
//selection和selectionArgs用来确定查询那些行
//sortOrder用于对结果进行排序
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return null;
}
//根据传入的URI来返回相应的MIME类型
@Override
public String getType(Uri uri) {
return null;
}
//插入数据,和Sqlite中的insert差不多,除了第一个参数
//uri来请确定插入那张表
//valus,存放插入的值
//返回一条用于表示这表记录的URI
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
//删除数据 和Sqlite中的delete差不多,除了第一个参数
//uri表示删除哪一张表
//selection和selectionArgs用来确定删除那些行
//被删除的行数作为返回值返回
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
//更新数据 和Sqlite中的update差不多,除了第一个参数
//uri表示那张表
//返回更新的行数
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return 0;
}
}
我们知道SQLite中CRUD中方法中第一个参数是Table表名,而在ContentProvider的CRUD中使用了Uri来代替
它到底表示什么?
其实它表示的是内容URI,内容URI给内容提供者创建数据提供了唯一标识,它由两部分组成:
权限(authority)和路径(path)
权限:是用于区分不同的应用程序,一般使用包名的方式来命名,比如com.shion.app包名,那么对应的权限可以命名为com.shion.app.provider.
路径:用于区分同一个应用中不同的表,通常加到权限的后面。比如一个应用中存在一张表book
那么路径可以命名为/book.
权限和路径组成一个URI,为了体现出它是URI,我们还要在前面加上协议声明content://
所以上面内容URI最标准的格式写法是:
content://com.shion.app.provider/book
得到了内容URI字符串之后,可以把它解析成 Uri对象作为参数传到ContentProvider的增删改查的方法中。
//把URI字符串解析成Uri对象
Uri uri=Uri.parse("content://com.shion.app.provider/book");了
另外要访问ContentProvider,就要使用ContentResolver类,到时看代码就知道了;
下面实现一个简单自定义ContentProvider的Demo;从ContentProvider中查询图书信息;
服务端代码
先是创建一个数据库帮助类:
public class BookDataHelper extends SQLiteOpenHelper {
public BookDataHelper(Context context, String name, int version) {
super(context, name, null, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "create table book(_id integer primary key autoincrement,name text,author text,publisher text);";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
String sql = "drop table if exists book";
db.execSQL(sql);
}
}
接着实现ContentProvider:
public class BookProvider extends ContentProvider {
private SQLiteOpenHelper dbHelper;
private static final int BOOK_DIR = 0;
private static final int BOOK_ITEM = 1;
private static final String AUTHORITY = "com.shion.app.provider";
private static UriMatcher sUriMatcher;
{
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
sUriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
}
@Override
public boolean onCreate() {
dbHelper = new BookDataHelper(getContext(), "app.db", 1);
return true;
}
@Override
public String getType(Uri uri) {
switch (sUriMatcher.match(uri)) {
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.shion.app.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.shion.app.provider.book";
default:
break;
}
return null;
}
@Override
public synchronized Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
switch (sUriMatcher.match(uri)) {
case BOOK_DIR:
cursor = db.query("book", projection, selection, selectionArgs,
null, null, sortOrder);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
cursor = db.query("book", projection, "_id = ?",
new String[] { bookId }, null, null, sortOrder);
break;
default:
break;
}
return cursor;
}
@Override
public synchronized Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
switch (sUriMatcher.match(uri)) {
case BOOK_DIR:
case BOOK_ITEM:
long insertId = db.insert("book", null, values);
Uri sUri = Uri.withAppendedPath(Uri.parse("content://"), AUTHORITY);
return ContentUris.withAppendedId(sUri, insertId);
default:
break;
}
return null;
}
@Override
public synchronized int delete(Uri uri, String selection,
String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
switch (sUriMatcher.match(uri)) {
case BOOK_DIR:
return db.delete("book", selection, selectionArgs);
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
return db.delete("book", "_id = ?", new String[] { bookId });
default:
break;
}
return 0;
}
@Override
public synchronized int update(Uri uri, ContentValues values,
String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getReadableDatabase();
switch (sUriMatcher.match(uri)) {
case BOOK_DIR:
return db.update("book", values, selection, selectionArgs);
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
return db
.update("book", values, "_id = ?", new String[] { bookId });
default:
break;
}
return 0;
}
}
补充一下:
1.“content://com.shion.app.provider/book”//这个内容URI表示想要访问book表
“content://com.shion.app.provider/book/1”//这个内容URI表示想要访问book表中id为1的数据
我们可以使用通配符来分别匹配他们
* : 表示匹配任意长度的的任意字符
# : 表示匹配任意长度的数字
匹配任意表可以这样写
“content://com.shion.app.provider/*”
匹配一个表中任意一行数据可以这样写
“content://com.shion.app.provider/book/#”
2. UriMatcher类可以轻松实现匹配内容URI的功能
比如下面这样就可以把权限和表和一个自定义的常量绑定:
sUriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);
sUriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
当下次调用 sUriMatcher.match(uri),返回自定义的常量实现匹配,可以判断访问的是book这张表还是Book这张表中的某一行数据;
switch (sUriMatcher.match(uri)) {
case BOOK_DIR:/*Book表*/
break;
case BOOK_ITEM:/*Book表中某行行数据*/
break;
default:
break;
}
3.getType方法的是MIME类型返回的值是系统要求的:
3.1. 开头必须是vnd
3.2.1 如果是路径结尾后面接android.cursor.dir/
3.2.2 如果是id结尾后面接android.cursor.item/
3.3 最后是vnd..
唯一区别就是dir和item 比如下面:
内容URI为:“content://com.shion.app.provider/book”所对应的MIME类型是:
“vnd.android.cursor.dir/vnd.com.shion.app.provider.book”//表示整张表的MIME
内容URI为:“content://com.shion.app.provider/book/1”所对应的MIME类型是
“vnd.android.cursor.item/vnd.com.shion.app.provider.book”//表示某一个条记录的MIME
4.在manifest中声明provider
<provider
android:name="com.shion.app.BookProvider"
android:authorities="com.shion.app.provider"
android:exported="true" >
</provider>
android:exported 是Android中的四大组件 Activity,Service,Provider,Receiver 四大组件中都会有的一个属性。
它的主要作用是:是否支持其它应用调用当前组件。
默认值:如果包含有intent-filter 默认值为true; 没有intent-filter默认值为false。
下面贴上布局文件和MainActivity中相关实现代码:
客户端
布局文件activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp" >
<Button
android:id="@+id/btnInsertData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="插入" />
<Button
android:id="@+id/btnDeleteData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="删除" />
<Button
android:id="@+id/btnQueryData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询" />
<Button
android:id="@+id/btnUpdateData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="更改" />
</LinearLayout>
java代码:
public class MainActivity extends Activity implements OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btnInsertData).setOnClickListener(this);
findViewById(R.id.btnDeleteData).setOnClickListener(this);
findViewById(R.id.btnQueryData).setOnClickListener(this);
findViewById(R.id.btnUpdateData).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnInsertData:
insert();
break;
case R.id.btnDeleteData:
delete();
break;
case R.id.btnQueryData:
query();
break;
case R.id.btnUpdateData:
update();
break;
default:
break;
}
}
private Uri uri = Uri.parse("content://com.shion.app.provider");
private void insert() {
Uri inSertUri = Uri.withAppendedPath(uri, "book");
ContentValues values = new ContentValues();
values.put("name", "Android4编程入门经典");
values.put("author", "wei-meng lee");
values.put("publisher", "清华大学");
getContentResolver().insert(inSertUri, values);
}
private void delete() {
Uri deleteUri = Uri.withAppendedPath(uri, "book/1");
getContentResolver().delete(deleteUri, null, null);
}
private void query() {
Uri queryUri = Uri.withAppendedPath(uri, "book");
Cursor c = getContentResolver().query(queryUri, null, null, null, null);
if (c != null) {
while (c.moveToNext()) {
String name = c.getString(c.getColumnIndex("name"));
String author = c.getString(c.getColumnIndex("author"));
String publisher = c.getString(c.getColumnIndex("publisher"));
Log.e("tag", "name: ===" + name);
Log.e("tag", "author: ===" + author);
Log.e("tag", "publisher: ===" + publisher);
}
}
}
private void update() {
Uri updateUri = Uri.withAppendedPath(uri, "book");
ContentValues values = new ContentValues();
values.put("name", "大话数据结构");
values.put("author", "程杰");
values.put("publisher", "清华大学");
getContentResolver().update(updateUri, values, "_id = ?",
new String[] { "1" });
}
}
另附一些系统应用常见的Uri:
关于联系人的一些URI
管理联系人的Uri:
ContactsContract.Contacts.CONTENT_URI
管理联系人的电话的Uri:
ContactsContract.CommonDataKinds.Phone.CONTENT_URI
管理联系人的Email的Uri:
ContactsContract.CommonDataKinds.Email.CONTENT_URI
(注:Contacts有两个表,分别是rawContact和Data,
rawContact记录了用户的id和name,
id栏名称为:ContactsContract.Contacts._ID,
name名称栏为ContactContract.Contracts.DISPLAY_NAME,
电话信息表的外键id为ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
电话号码栏名称为:ContactsContract.CommonDataKinds.Phone.NUMBER.
data表中Email地址栏名称为:
ContactsContract.CommonDataKinds.Email.DATA
其外键栏为:ContactsContract.CommonDataKinds.Email.CONTACT_ID)
关于多媒体的一些URI:
存储在sd卡上的音频文件:
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
存储在手机内部存储器上的音频文件:
MediaStore.Audio.Media.INTERNAL_CONTENT_URI
SD卡上的图片文件内容:
MediaStore.Audio.Images.EXTERNAL_CONTENT_URI
手机内部存储器上的图片:
MediaStore.Audio.Images.INTERNAL_CONTENT_URI
SD卡上的视频:
MediaStore.Audio.Video.EXTERNAL_CONTENT_URI
手机内部存储器上的视频:
MediaStore.Audio.Video.INTERNAL_CONTENT_URI
(注:图片的显示名栏:Media.DISPLAY_NAME,
图片的详细描述栏为:Media.DESCRIPTION
图片的保存位置:Media.DATA
关于短信URI:
Content://sms
发送箱中的短信URI:
Content://sms/outbox
收信箱中的短信URI:
Content://sms/sent
草稿中的短信URI:
Content://sms/draft