引言
我们知道Android有四大组件,ContentProvider是其中之一,顾名思义:内容提供者。什么是内容提供者呢?一个抽象类,可以暴露应用的数据给其他应用。应用里的数据通常说的是数据库,事实上普通的文件,甚至是内存中的对象,也可以作为内容提供者暴露的数据形式。为什么要使用内容提供者呢?从上面定义就知道,内容提供者可以实现应用间的数据访问,一般是暴露表格形式的数据库中的数据。内容提供者的实现机制是什么呢?由于是实现应用间的数据通信,自然也是两个进程间的通信,其内部实现机制是Binder机制。那么,内容提供者也是实现进程间通信的一种方式。事实上在开发中,很少需要自己写一个ContentProvider,一般都是去访问其他应用的ContentProvider。本篇文章之所以去研究如何自己写一个ContentProvider,也是为了更好的在开发中理解:如何访问其他应用的内容提供者
如何自定义一个ContentProvider
接下来介绍如何自己去实现一个内容提供者,大致分三步进行:- 继承抽象类ContentProvider,重写onCreate,CUDR,getType六个方法:onCreate()方法中,获取SQLiteDatabase对象;CUDR方法通过对uri进行判断,做相应的增删改查数据的操作;getType方法是返回uri对应的MIME类型。
- 注册可以访问内容提供者的uri:创建静态代码块,static{…code},在类加载的时候注册可以访问内容提供者的uri,使用类UriMatcher的addURI(…)完成。
- 清单文件中配置provider:注册内容提供者,加入authorities属性,对外暴露该应用的内容提供者。
以下是代码部分
package com.wzy.myprovider;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
public class BookProvider extends ContentProvider {
static final String DATABASE_NAME = "provider.db";
static final String DATABASE_TABLE = "book";
static final int DATABASE_VERSION = 1;
static final String DATABASE_CREATE = "create table " +
DATABASE_TABLE + " (_id integer primary key autoincrement, " + "name text not null, author text not null);";
static final String PROVIDER_NAME = "com.wzy.provider";
static final int BOOKS = 1;
static final int BOOK_ID = 2;
static UriMatcher uriMatcher;
SQLiteDatabase db;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS);
uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID);
}
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS BookInfo");
onCreate(db);
}
}
@Override
public boolean onCreate() {
DatabaseHelper helper = new DatabaseHelper(getContext());
db = helper.getWritableDatabase();
return false;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
switch (uriMatcher.match(uri)) {
case BOOKS:
return "vnd.android.cursor.dir/vnd.com.wzy.provider.books";
case BOOK_ID:
return "vnd.android.cursor.item/vnd.com.wzy.provider.books";
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Cursor cursor = null;
switch (uriMatcher.match(uri)) {
case BOOK_ID:
cursor = db.query(DATABASE_TABLE, projection, "_id = ", new String[]{uri.getPathSegments().get(1)}, null, null, sortOrder);
break;
case BOOKS:
cursor = db.query(DATABASE_TABLE, projection, selection, selectionArgs, null, null, sortOrder);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
return cursor;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
long rowId = db.insert(DATABASE_TABLE, null, values);
if (rowId > 0) {
Uri bookUri = ContentUris.withAppendedId(uri, rowId);
return bookUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
int rowIDs;
switch (uriMatcher.match(uri)) {
case BOOK_ID:
String bookID = uri.getPathSegments().get(1);
rowIDs = db.delete(DATABASE_TABLE, "_id = ", new String[]{bookID});
break;
case BOOKS:
rowIDs = db.delete(DATABASE_TABLE, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
return rowIDs;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
int rowIDs;
switch (uriMatcher.match(uri)) {
case BOOK_ID:
String bookID = uri.getPathSegments().get(1);
rowIDs = db.update(DATABASE_TABLE, values, "_id = ?", new String[]{bookID});
break;
case BOOKS:
rowIDs = db.update(DATABASE_TABLE, values, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
return rowIDs;
}
}
初次观察代码,我们可以发现,内容提供者就是基于sql实现的一层封装。我们再观察静态代码块,在这里,值得一提的是静态代码块总是运行在类的构造方法执行之前。
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(PROVIDER_NAME, "books", BOOKS);
uriMatcher.addURI(PROVIDER_NAME, "books/#", BOOK_ID);
}
我们进入UriMatcher的构造方法去查看下
public UriMatcher(int code)
{
mCode = code;
mWhich = -1;
mChildren = new ArrayList<UriMatcher>();
mText = null;
}
在这个里面维护了一个ArrayList,可以预知的是,这个类里面的addURI和match方法就是基于对这个列表添加和遍历实现的。在这里就不多做赘述了,有兴趣的童鞋可以自行研究。
我们继续往下观察,我们创建了一个抽象帮助类SQLiteOpenHelper的子类并在onCreate中去实例化了一个对象,然后通过getWritableDatabase()获取SQLiteDatabase实例。其实这里应该的做法是单独创建一个类继承自SQLiteOpenHelper,并使用单例模式来获取其实例。
我们来了解下uri。uri是如何构成的呢? uri 有三个构成元素scheme + authorities + path。先看这样一个uri,uri = “content://com.wzy.provider/books”,在这里,三个构成元素分别是:
scheme:“content://”; 一般是固定的
authorities :“com.wzy.provider”;authorities就是在清单文件中配置的authorities属性的值,唯一标识该应用的内容提供者。
path:"/books";path里面常常放的是一些表名,字段信息,确定访问该数据库中哪个表的哪些数据.
继续往下,我们接下里分析CUDR操作,我们重写了这样四个方法:query,insert,delete,update,这个四个方法的参数都是想访问该应用的其他用户传递过来的.我们调用uriMatcher.match(uri)来匹配uri进行对应的CRUD操作.如果返回的code没有在uriMatcher中注册过.就跑出IllegalArgumentException("Unsupported URI: " + uri)异常,代表uri不合法。如果合法就调用SQLiteDatabase的query,insert,delete,update四个方法进行增删改查数据。
值得一提的是,在进行insert操作的时候返回的是一个uri,我们往数据库插入一条记录并成功返回该记录的ID的时候,会将该ID以路径的方式拼接到原始uri上面。举个栗子:比如说我们通过"content://com.wzy.provider/books"往数据库表book成功插入一条数据,并返回id为99,那么我们得到的uri就是"content://com.wzy.provider/books/99".
这样,一个完整的ContentProvider就定义完成了。不要忘记在清单文件中进行申明:
<provider
android:name=".BookProvider"
android:authorities="com.wzy.provider"
android:exported="true">
</provider>
要注意,设置exported="true"是为了其他应用能访问到我们的BookProvider。
关于调用
现在内容提供者也定义好了,我们该派他上场了。之前说过,内容提供者是基于Binder实现的,可以进行跨进程访问。现在我们重新创建一个测试应用,写个简单的测试代码并打印log可以看下效果private void test() {
Uri uri = Uri.parse("content://com.wzy.provider/books");
ContentValues values = new ContentValues();
values.put("name","西游记");
values.put("author","吴承恩");
Uri bookUri = getContentResolver().insert(uri,values);
values.put("name","红楼梦");
values.put("author","曹雪芹");
getContentResolver().update(bookUri,values,null,null);
}
写 在最后
继承ContentProvider还有一个重要的getType()方法需要重写,这个请看
【我的Android进阶之旅】关于ContentProvider的getType方法
作者:王宗耀 欢迎转载,与人分享是进步的源泉!
转载请保留原文地址:http://blog.csdn.net/wzy901213