Content Provider知识点总结
前言
我努力的写完前半段后,正准备开始写后半段,发现了一个痛苦的事实,如果不懂SQLite的内容的话,contentProvider就太过于抽象了,拿不出实例。所以最好是先看过SQLite的内容之后,再来学习有关Content Provider的知识。有关SQLite的些许知识可以看这里。
1.定义
Android系统用于让不同的应用程序之间共享数据的接口,就是Content Provider(内容提供者)。
- 如果一个应用程序有设置这样的外部访问接口,那么任何其他的应用都可以访问到这一部分的内容。比如说Android系统自带的联系人,相册,短信之类,都会带有这样的接口。
2.访问其他应用的程序(ContentResolver)
如果想要访问其他应用的数据,需要使用ContentResolver。
-
在当前的Context环境中获取ContentResolver就可以获得其实例。这个是ContextWrapper类中的方法,任何继承ContextWrapper类的类都会自带这个方法(比如Activity)。
public class MainActivity extends AppCompatActivity { ContentResolver contentResolver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //获取contentResolver的实例 contentResolver = this.getContentResolver(); } }
-
对于ContentResolver而言,它能够对访问到的数据进行CRUD操作(Create, Retrieve, Update, Delete,也就是我们常说的增删改查)。这类似对数据库进行操作,但是还是有一些不同的地方。
-
ContentResolver不同于数据库,它是要访问其他应用的数据,不能单纯的获取数据库名和表名,所以ContentResolver是通过URI来代替从应用名到数据库到表的路径检索。
uri有两个参数,authority和path,authority对应应用程序,一般是应用程序的包名(比如QQ的包名就是com.tencent.mobileqq)加上’.provider’;path用来区分应用程序内部不同的表名。具体写法:authority/path。Uri uri = Uri.parse("content://com.example.contentproviderdemo.provider/table1"); //如果你想特别希望访问这个表内的第X行时,可以这么写,X是这个表内的id号,一个数字。 Uri uri = Uri.parse("content://com.example.contentproviderdemo.provider/table1/X");
-
接下来就可以获取数据了,获取数据需要用到query方法,这个方法类似SQLite的query方法,查询完成之后返回的是一个Cursor对象。
Cursor cursor = contentResolver.query( uri, projection, selection, selectionArgs, sortOrder);
参数 对应SQL部分 备注 uri from table_1 查询某个应用中的某个表 projection select column1, column2 查询指定的列名,如果为空就是选择所有列(相当于*) selection where column = value 约束条件,如果为空就是没有约束条件 selectionArgs - 为where中的占位符提供具体的值,为空就是无具体值,具体内容看8 sortOrder order by column1, column2 指定结果的排列方式,为空就是默认排列 SQL:
select column1, column2 from table_1 where column = value order by column1, column2 -
当访问成功之后,就会获得一个Cursor对象,这个对象就包含了我们所查询到的数据。从中取出数据即可。
if (cursor != null){ //确认是否查询成功 while(cursor.moveToNext()){ //遍历整个查询结果 String column1 = cursor.getString(cursor.getColumnIndex("column1")); //获取查询结果内的数据 int column2 = cursor.getInt(cursor.getColumnIndex("column2")); } }
-
用ContentResolver也可以添加新的数据到其他应用的表内,需要使用到insert方法。
ContentValues values = new ContentValues(); values.put("column1", "String"); values.put("column2", 1); contentResolver.insert(uri, values);
SQL:
insert into table_1 values (‘String’, 1) -
当然也可以修改内容,使用update()方法。
ContentValues values1 = new ContentValues(); values1.put("column1", "text"); values1.put("column2", 2); contentResolver.update(uri, values, "column1 = ? and column2 = ?", new String[]{"String", "1"}); //下面是update方法的具体参数 contentResolver.update( uri, contentValues, where, selectionArgs );
update方法的几个参数貌似不太好理解,前两个参数还是和之前一样,是表地址和添加值。第三个参数是where的说明语句,但是其中有两个‘?’,这个’?'就是占位符,而第四个参数就是对这两个占位符提供具体值。这样可能还是不太清楚,那么我们看看SQL语句是怎么写的就明白了。
SQL:
update table_1 set column1 = ‘text’, column2 = 2 where column1 = ‘String’ and column2 = 1
顺带一提,query的selectionArgs参数也是和这边一样,为selection中的‘?’提供具体值。 -
最后是删除语句,用delete方法实现。
contentResolver.delete(uri, "column1 = ? and column2 = ?", new String[]{"text", "2"}); //下面是delete方法具体参数 contentResolver.delete( uri, where, selectionArgs );
SQL:
delete from table_1 where column1 = ‘text’ and column2 = 2
3.让其他程序访问数据(ContentProvider)
接下来,如果想要让其他程序访问本程序的数据,就需要ContentProvider,这里我会拿一个简单的带有两个表的小项目来实际操作。地址:https://github.com/wodongx123/SQLiteDemo
这个项目就是单纯的内部一个数据库BookStore.db,其中带有两个表Book和People,没有任何其他功能。
-
想要让其他程序访问的话,要自建一个继承ContentProvider抽象类的类,然后为了消除错误添加其中必加的方法。
public class MyContentProvider extends ContentProvider { @Override public boolean onCreate() { return false; } @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) { return null; } @Nullable @Override public String getType(@NonNull Uri uri) { return null; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) { return null; } @Override public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) { return 0; } @Override public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) { return 0; } }
看上去很多,其实现阶段还没有内容,而且其中的增删改查四个方法还似曾相识,唯一不同的就是多了一个onCreate()和getType()。
onCreate() 初始化ContentProvider的时候调用,一般在这里初始化我们要用的数据库,如果返回true就表示初始化成功,返回false就是初始化失败了。
query(),insert(),delete(),update()几个参数发现和ContentResolver的方法参数一样,这个事实上就是从其他程序ContentResolver原原本本传过来的值,从其他程序传过来的值我们这边经过处理后,再把得到的数据返回回去,这样跨应用程序的数据交换就完成了。
getType() 这个方法是对其他方法中传入的Uri的参数返回对应的MIME类型。 -
每个Provider都要在AndroidManifest.xml中配置了才能生效,如果你是new -> other -> Content Provider创建的类的话,Android Studio会自动帮你创建配置,如果是像我一样new class创建的话,就要手动配置了。
<application …………………… <provider android:authorities="com.example.sqlitedemo.provider" android:name=".MyContentProvider" android:exported="true" android:enabled="true"/> …………………… </application>
authorities就是之前说过的应用地址,一般是包名加上’.provider’。
exported = true表示可以被其他应用发现。
enabled = true表示可以使用。 -
在onCreate()中初始化我们的数据库,我这边就初始化BookStore.db。
public class MyContentProvider extends ContentProvider { private MyDataBaseHelper myDataBaseHelper; private SQLiteDatabase sqLiteDatabase; @Override public boolean onCreate() { myDataBaseHelper = new MyDataBaseHelper(getContext(), "BookStore.db", null, 2); sqLiteDatabase = myDataBaseHelper.getWritableDatabase(); return true; } }
-
接下来对Uri进行解析,先再看看Uri的写法:
content://com.example.contentproviderdemo.provider/table1
content://com.example.contentproviderdemo.provider/table1/X
那么,前面的authority部分不会变动,因为已经限定是这个应用了,唯一变动的就是后面的path已经指定的行号。
这个时候就需要使用UriMatcher这个类,它会自动的根据不同的Uri来匹配不同的值。public class MyContentProvider extends ContentProvider { public static final int TABLE_BOOK = 0; //使用静态变量来代替两个表和表内数据 public static final int ITEM_BOOK = 1; public static final int TABLE_PEOPLE = 2; public static final int ITEM_PEOPLE = 3; private static UriMatcher uriMatcher; //声明一个uriMathcher static { uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); //addURI有三个参数,authority和path已经对应的值 uriMatcher.addURI("com.example.sqlitedemo.provider", "book", TABLE_BOOK); uriMatcher.addURI("com.example.sqlitedemo.provider", "book/#", ITEM_BOOK); uriMatcher.addURI("com.example.sqlitedemo.provider", "people", TABLE_PEOPLE); uriMatcher.addURI("com.example.sqlitedemo.provider", "people/#", ITEM_PEOPLE); } …………………… }
Uri中有一个叫做通配符的东西,简单来说是这样。
#:匹配任意长度的数字。
*:匹配任意长度的任意字符。
content://com.example.contentproviderdemo.provider/table1/#表示table1内的任意一条数据。 -
接下来实现具体的增删改查方法,根据urimatcher匹配出不同的内容进行不同的操作即可,这里就以query和insert为例,代码看上去很多,其实非常简单。
public class MyContentProvider extends ContentProvider { private MyDataBaseHelper myDataBaseHelper; private SQLiteDatabase sqLiteDatabase; public static final int TABLE_BOOK = 0; //使用静态变量来代替两个表和表内数据 public static final int ITEM_BOOK = 1; public static final int TABLE_PEOPLE = 2; public static final int ITEM_PEOPLE = 3; private static UriMatcher uriMatcher; //声明一个uriMathcher …………………… @Nullable @Override public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) { //虽然加密过了,但是参数就是Resolver的那几个参数 switch (uriMatcher.match(uri)){ case TABLE_BOOK: case ITEM_BOOK: //为了方便就把查询某一条的数据合并了 return sqLiteDatabase.query("book", strings, s, strings1, null, null, s1); case TABLE_PEOPLE: case ITEM_PEOPLE: return sqLiteDatabase.query("People", strings, s, strings1, null, null, s1); } return null; } @Nullable @Override public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) { //返回的是一个uri,通过这个uri告诉对方插入数据是否成功,返回为null就是失败了 Uri uriReturn = null; switch (uriMatcher.match(uri)){ case TABLE_BOOK: long bookId = sqLiteDatabase.insert("book", null, contentValues); uriReturn = Uri.parse("content://com.example.sqlitedemo.provider/book/" + bookId); break; case TABLE_PEOPLE: long peopleId = sqLiteDatabase.insert("People", null, contentValues); uriReturn = Uri.parse("content://com.example.sqlitedemo.provider/book/" + peopleId); break; } return uriReturn; } …………………… }
-
最后是关于getType()这个方法,会根据传入的URI值,返回对应的MIME,MIME的话Android对此有做规定,我们按照规定来写就好。
- vnd开头。
- 如果url以路径结尾,vnd接上android.cursor.dir/;如果以id结尾,接android.cursor.item/。
- 最后加上vnd.authority.path。
public String getType(@NonNull Uri uri) { switch (uriMatcher.match(uri)){ case TABLE_BOOK: return "vnd.android.cursor.dir/vnd.com.example.sqlitedemo.provider.book"; case ITEM_BOOK: return "vnd.android.cursor.item/vnd.com.example.sqlitedemo.provider.book"; case TABLE_PEOPLE: return "vnd.android.cursor.dir/vnd.com.example.sqlitedemo.provider.people"; case ITEM_PEOPLE: return "vnd.android.cursor.item/vnd.com.example.sqlitedemo.provider.people"; } return null; }
至于gettype这个方法到底有什么用,可以参考这篇,我一时间确实没看懂。
ContentProvider数据库共享之——MIME类型与getType()
https://blog.csdn.net/harvic880925/article/details/44620851
4.内容观察者(ContentObserver)
ContentObserver是输出数据的一方(也就是ContentProvider的一方)用于监控数据变动所设置的监视机制。目的是观察特定Uri引起的数据库变化,进而做出相应的措施。
参考材料
第一行代码——Android(第2版)
p254 - p265