一、什么是ContentProvider
ContentProvider是Android的四大组件之一,主要用于给不同应用程序提供接口,实现数据共享,并且可以保证数据的安全性。在手机的联系人、短信等应用都会创建ContentProvider提供接口将应用内数据提供给其他应用使用。ContentProvider的底层实现还是使用Binder,主要是以表格的形式操作存储数据,并且可以包含多张表格;也支持文件类型的数据储存操作,如图片、视频等。
二、ContentProvider的基础知识
BroadCast需要使用BroadCast Receive接收广播,而Content Provider数据存储需要借助ContentResolver类提供的一系列方法进行增删改查操作。ContentResolver可以通过Context的getContentResolver()方法获取实例。ContentResolver实例访问对应的数据,使用CRUD方法对数据进行增删改查。
1.内容 URI
内容 URI 给内容提供器中的数据建立了唯一标识符,内容URI的定义和Intentfilter中的data标签下的Uri基本是一致的,它由三部分组成,协议声明(content)、权限(authority)和路径(path)。权限(authority)是对不同应用的区分主要是“应用的包名”,路径(path)是对同一个应用“不同表格”的区分,总结起来就是访问“哪个应用/哪张表”,例如:
content://com.example.app.provider/table1
content://com.example.app.provider/table2/1
内容 URI有两种格式,以路径结尾的格式代表访问“整个表”;以数字id结束的表示访问表中的“某一列”,可以使用通配符的方式区分。例如:
//匹配所有的表
content://com.example.app.provider/*
//匹配table2表中的某一列
content://com.example.app.provider/table2/#
将字符串解析为Uri对象
Uri uri = Uri.parse("content://com.example.app.provider/table1);
2.getType()方法
ContentProvider必须实现的一个方法,是为了获取Uri对象对应的MIME类型。一个内容Uri对应的MIME类型主要分为3个部分组成与它的格式有关系。格式为:
//路径对应dir,匹配表,标准:"vnd."+"android.cursor.dir/"+"vnd."+"authority.path"
vnd.android.cursor.dir/vnd.com.example.app.provider/table1
//id对应item,匹配列,标准:"vnd."+"android.cursor.item/"+"vnd."+"authority.path",注意路径后面未带id
vnd.android.cursor.item/vnd.com.example.app.provider/table1
3.创建SQLite数据库
创建数据库是供ContentProvider进行增删改查的操作的,创建SQLite需要注意建表语句的书写注意空格;必须要有一个构造方法;必须要实现onCreat、update方法。先创建数据库,在调用onCreat方法时创建表,后通过数据库的实例对表进行操作。
package com.mrdouya.mycontenprovidertest;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class MyDatabaseHelper extends SQLiteOpenHelper {
private Context mContext;
private final String TAG = "MrDouYa-DatabaseHelper";
//建表语句,注意空格问题
public static final String CREAT_TABLE = "create table Category ("
+ " id integer primary key autoincrement"
+ ", category_name text"
+ ", category_code integer)";
public static final String CREAT_CATTEGORY = "create table Book ("
+ " id integer primary key autoincrement"
+ ", author text"
+ ", prices real"
+ ", pages integer"
+ ", name text)";
/**
* 必须实现父类的构造方法
* @param context
* @param name
* @param factory
* @param version
*/
public MyDatabaseHelper(Context context,String name,SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
/**
* 必须重写父类的onCreat方法
* @param sqLiteDatabase
*/
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
//创建两张表
sqLiteDatabase.execSQL(CREAT_TABLE);
sqLiteDatabase.execSQL(CREAT_CATTEGORY);
Log.d(TAG,"table book have create!");
}
/**
* 必须重写父类的onUpgrade方法
* @param sqLiteDatabase
* @param i
* @param i1
*/
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
//比较强盗,查询如果存在表就删除
sqLiteDatabase.execSQL("drop table if exists Book");
sqLiteDatabase.execSQL("drop table if exists Category");
//onCreate创建表
onCreate(sqLiteDatabase);
Log.d(TAG,"onUpgrade Book have update!");
}
}
4.六个必须实现的父类方法
package com.mrdouya.mycontenprovidertest;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
public class MyProvider extends ContentProvider {
private final String TAG = "MrDouYa";
/**
* 初始化内容提供器使用
* 完成对数据库的创建和升级等操作
*/
@Override
public boolean onCreate() {
Log.d(TAG,"MyProvider--->onCreate");
return false;
}
/**
* 从内容提供器中查询数据,返回Cursor对象,uri为查询哪张表
* @param uri
* @param strings
* @param s
* @param strings1
* @param s1
* @return
*/
@Override
public Cursor query(Uri uri,String[] strings,String s,String[] strings1,String s1) {
return null;
}
/**
* 更新已有的数据,更据参数约束,返回值为受影响的行数
* @param uri
* @param contentValues
* @param s
* @param strings
* @return
*/
@Override
public int update(Uri uri,ContentValues contentValues,String s,String[] strings) {
return 0;
}
/**
* 向内容提供器添加一条数据,返回一个用于表示这条新记录的URI
* @param uri
* @param contentValues
* @return
*/
@Override
public Uri insert(Uri uri,ContentValues contentValues) {
return null;
}
/**
* 删除一条数据,返回值为被删除的行数
* @param uri
* @param s
* @param strings
* @return
*/
@Override
public int delete(Uri uri,String s,String[] strings) {
return 0;
}
/**
* 更具传入的Uri的返回对应的MIME类型
* @param uri
* @return
*/
@Override
public String getType(Uri uri) {
return null;
}
}
三、实际使用ContentProvider
1.访问其他程序的ContentProvider
非常简单可以分为3个步骤:
- 1).明确Uri(第三方程序提供的数据共享的Uri)
2).根据需要通过getContentRelsove()实例调用对应的方法进行数据操作
3).遍历提取cursor对象中封装的数据
注意:访问权限声明如获取联系人权限、ContentProvider重要方法的“返回值”
package com.mrdouya.mycontenprovidertest;
import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MyProviderTest extends Activity implements View.OnClickListener {
public static final String TAG = "MrDouYa-MyProviderTest";
private String newId;
private Button queryButton;
private Button updateButton;
private Button deleteButton;
private Button insertButton;
private Uri uri;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_provider);
queryButton = findViewById(R.id.bt_provider_query_book);
updateButton = findViewById(R.id.bt_provider_update_book);
deleteButton = findViewById(R.id.bt_provider_delete_book);
insertButton = findViewById(R.id.bt_provider_add_book);
queryButton.setOnClickListener(this);
updateButton.setOnClickListener(this);
deleteButton.setOnClickListener(this);
insertButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bt_provider_add_book:
//Uri唯一标志,访问com.mrdouya.mycontenprovidertest应用下的Book表
uri = Uri.parse("content://com.mrdouya.mycontenprovidertest/Book");
//一般使用ContentValues将数据封装
ContentValues contentValues = new ContentValues();
contentValues.put("name","A Clash of Kings");
contentValues.put("author","Geoge Martin");
contentValues.put("pages",1040);
contentValues.put("prices",22.85);
//获取getContentResolver()实例调用insert方法将uri和contentValues传入,返回一个“被改变的Uri地址”
Uri newUri = getContentResolver().insert(uri,contentValues);
//获取newUri地址中被改变的id,用于后续删除、更新Book表中现在插入的这条数据
newId = newUri.getPathSegments().get(1);
Log.d(TAG,"insert book over!,newId = "+newId);
break;
case R.id.bt_provider_query_book:
//Uri唯一标志
uri = Uri.parse("content://com.mrdouya.mycontenprovidertest/Book");
//查询,返回cursor对象
Cursor cursor = getContentResolver().query(uri,null,null,null,null);
if(cursor != null){
while (cursor.moveToNext()){
String name = cursor.getString(cursor.getColumnIndex("name"));
String author = cursor.getString(cursor.getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
double prices = cursor.getDouble(cursor.getColumnIndex("prices"));
Log.d(TAG,"query book name:"+name+" ,author : "+author+" ,pages :"+pages+" ,prices :"+prices);
}
cursor.close();
}
Log.d(TAG,"query book over!");
break;
case R.id.bt_provider_update_book:
//Uri唯一标志+newId上面插入的一条记录的id
uri = Uri.parse("content://com.mrdouya.mycontenprovidertest/Book/"+newId);
//添加数据
ContentValues values = new ContentValues();
values.put("name","A Storm of Swords");
values.put("pages",1216);
values.put("prices",24.05);
//更新
getContentResolver().update(uri,values,null,null);
Log.d(TAG,"update book over!");
break;
case R.id.bt_provider_delete_book:
//删除
uri= Uri.parse("content://com.mrdouya.mycontenprovidertest/Book/"+newId);
getContentResolver().delete(uri,null,null);
Log.d(TAG,"delete book over!");
break;
}
}
}
执行插入、查询、更新、删除、更新的Log如下:
2019-09-04 13:58:56.990 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: insert book over!,newId = 1
2019-09-04 13:59:04.050 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book name:A Clash of Kings ,author : Geoge Martin ,pages :1040 ,prices :22.85
2019-09-04 13:59:04.055 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book over!
2019-09-04 13:59:15.941 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: update book over!
2019-09-04 13:59:19.158 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book name:A Storm of Swords ,author : Geoge Martin ,pages :1216 ,prices :24.05
2019-09-04 13:59:19.162 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book over!
2019-09-04 13:59:31.015 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: delete book over!
2019-09-04 13:59:34.697 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book over!
2.创建ContentProvider提供接口
1)需要创建数据库,提供可操作的表或数据,如第二节第4点
2)创建ContentProvider,提供可访问的Uri,如下:
在Androidmanifest.xml中声明,ContentProvider是四大组件之一,需要注意android:authorities属性表明该内容提供器的权限
<provider
android:name="com.mrdouya.mycontenprovidertest.MyProvider"
android:authorities="com.mrdouya.mycontenprovidertest"
/>
自定义的ContentProvider:
package com.mrdouya.mycontenprovidertest;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
public class MyProvider extends ContentProvider {
private final String TAG = "MrDouYa-MyProvider";
//定义int用于区分是访问哪张表的路径或者item
public static final int BOOK_DIR = 0;
public static final int BOOK_ITEM = 1;
public static final int CATEGORY_DIR = 2;
public static final int CATEGORY_ITEM = 3;
//与android:authorities一致,Uri唯一标志中的author
public static final String AUTHOR = "com.mrdouya.mycontenprovidertest";
//数据库实例
private MyDatabaseHelper myDatabaseHelper;
//UriMatcher可以实现Uri的匹配,先定义
private static UriMatcher uriMatcher;
/**
*初始化uriMatcher,并添加能访问这个ContentProvider的Uri唯一标志
*addURI()传入的是AUTHOR、path、自定义代码
*/
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHOR,"Book",BOOK_DIR);//表
uriMatcher.addURI(AUTHOR,"Book/#",BOOK_ITEM);//item
uriMatcher.addURI(AUTHOR,"Category",CATEGORY_DIR);
uriMatcher.addURI(AUTHOR,"Category/#",CATEGORY_ITEM);
}
/**
* 初始化内容提供器使用
* 完成对数据库的创建和升级等操作
*/
@Override
public boolean onCreate() {
myDatabaseHelper = new MyDatabaseHelper(getContext(),"BookStore.db",null,3);
Log.d(TAG,"onCreate");
return false;
}
/**
* 从内容提供器中查询数据,返回Cursor对象,uri为查询哪张表
* @param uri
* @param strings
* @param s
* @param strings1
* @param s1
* @return
*/
@Override
public Cursor query(Uri uri,String[] strings,String s,String[] strings1,String s1) {
Log.d(TAG,"query");
//getReadableDatabase()方法获取“读取SQLite”的实例,实际通过SQLiteDatabase的query方法去查询数据
SQLiteDatabase db = myDatabaseHelper.getReadableDatabase();
Cursor cursor = null;
//match()方法返回值用于匹配当前ContentProvider的Uri唯一标识
switch (uriMatcher.match(uri)){
case BOOK_DIR :
cursor = db.query("Book",strings,s,strings1,null,null,s1);
break;
case BOOK_ITEM :
//如果访问item,就获取对应item的id
//getPathSegments()会获取Uri中author之后的部分,并以“/”分割字符放入字符串列表,列表第一列“0”为路径,第二列“1”为id
String bookId = uri.getPathSegments().get(1);
cursor = db.query("Book",strings,"id = ?",new String[]{ bookId },null,null,s1);
break;
case CATEGORY_DIR :
cursor = db.query("Category",strings,s,strings1,null,null,s1);
break;
case CATEGORY_ITEM :
String categoryId = uri.getPathSegments().get(1);
cursor = db.query("Category",strings,"id = ?",new String[]{ categoryId },null,null,s1);
break;
default:
break;
}
return cursor;
}
/**
* 更新已有的数据,更据参数约束,返回值为受影响的行数
* @param uri
* @param contentValues
* @param s
* @param strings
* @return
*/
@Override
public int update(Uri uri,ContentValues contentValues,String s,String[] strings) {
Log.d(TAG,"update");
SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
int updatedRows = 0;
switch (uriMatcher.match(uri)){
case BOOK_DIR :
updatedRows = db.update("Book",contentValues,s,strings);
break;
case BOOK_ITEM :
String bookId = uri.getPathSegments().get(1);
updatedRows = db.update("Book",contentValues,"id = ?",new String[]{ bookId });
break;
case CATEGORY_DIR :
updatedRows = db.update("Category",contentValues,s,strings);
break;
case CATEGORY_ITEM :
String categoryId = uri.getPathSegments().get(1);
updatedRows = db.update("Category",contentValues,"id = ?",new String[]{ categoryId });
break;
default:
break;
}
return updatedRows;
}
/**
* 向内容提供器添加一条数据,返回一个用于表示这条新记录的URI
* @param uri
* @param contentValues
* @return
*/
@Override
public Uri insert(Uri uri,ContentValues contentValues) {
Log.d(TAG,"insert");
SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
Uri uriReturn = null;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
case BOOK_ITEM:
//获取SQLite增加数据后返回item的id
long newBookId = db.insert("Book",null,contentValues);
//将id添加到Uri唯一标志上,返回一个新Uri唯一标志
uriReturn = Uri.parse("content://"+AUTHOR+"/Book/"+newBookId);
break;
case CATEGORY_DIR:
case CATEGORY_ITEM:
long newCategoryId = db.insert("Category",null,contentValues);
uriReturn = Uri.parse("content://"+AUTHOR+"/Category/"+newCategoryId);
break;
}
return uriReturn;
}
/**
* 删除一条数据,返回值为被删除的行数
* @param uri
* @param s
* @param strings
* @return
*/
@Override
public int delete(Uri uri,String s,String[] strings) {
Log.d(TAG,"delete");
SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
int deleteRows = 0;
switch (uriMatcher.match(uri)){
case BOOK_DIR :
deleteRows = db.delete("Book",s,strings);
break;
case BOOK_ITEM :
String bookId = uri.getPathSegments().get(1);
deleteRows = db.delete("Book","id = ?",new String[]{ bookId });
break;
case CATEGORY_DIR :
deleteRows = db.delete("Category",s,strings);
break;
case CATEGORY_ITEM :
String categoryId = uri.getPathSegments().get(1);
deleteRows = db.delete("Category","id = ?",new String[]{ categoryId });
break;
default:
break;
}
return deleteRows;
}
/**
* 更具传入的Uri的返回对应的MIME类型
* @param uri
* @return
*/
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)){
case BOOK_DIR :
return "vnd.android.cursor.dir/vnd.com.mrdouya.mycontenprovidertest.Book";
case BOOK_ITEM :
return "vnd.android.cursor.item/vnd.com.mrdouya.mycontenprovidertest.Book";
case CATEGORY_DIR :
return "vnd.android.cursor.dir/vnd.com.mrdouya.mycontenprovidertest.Category";
case CATEGORY_ITEM :
return "vnd.android.cursor.item/vnd.com.mrdouya.mycontenprovidertest.Category";
}
return null;
}
}
3)访问验证
其实在本节第一小点访问的就是自定义的contentProvider,我将测试类运行在单独的线程中,去访问自定义的contentProvider这与两个应用资源共享效果是一致的,声明如下:
<activity android:name=".MyProviderTest"
android:process=":providertest">
</activity>
注意:四大组件均支持process属性,但不能轻易使用的时候,主要有一下几点权衡
优点:
- 1)分散应用内存,Android应用的内存大小是有规定的
- 2)应用的模块化,大型应用
- 3)守护线程
- 4)子线程、主线程的异步
缺点:
- 1)静态属性、单列模式失效
- 2)SheredPerference安全性降低
- 3)锁失效
- 4)appliction多次创建
应用私有进程:
<activity android:name=".MyProviderTest"
android:process=":providertest">
</activity>
应用公共进程:
<!--process的属性字段必须包含符号“.”-->
<activity android:name=".MyProviderTest"
android:process="com.providertest">
</activity>
参考:
书籍:《第一行代码》、《Android开发与艺术探索》
Process属性:https://blog.csdn.net/lixpjita39/article/details/77435156