Content provider是向其他应用程序提供本应用SQLite数据库访问的标准接口,当应用程序的数据库不需要被其他应用访问时直接使用SQLite数据库即可,只有当需要向其他应用开放数据库访问时才考虑使用Content provider。使用Content provider需要SQLite知识的前期准备,可以参见http://blog.csdn.net/leelit/article/details/38853055
一般学Content provider都是先学通过Content provider访问其他应用的数据,再学自己创建内容提供者(或者省略这一步)。我觉得我们以后的开发应该会是访问别人的数据多点吧,所以倒过来学,这样应该对通过Content provider访问别人的数据的原理更加清晰。
实例一:创建应用的内容提供者
①新建一个类继承SQLiteOpenHelper,http://blog.csdn.net/leelit/article/details/38853055,照搬里面的
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DBNAME = "people.db";
private static final int VERSION = 1;
public DatabaseHelper(Context context) {
super(context, DBNAME, null, VERSION);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
// SQL语句
String sql = "create table people(id integer primary key autoincrement,name varchar(64))";
db.execSQL(sql);
System.out.println("--> Database onCreate");
}
@Override
public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) {
// TODO Auto-generated method stub
}
}
②新建一个类继承ContentProvider,重写六个抽象方法
1、onCreate();
2、Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder)
3、Uri insert(Uri uri, ContentValues values)
4、update(Uri uri, ContentValues values, String selection,String[] seletionArgs)
5、delete(Uri uri, String selection, String[] seletionArgs)
6、String getType(Uri uri)
可以很明显观察到,这个类就是用于操作数据库的,而它操作数据库的方法比SQLite的操作多了一个Uri参数。
Uri的标准写法是:content://com.example.app.provider/table,后面也可以再加上一个id,也可以使用通配符/*匹配所有,/#匹配数字。如果其他应用传进了一个Uri,本应用通过解析这个Uri就可以得知其他应用想要对数据库进行何种操作了,而这个解析则是通过UriMatcher这个类来完成的,完成方式也很简单,就是给每个Uri一个值,然后判断这个值来进行数据库操作。
public class MyProvider extends ContentProvider {
public static final int PEOPLE_TABLE = 0;
public static final int PEOPLE_ROW = 1;
public static final String AUTHORITY = "com.example.sqlitedemo.provider";
private static UriMatcher uriMatcher;
private DatabaseHelper dbHelper;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 权限(包名)是com.example.sqlitedemo.provider,路径是people(表名),给这个Uri匹配个0
uriMatcher.addURI(AUTHORITY, "people", PEOPLE_TABLE);
uriMatcher.addURI(AUTHORITY, "people/#", PEOPLE_ROW); // 处理单条记录
}
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
dbHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = null;
// 根据不同的匹配值执行不同的操作,也就是说可以对外界访问做出很多限制
switch (uriMatcher.match(uri)) {
case PEOPLE_TABLE:
cursor = db.query("people", projection, selection, selectionArgs,
null, null, sortOrder);
break;
case PEOPLE_ROW:
String peopleId = uri.getPathSegments().get(1); // 这个方法是将权限后面的以/号分割,取第二个值,其实就是ID啦!
cursor = db.query("people", projection, "id = ?",
new String[] { peopleId }, null, null, sortOrder);
break;
}
// 返回的是查询数据库的游标,可以看到ContentProvider只是间接使用SQLite
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri uriReturn = null;
long lastPeopleId = db.insert("people", null, values);
uriReturn = Uri
.parse("content://com.example.sqlitedemo.provider/people/"
+ lastPeopleId);
return uriReturn;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] seletionArgs) {
// TODO Auto-generated method stub
SQLiteDatabase db = dbHelper.getWritableDatabase();
int updateRows = 0;
switch (uriMatcher.match(uri)) {
case PEOPLE_TABLE:
updateRows = db.update("people", values, selection, seletionArgs);
break;
case PEOPLE_ROW:
String peopleId = uri.getPathSegments().get(1);
updateRows = db.update("people", values, "id = ?",
new String[] { peopleId });
break;
}
return updateRows;
}
@Override
public int delete(Uri uri, String selection, String[] seletionArgs) {
// TODO Auto-generated method stub
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deleteRows = 0;
switch (uriMatcher.match(uri)) {
case PEOPLE_TABLE:
deleteRows = db.delete("people", selection, seletionArgs);
break;
case PEOPLE_ROW:
String peopleId = uri.getPathSegments().get(1);
deleteRows = db.delete("people", "id = ?",
new String[] { peopleId });
break;
}
return deleteRows;
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
switch (uriMatcher.match(uri)) {
case PEOPLE_TABLE:
return "vnd.android.cursor.dir/vnd.com.example.sqlitedemo";
case PEOPLE_ROW:
return "vnd.android.cursor.item/vnd.com.example.sqlitedemo";
}
return null;
}
}
③Manifest文件中application节点下增加一个provider节点
<provider
android:name="com.example.sqlitedemo.MyProvider"
android:authorities="com.example.sqlitedemo.provider" >
</provider>
就这样就OK了,其他应用就可以访问本应用的数据库里的people表啦。新建一个新的应用工程TestProvider,用单元测试来测试一下看能否访问上面SqliteDemo工程的数据库,不懂单元测试的也可以用四个Button来模拟。
public class TestProvier extends AndroidTestCase {
// 添加两条记录
public void testInsert() {
Uri uri = Uri.parse("content://com.example.sqlitedemo.provider/people");
ContentValues values = new ContentValues();
values.put("name", "leelit");
// 访问其他应用的数据库需要用ContentResolver对象
Uri newUri = getContext().getContentResolver().insert(uri, values);
String newId = newUri.getPathSegments().get(1); // 这个方法相当于获得ID值
System.out.println(newId);
ContentValues values1 = new ContentValues();
values1.put("name", "Tom");
Uri newUri1 = getContext().getContentResolver().insert(uri, values1);
String newId1 = newUri1.getPathSegments().get(1);
System.out.println(newId1);
}
// 删除第一条记录
public void testDelete() {
Uri uri = Uri.parse("content://com.example.sqlitedemo.provider/people");
int deleteRows = getContext().getContentResolver().delete(uri,
"id = ?", new String[] { "1" });
System.out.println(deleteRows);
}
// 更新name = leelit的记录
public void testUpdate() {
Uri uri = Uri.parse("content://com.example.sqlitedemo.provider/people");
ContentValues values = new ContentValues();
values.put("name", "jack");
int updateRows = getContext().getContentResolver().update(uri, values,
"name = ?", new String[] { "leelit" });
System.out.println(updateRows);
}
// 查询记录并打印出来
public void testQuery() {
Uri uri = Uri.parse("content://com.example.sqlitedemo.provider/people");
Cursor cursor = getContext().getContentResolver().query(uri, null,
null, null, null);
while (cursor.moveToNext()) {
int id = cursor.getInt(cursor.getColumnIndex("id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
System.out.println("--> " + id + " " + name);
}
}
}
此时,其他应用就可以通过我们自己创建的内容提供者来访问数据库啦!我们可以通过限制Uri的匹配来控制访问权限,假如我们还有一张student的表,如果要被外界访问时,就uriMatcher.addURI(AUTHORITY, "student", 2)就可以啦,后面的数据库增删改查就通过判断uriMatcher.match(uri)来判断匹配哪个数据库的操作,或者根本不匹配即不能被外界访问咯。
小结一下:①一个继承SQLiteOpenHelper的类对象来操作数据库;②一个继承ContentProvider的类向外界提供访问本地数据库的可能,主要是通过给URI匹配一个值,URI的标准写法是content://package.provider/table,通过这个值来判断操作哪个数据库;③Manifest文件下application节点下增加一个provider节点
实例二:通过内容提供者访问系统数据库之通讯录
①首先要增加一个读取通讯录的权限<uses-permission android:name="android.permission.READ_CONTACTS"/>
②做一个单元测试,不懂的也可以用Button来代替......
public class Test extends AndroidTestCase {
public void testReadContacts() {
Cursor cursor = null;
try {
// 系统封装好的通讯录URI,其实就是一张通讯录的表
Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
cursor = getContext().getContentResolver().query(uri, null, null,
null, null);
// 名称的列名
String column_Name = ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME;
// 号码的列名
String column_Number = ContactsContract.CommonDataKinds.Phone.NUMBER;
while (cursor.moveToNext()) {
String name = cursor.getString(cursor
.getColumnIndex(column_Name));
String number = cursor.getString(cursor
.getColumnIndex(column_Number));
String item = name + " : " + number;
System.out.println(item);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
}
}
}
打印出了我的通讯录,通过内容提供者访问其他应用数据库成功。
小结一下:通过先学习创建自己的内容提供者再来使用内容提供者访问别的应用的数据库,这样就能知道底层的原理啦。①getContentResolver()获得一个ContentResolver对象,调用这个对象的IDUQ方法来访问其他应用数据库;②IDUQ方法都会有一个Uri参数,而这个URI主要是用来识别要访问数据库的哪张表的,相信看了实例一就明白了。