ContentProvider的简单解析
1.定义
ContentProvider为四大组件之一
2.作用:
实现进程间的数据通信
3.实现原理
Android的binder机制
Binder解析
4.具体使用
在介绍ContentProvider的具体使用时,先介绍一些相关的知识
4.1URI
URI:统一资源标识符,用于唯一标识使用的ContentProvider和数据
外部进程通过URI找到对应的ContentProvider,并调用其中的方法来操作数据
URI分为系统预置URI和自定义URI,这里说明自定义URI
URI:主题://授权信息/表名/记录
主题(Sche):ContentProvider的URI的前缀。
授权信息(Authority):ContentProvider的唯一标识。
表名(Path):ContentProvider指向的数据库表名。
记录(Record):表名中的某一条记录。
Uri parse = Uri.parse("content://com.zhqy.provider/user/1");
//uri指向名为com.zhqy.provider的contentProvider中表名为user的id为1的记录
//URI存在通配符*和#:
//*:代表任何长度的有效字符串
//content://com.zhqy.provider/# 匹配该contentProvider的所有内容
//#:代表任意长度的有效数字字符串
//content://com.zhqy.provider/user/# 匹配contentProvider中表名为user的所有数据
4.2MIME数据类型
定义:指定文件后缀名以那种方式打开
text/html:指定.html为后缀的文件已text应用程序打开
MIME类型组成:
MiME分为父类型和子类型
text/html text为父类型 html为子类型
MIME类型形式
MIME有两种形式:
// 形式1:单条记录
vnd.android.cursor.item/自定义
// 形式2:多条记录(集合)
vnd.android.cursor.dir/自定义
// 注:
// 1. vnd:表示父类型和子类型具有非标准的、特定的形式。
// 2. 父类型已固定好(即不能更改),只能区别是单条还是多条记录
// 3. 子类型可自定义
具体实例:
如果URI为content://com.zhqy.provider/user/1
则mime类型可以为:vnd.android.cursor.item/vnd.provider.user
如果URI为content://com.zhqy.provider/user
则mime类型可以为:vnd.android.cursor.dir/vnd.provider.user
获取MIME类型
可以通过ContentProvider.getType(uri)来获取uri对应的MIME类型。
4.3ContentProvider
数据组织方式
ContentProvider的数据组织方式以表格为主,即一个表格可以有多个表,表中可以有多行和多列,行成为记录,列成为字段。
数据库就是表格形式的数据集
当然ContentProvider的数据源也可以是其他形式的数据,如文件,网络数据等
主要方法
一旦继承了ContentProvider就需要实现如下六个方法:
//外部进程查询contentProvider中的数据
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
//外部进程向contentProvider中添加数据
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
//外部进程向contentProvider中删除数据
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
//外部进程向contentProvider中更新数据
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
//获取URI对应的mime类型
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
//当contentProvider创建或外部进程第一次调用contentProvider时会调用该方法,可以在该方法中初始化数据源
// 注:运行在ContentProvider进程的主线程,故不能做耗时操作
@Override
public boolean onCreate() {
return false;
}
如果是自定义ContentProvider则必须重写上述六个方法
4.4 辅助工具类
ContentResolver
统一管理不同ContentProver的操作
ContentResolver的主要方法:
//查询数据
Cursor query(Uri uri,String[] projection, String selection,String[] selectionArgs,String sortOrder)
//添加数据
insert( Uri url, ContentValues values)
//修改数据
update(Uri uri,ContentValues values, String where,String[] selectionArgs)
//删除数据
delete(Uri url, String where,String[] selectionArgs)
具体使用:
//获取contenResolver
ContentResolver contentResolver = getContentResolver();
//解析URI
Uri uri = Uri.parse("content://com.zhqy.provider/user/1");
//使用contentResolver对uri对应的contentProvider中的数据进行操作
contentResolver.query(uri,null,null,null,null);
ContenUris
对URI进行操作
有两个核心方法:withAppendId和parseId
withAppendId(Uri uri,long id):向uri中追加一个id
parseId(Uri uri):解析uri中的id值
使用方法如下:
//解析URI
Uri uri = Uri.parse("content://com.zhqy.provider/user");
//向uri中增加id,此时uri为content://com.zhqy.provider/user/1
ContentUris.withAppendedId(uri,1);
//解析uri中的id,获取的id值为1
long id = ContentUris.parseId(uri);
UriMatcher
在ContentProvider 中注册URI
根据 URI 匹配 ContentProvider 中对应的数据表
具体使用:
//权限
private static final String AUTHPORITY="com.zhqy.provider";
//表名
private static final String TABLE_USER="user";
private static final String TABLE_JOB="job";
//注册码
private static final int CODE_USER=1;
private static final int CODE_JOB=2;
private static UriMatcher uriMatcher;
static {
// 即初始化时不匹配任何东西
uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
//注册uri
uriMatcher.addURI(AUTHPORITY,TABLE_USER,CODE_USER);
uriMatcher.addURI(AUTHPORITY,TABLE_JOB,CODE_JOB);
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
//根据uri对应的注册码返回MIME类型
switch (uriMatcher.match(uri)){
case CODE_USER:
return "vnd.android.dir/vnd.provider.user";
case CODE_JOB:
return "vnd.android.dir/vnd.provider.job";
}
return null;
}
ContentObserver
内容观察者,当ContentProvider发生改变时(增删改)通知观察者ContentProvider的数据发生改变
具体使用:
public class MyContentObserver extends ContentObserver {
/**
* Creates a content observer.
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public MyContentObserver(Handler handler) {
super(handler);
}
//当ContentProvider数据发生改变时会触发该方法
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
}
// 步骤1:注册内容观察者ContentObserver
getContentResolver().registerContentObserver(uri);
// 通过ContentResolver类进行注册,并指定需要观察的URI
// 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
public class UserContentProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
db.insert("user", "userid", values);
getContext().getContentResolver().notifyChange(uri, null);
// 通知访问者
}
}
// 步骤3:解除观察者
getContentResolver().unregisterContentObserver(uri);
// 同样需要通过ContentResolver类进行解除
5.ContentProvider的实际使用
ContentProvider的使用有以下两种情况:1.同一进程下的使用ContentProvider操作数据2.不同进程间使用ContentProvider操作数据.
同一进程(数据源为数据库)
1.创建数据库操作类
package com.zhqy.contentproviderdemo;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* Created by jackal on 2019/8/20.
*/
public class MySqlliteOpenHelper extends SQLiteOpenHelper {
private static final String TABLE_USER="user";
private static final String TABALE_JOB="job";
private static final String DATABASE_NAME="databse";
private static int VERSION=1;
public MySqlliteOpenHelper(Context context) {
super(context, DATABASE_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
//创建表
db.execSQL("CREATE TABLE IF NOT EXISTS "+TABLE_USER+"(id INTEGER PRIMARY KEY AUTOINCREMENT ,name text)");
db.execSQL("CREATE TABLE IF NOT EXISTS "+TABALE_JOB+"(id INTEGER PRIMARY KEY AUTOINCREMENT ,jobname text)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
2.自定义ContentProvider
package com.zhqy.contentproviderdemo;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
/**
* Created by jackal on 2019/8/20.
*/
public class MyContentProvider extends ContentProvider {
//权限
private static final String AUTHPORITY = "com.zhqy.provider";
//表名
private static final String TABLE_USER = "user";
private static final String TABLE_JOB = "job";
//注册码
private static final int CODE_USER = 1;
private static final int CODE_JOB = 2;
private static UriMatcher uriMatcher;
private SQLiteDatabase database;
static {
// 即初始化时不匹配任何东西
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//注册uri
uriMatcher.addURI(AUTHPORITY, TABLE_USER, CODE_USER);
uriMatcher.addURI(AUTHPORITY, TABLE_JOB, CODE_JOB);
}
@Override
public boolean onCreate() {
Context context = getContext();
//创建数据库操作类
MySqlliteOpenHelper mySqlliteOpenHelper = new MySqlliteOpenHelper(context);
database = mySqlliteOpenHelper.getWritableDatabase();
//添加记录
ContentValues values1 = new ContentValues();
values1.put("name", "小鸡");
database.insert(TABLE_USER, null, values1);
ContentValues values2 = new ContentValues();
values2.put("name", "小鸭");
database.insert(TABLE_USER, null, values2);
ContentValues values3 = new ContentValues();
values3.put("jobname", "码农");
database.insert(TABLE_JOB, null, values3);
ContentValues values4 = new ContentValues();
values4.put("jobname", "菜农");
database.insert(TABLE_JOB, null, values4);
return true;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
//根据uri对应的注册码返回MIME类型
switch (uriMatcher.match(uri)) {
case CODE_USER:
return "vnd.android.dir/vnd.provider.user";
case CODE_JOB:
return "vnd.android.dir/vnd.provider.job";
}
return null;
}
//查询数据
@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 database.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);
}
//添加数据
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
String tableName = getTableName(uri);
database.insert(tableName, null, values);
return uri;
}
//删除数据
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
String tableName = getTableName(uri);
return database.delete(tableName, selection, selectionArgs);
}
//更新数据
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
String tableName = getTableName(uri);
return database.update(tableName,values,selection,selectionArgs);
}
//获取表名
public String getTableName(Uri uri) {
String tableName = null;
switch (uriMatcher.match(uri)) {
case CODE_USER:
tableName = "user";
break;
case CODE_JOB:
tableName = "job";
break;
}
return tableName;
}
}
3.在Manifest.xml注册该ContentProvider
<provider
android:authorities="com.zhqy.provider"
android:name=".MyContentProvider"></provider>
4.访问ContentProvider中的数据
package com.zhqy.contentproviderdemo;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//解析URI
Uri uri = Uri.parse("content://com.zhqy.provider/user");
//获取contentResolver
ContentResolver contentResolver = getContentResolver();
//查询表中数据
Cursor query = contentResolver.query(uri, null, null, null, null);
while (query.moveToNext()){
Log.e("user",query.getInt(0)+","+query.getString(1));
}
}
}
测试结果:
08-20 17:19:36.267 21006-21006/com.zhqy.contentproviderdemo E/user: 1,小鸡
08-20 17:19:36.267 21006-21006/com.zhqy.contentproviderdemo E/user: 2,小鸭
进程间访问ContentProvider(数据源为数据库)
1.创建数据库操作类(同上)
2.自定义ContentProvider(同上)
3.将contentProvider注册到Manifest.xml上
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zhqy.contentproviderdemo">
<permission android:name="com.zhqy.PROVIDER" android:protectionLevel="normal"></permission>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:authorities="com.zhqy.provider"
android:name=".MyContentProvider"
android:exported="true"
android:permission="com.zhqy.PROVIDER"
></provider>
</application>
</manifest>
其中
android:exported="true" 允许其他进程访问
android:permission="com.zhqy.PROVIDER" 访问该provider所需要的权限
android:authorities="com.zhqy.provider" 授权信息
<permission android:name="com.zhqy.PROVIDER" android:protectionLevel="normal"></permission> 声明权限
操作进程:
1.使用contenProvider所需要的权限
<uses-permission android:name="com.zhqy.PROVIDER"></uses-permission>
2.访问ContentProvider的类
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//解析URI
Uri uri = Uri.parse("content://com.zhqy.provider/user");
ContentResolver contentResolver = getContentResolver();
//通过contenResolver操作contentProvider进行数据查询
Cursor query = contentResolver.query(uri, null, null, null,null);
while (query.moveToNext()){
Log.e("databse",query.getInt(0)+","+query.getString(1));
}
}
}
测试结果
08-20 17:35:38.958 22250-22250/com.zhqy.contentproviderclientdemo E/databse: 1,小鸡
08-20 17:35:38.958 22250-22250/com.zhqy.contentproviderclientdemo E/databse: 2,小鸭
优点
6.1 安全
ContentProvider为应用间的数据交互提供了一个安全的环境:允许把自己的应用数据根据需求开放给 其他应用 进行 增、删、改、查,而不用担心因为直接开放数据库权限而带来的安全问题
6.2 访问简单 & 高效
对比于其他对外共享数据的方式,数据访问方式会因数据存储的方式而不同:
采用 文件方式 对外共享数据,需要进行文件操作读写数据;
采用 Sharedpreferences 共享数据,需要使用 sharedpreferences API读写数据
这使得访问数据变得复杂 & 难度大。
而采用ContentProvider方式,其 解耦了 底层数据的存储方式,使得无论底层数据存储采用何种方式,外界对数据的访问方式都是统一的,这使得访问简单 & 高效
如一开始数据存储方式 采用 SQLite 数据库,后来把数据库换成 MongoDB,也不会对上层数据ContentProvider使用代码产生影响
示意图.png