ContentProvider
ContentProvider:内容提供者
应用程序能够将它们的数据保存到文件、SQLite数据库中,甚至是任何有效的设备中。当需要将当前应用数据与其它应用共享时,ContentProvider类实现了一组标准方法,从而能够让其它的应用保存或读取此ContentProvider处理的各种数据类型。
数据共享
为了在应用程序之间共享数据,Android提供了ContentProvider,这是一种不同应用之间共享数据的标准API:
① 当应用希望提供数据时,就提供ContentProvider。
② 其它应用通过ContentResolver来操作。
注意:
ContentProvider需要在AndroidManifest.xml中注册。
一旦应用提供CP,不论应用启动与否,都可被操作。
数据共享标准
ContentProvider是应用间共享数据的标准,它以Uri的形式对外提供数据,其他应用程序使用ContentResolver根据Uri去访问指定数据。
ContentProvider是单例模式的,多个ContentResolver请求数据时,是委托给同一个ContentProvider对象来操作的。
Uri简介
Uri的结构和网站URL命名规则类似:
URL举例:http://www.baidu.com/index.php
Uri举例:content://org.edu.provider/words
① content://:这部分是Android固定的
② org.edu.provider:这部分是ContentProvider中的authority
③ words:资源部分,根据资源不同而有所不同
Uri可以表达的功能有很多.
content://org.edu.provider/words/2
content://org.edu.provider/words/2/name
content://org.edu.provider/words
通过Uri提供的静态方法parse()来实现将字符串转换为Uri。
Uri uri = Uri.parse(“content://org.edu.p…/words”)
操作Uri的工具类
ContentProvider不对Uri做判断,Uri工具类可协助ContentProvider操作Uri。
UriMatcher工具类:
① UriMatcher(UriMatcher.NO_MATCH):构造方法。
② void addURI(String authority,String path,int code):用于向UriMatcher对象注册Uri。
③ int match(Uri uri):根据注册的Uri判断指定Uri对应的标识码,找不到则返回-1。
authority是外部访问此ContentProvider时的唯一地址
自定义ContentProvider步骤:
1)自定义ContentProvider,重写增删改查方法
方式一: 自定义一个类,继承ContentProvider,重写增、删、改、查的方法;在manifest文件中注册自定义的ContentProvider
方式二:自定义一个ContentProvider,重写增、删、改、查的方法(这种方式不需要自己手动注册)
2)自定义一个数据库管理类【MySQLiteHelper】,继承SQLiteOpenHelper,一般需要实现onCreate()方法和onUpgrade()方法
onCreate()方法:第一次创建数据库的时候调用,一般用来生成表结构和初始化数据,只在数据库第一次创建时才会执行
onUpgrade()方法:数据库版本更新的回调
package com.example.ch08providerdemo;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import androidx.annotation.Nullable;
public class MySQLiteHelper extends SQLiteOpenHelper {
public MySQLiteHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
//第一次使用时创建数据库表
String table = "create table StarTB(id integer primary key autoincrement,name varchar(10),hobby varchar(50))";
sqLiteDatabase.execSQL(table);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
3)在自定义的ContentProvider中定义自定义数据库管理类的对象属性,并借助此方法实现onCreate()方法
//定义数据库管理类的对象属性
private MySQLiteHelper dbHelper;
@Override
public boolean onCreate() {
dbHelper = new MySQLiteHelper(getContext(),//context参数
"StarsDB.db",//数据库名.db
null,//游标工厂
1);//数据库版本
return true;
}
4)在自定义的ContentProvider中定义UriMatcher属性(处理Uri),并借此实现getType()方法,根据参数的形式来返回相应的MIME类型。
//定义UriMatcher属性
private UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
@Override
public String getType(Uri uri) {
String type = null;
//根据参数的形式(批量/单一 操作)来返回相应的MIME类型
switch (matcher.match(uri)){
case STARS://表示批量操作数据
type = "lrf.cursor.dir/net.onest.lrf.provider";
break;
case STAR://表示单一数据操作
type = "lrf.cursor.item/net.onest.lrf.provider";
break;
default:
throw new UnsupportedOperationException("Not yet implemented");
}
return type;
}
①如果uri操作的是多条数据,则MIME类型以vnd.android.cursor.dir/开头;
例:“lrf.cursor.dir/net.onest.lrf.provider”
②如果uri操作的是单条数据,则MIME类型以vnd.android.cursor.item/开头。
例:“lrf.cursor.item/net.onest.lrf.provider”
5)注册Uri并关联Uri与标号
//定义两个标号
private static final int STARS = 1;
private static final int STAR = 2;
//注册两个Uri并关联Uri与标号
static {
// matcher.addURI(authority部分,资源部分,标号)
matcher.addURI(MyStar.AUTHORITY,"/stars",STARS);
matcher.addURI(MyStar.AUTHORITY,"/star/#",STAR);
}
6)实现增、删、改、查方法:
(1)insert方法:
① 判断是批量操作还是单一操作
② 获取可写的数据库对象
③ 执行插入操作(直接调用数据库对象的insert方法,返回值为long类型)
④ 如果插入成功,则拼接Uri和被插入数据的id
@Override
public Uri insert(Uri uri, ContentValues values) {
//实现插入数据操作
//1.判断是批量操作还是单一操作:matcher.match(uri[参数])
switch (matcher.match(uri)){
case STAR:
case STARS:
//2.获取可写的数据库对象
SQLiteDatabase db = dbHelper.getWritableDatabase();
//3.执行插入操作(直接调用数据库对象的insert方法,返回值为long类型)
long rowId = db.insert("StarTB",//数据库表名
null,//列
values);//需要插入的数据,类型为ContentValues
if(rowId > 0){
//插入成功
//通知数据改变
getContext().getContentResolver().notifyChange(uri,null);
//拼接Uri和被插入数据的id
return ContentUris.withAppendedId(uri,rowId);
}else{//插入失败
return null;
}
default:
throw new UnsupportedOperationException("Not yet implemented");
}
}
(2)delete方法:
① 获取可写的数据库对象
② 定义一个int类型的变量
③ 判断是批量操作还是单一操作
如果是批量操作,则直接调用数据库对象的delete方法,返回值为int类型
如果是单一操作,先解析Uri中的id,重新拼接where条件后再执行数据库的删除操作
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
//获取可写的数据库对象
SQLiteDatabase db = dbHelper.getWritableDatabase();
//定义一个int类型的变量
int n = 0;
//判断是批量操作还是单一操作:matcher.match(uri)//uri为参数
switch (matcher.match(uri)){
case STARS:
n = db.delete("StarTB",//数据库表名
selection,//参数,相当于where子句
selectionArgs);//占位符
break;
case STAR:
//解析Uri中的id
long id = ContentUris.parseId(uri);
//重新拼接where条件
String where = "id = " + id;
if(null != selection && !selection.equals("")){
where = "id = " + id + " and " + selection;
}
//执行数据库删除的操作
n = db.delete("StarTB",//数据库表名
where,//where条件
selectionArgs);//占位符
break;
default:
throw new UnsupportedOperationException("Not yet implemented");
}
if(n > 0){
//通知数据改变
getContext().getContentResolver().notifyChange(uri,null);
}
return n;
}
(3)update方法:
① 获取可写的数据库对象
② 定义一个int类型的变量
③ 判断是批量操作还是单一操作:
如果是批量操作,则直接调用数据库对象的update方法,返回值为int类型
如果是单一操作,先解析Uri中的id,重新拼接where条件后再执行数据库的更新操作
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
//获取可写的数据库对象
SQLiteDatabase db = dbHelper.getWritableDatabase();
//定义一个int类型的变量
int n = 0;
//判断是批量操作还是单一操作
switch (matcher.match(uri)){
case STARS:
n = db.update("StarTB",//数据库表名
values,//参数,需要更新的数据
selection,//参数,相当于where子句
selectionArgs);//占位符
break;
case STAR:
//解析Uri中的id
long id = ContentUris.parseId(uri);
//重新拼接where条件
String where = "id = " + id;
if(null != selection && !selection.equals("")){
where = "id = " + id + " and " + selection;
}
//执行数据库更新的操作
n = db.update("StarTB",//数据库表名
values,//参数,需要更新的数据
where,//where条件
selectionArgs);//占位符
break;
default:
throw new UnsupportedOperationException("Not yet implemented");
}
return n;
}
(4)query方法:
① 获取可读的数据库对象
② 定义一个Cursor变量
③ 判断是批量操作还是单一操作:
如果是批量操作,则直接调用数据库对象的update方法,返回值为Cursor类型;
如果是单一操作,先解析Uri中的id,重新拼接where条件后再执行数据库的查询操作。
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
//获取可读的数据库对象
SQLiteDatabase db = dbHelper.getReadableDatabase();
//定义一个Cursor变量
Cursor cursor = null;
//判断是批量操作还是单一操作
switch (matcher.match(uri)){
case STARS://批量查询操作
cursor = db.query("StarTB",//数据库表名
projection,//参数,查询要返回的列,一般设为null
selection,//参数,相当于where子句
selectionArgs,//占位符
null,//group by
null,//having
sortOrder);//order by
break;
case STAR://单一查询操作
//解析Uri中的id
long id = ContentUris.parseId(uri);
//重新拼接where条件
String where = "id=" + id + " and " + selection;
//获取数据库执行查询操作
cursor = db.query("StarTB",//数据库表名
projection,//参数,columns
where,//where条件
selectionArgs,//占位符group by],null[having],sortOrder[order by])
null,//group by
null,//having
sortOrder);//order by
break;
default:
throw new UnsupportedOperationException("Not yet implemented");
}
return cursor;
}
7)自定义一个类,继承ContentObserver类,重写onChange()方法用来监测数据变化
public class MyObserver extends ContentObserver {
/**
* Creates a content observer.
*
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public MyObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
if(selfChange){
Log.e("lrf","数据发生变化");
}
}
}
8)在需要监测数据变化的Activity中注册ContentObserver
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注册ContentObserver(监测改变)
getContentResolver().registerContentObserver(
Uri.parse("content://" + MyStar.AUTHORITY),
true,
new MyObserver(null)
);
}
}
9)创建一个新的工程来测试是否可以实现数据共享
① 创建ContentResolver对象,得到操作ContentProvider的Uri
//创建ContentResolver对象
ContentResolver resolver = getContentResolver();
//得到操作ContentProvider的Uri
Uri uri = Uri.parse("content://authority/资源部分");
② 插入一条数据:
//封装待插入的数据
ContentValues cv = new ContentValues();
cv.put("name","Bob");//key,value
cv.put("hobby","追剧");
//使用ContentResolver对象调用insert方法进行插入
Uri resultUri = resolver.insert(uri,cv);
//解析插入结果的id(long类型),当rowId > 0时,表明插入数据成功
long rowId = ContentUris.parseId(resultUri);
if(rowId > 0){
Toast.makeText(this,"插入成功",Toast.LENGTH_SHORT).show();
}
③查询数据:(返回的数据类型为Cursor)
//使用ContentResolver对象调用query方法进行查询,并用Cursor进行接收数据
Cursor result = resolver.query(uri,null,null,null,null);
//如果需要将查询到的结果显示在界面:
TextView tvStarts = findViewById(R.id.tv_stars);
//定义StringBuffer变量
StringBuffer buffer = new StringBuffer();
//遍历结果
while(result.moveToNext()){
String id = result.getString(result.getColumnIndex("id"));//列名
String name = result.getString(result.getColumnIndex("name"));//列名
String hobby = result.getString(result.getColumnIndex("hobby"));//列名
//将数据添加到StringBuffer变量中
buffer.append(id + " : " + name + " : " + hobby + "\n");
}
//将结果显示在界面
tvStarts.setText(buffer.toString());
④ 删除数据:
//使用ContentResolver对象调用delete方法进行删除,并用int类型的变量进行接收
//当 n > 0时,表明删除数据成功
Uri uriSingle = Uri.parse("content://net.onest.lrf.provider/star/1");
int n = resolver.delete(uri,null,null);
if(n > 0){
//删除成功
Toast.makeText(this,"删除成功" + n + "条数据",Toast.LENGTH_SHORT).show();
Log.e("lrf","删除成功" + n + "条数据");
}
⑤更新数据:
//封装待插入的数据
ContentValues cv1 = new ContentValues();
cv.put("hobby","唱歌");//key,value
//使用ContentResolver对象调用update方法进行更新,并用int类型的变量进行接收
//当 n > 0时,表明更新数据成功
int m = resolver.update(uriSingle,cv1,null,null);
if(m > 0){
//更新数据成功
Toast.makeText(this,"成功修改" + m + "条数据",Toast.LENGTH_LONG).show();
}
上述例子中用到的用预定义数据表的结构的Java Bean:MyStar
public final class MyStar {
//定义Authority
public static final String AUTHORITY = "net.onest.lrf.provider";
//定义内部表结构(列名)
public static final class MyStarTable implements BaseColumns{
//定义数据表的列名称
public final static String _ID = "id";
public final static String NAME = "name";
public final static String HOBBY = "hobby";
//定义两个Uri
public final static Uri STARS_CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/stars");
public final static Uri STAR_CONTENT_URI =
Uri.parse("content://"+AUTHORITY+"/star/#");//#可以代表任意的数字
}
}