本文是我各处东拼西凑加上自己实现一个ContentProvider的使用总结,留做后用,主要介绍ContentProvider的集成方法。
一、综述
ContentProvider是Android四大组件之一,其核心功能是提供应用间的统一的数据访问方式,当然也可以用于应用内的数据封装。ContentProvider类似C/S结构,应用A实现ContentProvider向外提供应用内的数据访问,应用B使用Context.getContentResolver()来间接与应用A的Provider交互。
ContentProvider提供一套类似数据库的query/delete/insert/update的方法来操作数据,开发者可以以此来屏蔽管理数据的真正方式 (SharePreference/File/SQLite等)。另外,ContentProvider也提供了直接操作文件的方式(覆写openFile),第三方应用可以通过该方式读写当前应用的私有文件(data/data等)。
二、相关知识
2.1 UriMatcher和ContentUris的使用
UriMatcher主要用户判断Uri是否符合特定格式,用法如下:
private static final int TYPE_ALL = 1;
private static final int TYPE_SINGLE = 2;
private static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
mUriMatcher.addURI(AUTHORITY, "person", TYPE_ALL);
mUriMatcher.addURI(AUTHORITY, "person/#", TYPE_SINGLE);//#号表示一个数字
}
以上为其声明与配置,下面是使用方法:
switch (mUriMatcher.match(uri)) {
case TYPE_ALL:
//uri匹配到第一种类型
case TYPE_SINGLE:
//uri匹配到第二种类型
default:
//uri没有匹配到这两种类型
}
这样我们就可以知道外部传入的Uri的类型了。
2.2 ContentUris用法
ContentUris用于处理Uri后面的id部分(纯数字),常用函数有:
parseId(uri): 解析Uri里的最后的id
withAppendedId(uri, id):在路径后面加上id部分
三、封装ContentProvider的步骤
提供Provider的工程简称应用A。下面是封装ContentProvider的步骤:
3.1 在Manifest中声明ContentProvider:
<provider android:name="org.test.auth.MyDataProvider"
android:authorities="org.test.auth"
android:exported="true"/>
声明后,应用安装到设备上第三方应用就能通过ContentResolver访问此Provider。
3.2 ContentProvider的代码
package org.test.auth;
import java.io.File;
import java.io.FileNotFoundException;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log;
public class MyDataProvider extends ContentProvider {
private static final String AUTHORITY = "org.test.auth";
private static final int TYPE_ALL = 1;
private static final int TYPE_SINGLE = 2;
private static final String TAG = "TEST111_DataProvider";
private MyDBHelper mDbHelper;
private SQLiteDatabase mDatabase;
private static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
mUriMatcher.addURI(AUTHORITY, "person", TYPE_ALL);
mUriMatcher.addURI(AUTHORITY, "person/#", TYPE_SINGLE);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
switch (mUriMatcher.match(uri)) {
case TYPE_ALL:
return mDatabase.delete(MyDBHelper.TABLE_NAME,
selection,
selectionArgs);
case TYPE_SINGLE:
long id = ContentUris.parseId(uri);
String where = MyDBHelper.COLUMN_ID + " = " + id;
if(!TextUtils.isEmpty(selection)) {
where = where + " and " + selection;
}
return mDatabase.delete(MyDBHelper.TABLE_NAME,
where,
selectionArgs);
default:
return -1;
}
}
@Override
public String getType(Uri uri) {
switch (mUriMatcher.match(uri)) {
case TYPE_ALL:
return "vnd.android.cursor.dir/person";
case TYPE_SINGLE:
return "vnd.android.cursor.item/person";
default:
return null;
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
//由于此时的values中具体是否含有数据是不确定的,所以此时需要在第二个参数中添加person表中的非主键的一列
long id = mDatabase.insert(MyDBHelper.TABLE_NAME, MyDBHelper.COLUMN_NAME, values);
return ContentUris.withAppendedId(uri, id);
}
@Override
public boolean onCreate() {
mDbHelper = new MyDBHelper(getContext());
mDatabase = mDbHelper.getWritableDatabase();
return true;
}
@Override
public Cursor query(Uri uri, String[] columns,
String selection, String[] selectionArgs,
String orderBy) {
switch (mUriMatcher.match(uri)) {
case TYPE_ALL:
return mDatabase.query(MyDBHelper.TABLE_NAME, columns, selection, selectionArgs, null, null, orderBy);
case TYPE_SINGLE:
long id = ContentUris.parseId(uri);
String where = MyDBHelper.COLUMN_ID + " = " + id;
if(!TextUtils.isEmpty(selection)) {
where = where + " and " + selection;
}
return mDatabase.query(MyDBHelper.TABLE_NAME, columns, where, selectionArgs, null, null, orderBy);
default:
return null;
}
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
switch (mUriMatcher.match(uri)) {
case TYPE_ALL:
return mDatabase.update(MyDBHelper.TABLE_NAME, values,
selection, selectionArgs);
case TYPE_SINGLE:
long id = ContentUris.parseId(uri);
String where = MyDBHelper.COLUMN_ID + " = " + id;
if(!TextUtils.isEmpty(selection)) {
where = where + " and " + selection;
}
return mDatabase.update(MyDBHelper.TABLE_NAME, values,
where, selectionArgs);
default:
return -1;
}
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File root = new File(getContext().getFilesDir() + "/" + uri.getPath());
root.getParentFile().mkdirs();
int imode = 0;
if (mode.contains("w")) {
imode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
if (!root.exists()) {
try {
root.createNewFile();
} catch (Exception ex) {
Log.d(TAG, "error happened", ex);
}
}
}
if (mode.contains("r")) imode |= ParcelFileDescriptor.MODE_READ_ONLY;
if (mode.contains("+")) imode |= ParcelFileDescriptor.MODE_APPEND;
return ParcelFileDescriptor.open(root, imode);
}
}
此示例中使用的数据操作方式是数据库,onCreate执行ContentProvider启动时的初始化操作,query/insert/delete/update操作数据,getType返回指定Uri对应的数据类型。“vnd.android.cursor.dir”表示cursor集合,“vnd.android.cursor.item”表示cursor条目。
覆写openFile方法提供私有文件的对外访问,在该方法内可以控制是否提供文件读写,本例代码来自网络,支持读写操作。
附上数据库帮助类代码:
package org.test.auth;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class MyDBHelper extends SQLiteOpenHelper {
public static final String DB_NAME = "test.db";
public static final String TABLE_NAME = "person";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_AGE = "age";
public static final String COLUMN_NAME = "name";
public static final int DB_VERSION = 1;
public MyDBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase database) {
String sql = "create table if not exists " + TABLE_NAME + "( " +
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
COLUMN_NAME +" varchar(100), " +
COLUMN_AGE + " integer" +
" )" ;
database.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) {
}
}
四、测试代码
测试代码在另一个工程中,简称应用B。
package com.example.testcontentprovider;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.util.Log;
public class MainActivity extends Activity {
private static final String URI = "content://org.test.auth/person";
private static final String TAG = "TEST111_";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
doTest();
}
private void doTest() {
ContentResolver cr = getContentResolver();
String typeAll = cr.getType(Uri.parse(URI));
Log.d(TAG, "get all type= " + typeAll);
String typeOne = cr.getType(Uri.parse(URI + "/1"));
Log.d(TAG, "get one type= " + typeOne);
insertValue(cr, "张三", 12);
insertValue(cr, "李四", 23);
//查询所有数据
query(cr, -1);
//查询单条数据
query(cr, 1);
//更改id=1的数据
updateValue(cr, 1, "王五", 20, null, null);
//查询所有数据
query(cr, -1);
//更改name=李四的数据
updateValue(cr, 2, "哈哈", 34, "name=?", new String[] {"李四"});
//查询所有数据
query(cr, -1);
//更改所有数据
updateValue(cr, -1, "小明", 15, null, null);
//查询所有数据
query(cr, -1);
//删除id=1数据
int affectedColumnCountOne = cr.delete(Uri.parse(URI + "/1"), null, null);
Log.d(TAG, "delete one affectedColumnCount= " + affectedColumnCountOne);
//查询所有数据
query(cr, -1);
//删除所有数据
int affectedColumnCountAll = cr.delete(Uri.parse(URI), null, null);
Log.d(TAG, "delete all affectedColumnCount= " + affectedColumnCountAll);
//查询所有数据
query(cr, -1);
//测试文件读写
try {
OutputStream os = cr.openOutputStream(Uri.parse(URI+"/provider_file.txt"));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
bw.append("think who am I?!");
bw.flush();
bw.close();
InputStream is = cr.openInputStream(Uri.parse(URI+"/provider_file.txt"));
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String result = br.readLine();
br.close();
Log.d(TAG, "read file result : " + result);
} catch (Exception e) {
Log.d(TAG, "file op failed", e);
}
}
private void query(ContentResolver cr, int id) {
String idCause = "";
if(id >= 0) {
idCause = "/" + id;
}
Cursor cursorResultAll = cr.query(Uri.parse(URI + idCause), null, null, null, null);
if(null != cursorResultAll) {
while(cursorResultAll.moveToNext()) {
int result = cursorResultAll.getInt(0);
String name = cursorResultAll.getString(1);
int age = cursorResultAll.getInt(2);
Log.d(TAG, "query()| originid= " + id +"; id = " + result + " name= " + name + " age= " + age);
}
}
}
private void insertValue(ContentResolver cr, String name, int age) {
ContentValues value = new ContentValues();
value.put("name", name);
value.put("age", age);
Uri insertColumn = cr.insert(Uri.parse(URI), value);
Log.d(TAG, "insertValue() insert at column: " + insertColumn);
}
private void updateValue(ContentResolver cr, int id, String name, int age,
String selection, String[] selectionArgs) {
if (id >= 0) {
ContentValues value = new ContentValues();
value.put("name", name);
value.put("age", age);
int affectedColumnCount = cr.update(Uri.parse(URI + "/" + id), value, selection, selectionArgs);
Log.d(TAG, "updateValue() affectedColumnCount: " + affectedColumnCount);
} else {
ContentValues value = new ContentValues();
value.put("name", name);
value.put("age", age);
int affectedColumnCount = cr.update(Uri.parse(URI), value,
selection, selectionArgs);
Log.d(TAG, "updateValue() affectedColumnCount: " + affectedColumnCount);
}
}
}
以上,就是实现一个ContentProvider的方法,后续需要补充ContentProvider的原理部分,据说进程间通信是基于共享内存的,以后有时间需要分析一下。