ContentProvider的意义
ContentProvider的类注释如下:
Content providers are one of the primary building blocks of Android applications, providing content to applications. They encapsulate data and provide it to applications through the single ContentResolver interface. A content provider is only required if you need to share data between multiple applications. For example, the contacts data is used by multiple applications and must be stored in a content provider. If you don't need to share data amongst multiple applications you can use a database directly via android.database.sqlite.SQLiteDatabase
内容提供者是Android应用程序的主要构建块之一,为应用程序提供内容。它们封装数据并通过单一的ContentResolver接口提供给应用程序。如果需要在多个应用程序之间共享数据,则只需要内容提供程序。例如,联系人数据由多个应用程序使用,必须存储在内容提供者中。如果您不需要在多个应用程序之间共享数据,您可以直接通过数据库使用数据库。
由此可见,ContentProvider适合进程间的数据共享,你的应用可以通过ContentProvider将需要共享的部分数据提供给其他应用。你也可以通过ContentProvider获取手机的图片、音视频、通讯录等数据
ContentProvider实现
需要重写以下的几个主要方法
- onCreate 初始化创建ContentProvider
- query 返回查询数据
- insert 插入数据
- update 更新数据
- delete 删除数据
- getType
getType方法很特别,基本用不上,但是它具体的作用又是什么呢,来看看它的注释
Implement this to handle requests for the MIME type of the data at the
given URI. The returned MIME type should start with vnd.android.cursor.item for a single record, or vnd.android.cursor.dir/ for multiple items.
根据访问的URI,返回对应的MIME类型的字符串,这个字符串需要以vnd.android.cursor.item(返回单条数据记录)或者vnd.android.cursor.dir/(返回多条数据记录) 开头
看到这些方法是不是很熟悉?跟数据库操作一毛一样啊,其实在这个时候我们可以将ContentProvider理解成一个共享数据库,我们只要在ContentProvider初始化的时候,实例化一个可读写的数据库,在以上的方法中对这个数据库进行增删改查的操作就可以了。当你的数据库有多个表的时候,这些增删改查又是如何区分不同的表呢?其实这些方法都有一个Uri的参数回调,我们可以通过实例UriMatcher来区分用户操作的到底是哪个表。
示例
创建一个DBOpenHelper
public static class TestDBOpenHelper extends SQLiteOpenHelper{
public static final String DB_NAME = "test.db";
public static final int VERSION=1;
public static final String TABLE_NAME = "person";
public static final String TABLE_NAME2 = "class";
public static final String CREATE_TABLE = "create table if not exists "+TABLE_NAME+"(_id integer, name TEXT, desc TEXT)";
public static final String CREATE_TABLE2= "create table if not exists "+TABLE_NAME2+"(id integer, className TEXT, desc TEXT)";
public TestDBOpenHelper(Context context){
super(context,DB_NAME,null,VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
db.execSQL(CREATE_TABLE2);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
ContentProvider
public class TestContentProvider extends ContentProvider {
private static final UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
public static final String AUTHORITY = "com.gzkit.dailysample.TestContentProvider"; //授权
public static final Uri PERSON_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/person");
public static final Uri CLASS_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/class");
private static final int TABLE_CODE_PERSON = 2;
private static final int TABLE_CODE_CLASS = 3;
static {
//关联不同的 URI 和 code,便于后续 getType
mUriMatcher.addURI(AUTHORITY, "person", TABLE_CODE_PERSON);
mUriMatcher.addURI(AUTHORITY, "class", TABLE_CODE_CLASS);
}
//数据库
private SQLiteDatabase mDatabase;
private Context mContext;
private String mTable;
private void initProvider(){
mContext = getContext();
mTable = TestDBOpenHelper.TABLE_NAME;
mDatabase = new TestDBOpenHelper(mContext).getWritableDatabase();
//插入一条初始数据
mDatabase.execSQL("insert into "+mTable+" values(1,'test','good boy') ");
}
@Override
public boolean onCreate() {
initProvider();
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
String tableName = getTableName(uri);
return mDatabase.query(tableName,projection,selection,selectionArgs,null,sortOrder,null);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
int match = mUriMatcher.match(uri);
String MIME = "";
switch (match) {
case TABLE_CODE_PERSON:
MIME = "vnd.android.cursor.dir/multi";
break;
case TABLE_CODE_CLASS:
MIME = "vnd.android.cursor.item/single";
break;
}
return MIME;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
String tableName = getTableName(uri);
mDatabase.insert(tableName,null,values);
//发送数据变动的通知
mContext.getContentResolver().notifyChange(uri,null);
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
/**
* CRUD 的参数是 Uri,根据 Uri 获取对应的表名
*
* @param uri
* @return
*/
private String getTableName(final Uri uri) {
String tableName = "";
int match = mUriMatcher.match(uri);
switch (match){
case TABLE_CODE_PERSON:
tableName = TestDBOpenHelper.TABLE_NAME;
break;
case TABLE_CODE_CLASS:
tableName = TestDBOpenHelper.TABLE_NAME2;
break;
}
return tableName;
}
在AndroidManifest中注册ContentProvider
提供ContentProvider的APP按实际需要加入以下权限
<permission android:name="com.gzkit.dailysample.permission.READ_CONTENT"
android:protectionLevel="normal"
/>
<permission android:name="com.gzkit.dailysample.permission.WRITE_CONTENT"
android:protectionLevel="normal"
/>
<permission android:name="com.gzkit.dailysample.provider.permission"
android:protectionLevel="normal"
/>
如果provider不存在这些权限的属性,声明这些权限则不是必要的
application节点下
<provider
android:name="com.gzkit.dailysample.bean.TestContentProvider"
android:authorities="com.gzkit.dailysample.TestContentProvider"
android:exported="true"
android:readPermission="com.gzkit.dailysample.permission.READ_CONTENT"
android:writePermission="com.gzkit.dailysample.permission.WRITE_CONTENT"
android:permission="com.gzkit.dailysample.provider.permission"
android:process=":provider"
/>
- name 自定义的类
- authorities 定义授权的名称,在自定义的ContentProvider中要用到
- exported 外部是否能调用
- process 进程名
- readPermission 读取的权限名,必须和前面的permission节点名一致
- writePermission 写入的权限名,必须和前面的permission节点名一致
- permission 读写的权限名,必须和前面的permission节点名一致
值得注意的是,readPermission和writePermission的优先级比permission高。
有以下几种情况
- 所有权限都不声明,其他应用无须声明权限就可以自由读写
- 只声明了permission,则其他应用需要声明对应的权限,即可读写
- 只声明readPermission,则其他应用需要声明此权限,才可以读取数据,但仍然可以写入数据
- 只声明writePermission,和上面相反
- 声明readPermission或writePermission,同时声明了permission,则其他应用需要声明对应的权限,可以读写。
ContentProvider声明了哪种权限,调用的那方就必须声明对应的权限
调用方需要声明对应的权限
<!--ContentProvider需要的权限-->
<uses-permission android:name="com.gzkit.dailysample.permission.READ_CONTENT"/>
<uses-permission android:name="com.gzkit.dailysample.permission.WRITE_CONTENT"/>
<uses-permission android:name="com.gzkit.dailysample.provider.permission"/>
使用ContentProvider
在activity或service中通过getContentResolver()获取resolver接口进行数据的增删查改
public static final String AUTHORITY = "com.gzkit.dailysample.TestContentProvider"; //授权
public static final Uri PERSON_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/person");
public static final Uri CLASS_CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/class");
public void insert(){
ContentValues contentValues = new ContentValues();
contentValues.put("_id",1);
contentValues.put("name","test here");
contentValues.put("desc","here is desc");
getContentResolver().insert(PERSON_CONTENT_URI,contentValues);
}
public void query(){
Cursor cursor = getContentResolver().query(CLASS_CONTENT_URI,null,null,null,null);
Toast.makeText(mContext, "查询结果数:"+cursor.getCount(), Toast.LENGTH_SHORT).show();
cursor.close();
}
下一篇,将会讲到如何利用ContentProvider读取手机的多媒体资料