第七章内容主讲内容提供器
一、内容提供器简介
虽然文件和 SharedPreferences 存储中提供了 MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE 这两种操作模式,用于给其他应用程序访问当前应用的数据,但这两种模式在 Android4.2版本中都已经被废弃了,因此实现跨程序数据共享的功能,就应该使用更安全可靠的内容提供器技术。
为何要将数据共享给其他程序呢?因为一些基础性的数据,可以让其他程序进行二次开发。比如电话簿,短信,媒体库等都实现了跨程序数据共享的功能。
内容提供器主要用于在不同的应用程序间实现数据共享的功能。它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问的数据的安全性。使用内容提供器是 Android 实现跨进程共享数据的标准方式。
不同于文件存储和 SharedPreferences 存储中的两种全局可读写操作模式,内容提供器可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险。
二、运行时权限
Android 开发团队在Android 6.0系统中引用了运行时权限这个功能,从而更好地保护了用户的安全和隐私。
以前的 Android 权限机制是只需要在 AndroidManifest.xml 文件中声明权限,当用户安装应用程序的时候,需要认可应用所申请的所有权限,如果用户不认可,那么就拒绝安装。
而运行时权限,就是说,用户不需要在安装软件的时候一次性授权所有申请的权限,而是可以在软件使用的过程中,再对某一项权限进行授权。比如说,相机应用申请了地理位置定位权限,就算用户拒绝了这个权限,用户还是能使用这个应用的其他功能,而不是像以前一样,直接无法安装应用了。
并不是所有的权限都需要在运行时申请,Android 将所有的权限归成了两类,一类是普通权限,一类是危险权限。普通权限指的是不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮我们进行授权,不需要用户再去手动操作了。而危险权限表示可能会触及到用户的隐私,或对设备的安全性造成影响的权限,比如,获取联系人信息,定位设备的地理位置等,对于这部分权限的申请,必须要用户手动点击授权才可以,否则程序就无法使用相应的功能。
其实危险权限就那么几个,除了危险权限以外,剩余的都是普通权限,下表列出了 Android 中所有的危险权限,共9组24个权限。
|--------------------------------|---------------------------|
| 权限组 | 权限名 |
|--------------------------------|---------------------------|
| | READ_CALENDAR |
| CALENDAR | |
| | WRITE_CALENDAR |
|--------------------------------|---------------------------|
| CAMERA | CAMERA |
|--------------------------------|---------------------------|
| | READ_CONTACTS |
| CONTACTS | WRITE_CONTACTS |
| | GET_ACCOUNTS |
|--------------------------------|---------------------------|
| | ACCESS_FINE_LOCATION |
| LOCATION | |
| | ACCESS_COARSE_LOCATION |
|--------------------------------|---------------------------|
| MICROPHONE | RECORD_AUDIO |
|--------------------------------|---------------------------|
| | READ_PHONE_STATE |
| | CALL_PHONE |
| | READ_CALL_LOG |
| PHONE | WRITE_CALL_LOG |
| | ADD_VOICEMAIL |
| | USE_SIP |
| | PROCESS_OUTGOING_CALLS |
|--------------------------------|---------------------------|
| SENSORS | BODY_SENSORS |
|--------------------------------|---------------------------|
| | SEND_SMS |
| | RECEIVE_SMS |
| SMS | READ_SMS |
| | RECEIVE_WAP_PUSH |
| | RECEIVE_MMS |
|--------------------------------|---------------------------|
| | READ_EXTERNAL_STORAGE |
| STORAGE | |
| | WRITE_EXTERNAL_STORAGE |
|--------------------------------|---------------------------|
如果属于上述表格中的权限,就需要进行运行时权限处理,如果不属于上述表格中的权限,只需要在 AndroidManifest.xml 文件中添加权限声明就可以了。
注意:表格中每个危险权限都属于一个权限组,我们在进行运行时权限处理时使用的是权限名,但是一旦用户同意授权了,那么该权限所对应的权限组中的所有其他权限也会同时被授权。访问 http://developer.android.com/reference/android/Manifest.permiss可以查看 Android 系统中完整的权限列表。
假如我们要写一个拨打电话的功能,Android6.0系统之前可以这么写
/*
指定 Intent 的 action 为 ACTION_CALL,这是一个系统内置的
打电话的动作,然后在 data 部分指定了协议是 tel,号码是10086
注:Intent.ACTION_DIAL:表示打开拨号界面,这个不需要声明权限
Intent.ACTION_CALL:表示直接拨打电话,必须声明权限
*/
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
然后在清单文件中声明权限
如果将上述代码运行在 Android6.0及其以上的系统中,我们会在错误日志中找到"Permission Denial"字样,意思是由于权限被禁止所导致的。修改代码:
findViewById(R.id.tv_call).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
/*
判断用户是否已经授权了 CALL_PHONE 权限
GRANTED : 授权
这里 checkSelfPermission()方法接受两个参数:
参1:Context,参2:具体的权限名
*/
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {//未授权
/*
向用户申请授权,requestPermission()方法接受三个参数:
参1:Activity 的实例对象
参2:是一个 String 数组,里面包含了要申请的权限名
参3:是请求码,传入一个唯一值即可,这里传入了1
*/
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.CALL_PHONE}, 1);
} else {//已经授权了,就直接去拨打电话
call();
}
}
});
/**
* 指定 Intent 的 action 为 ACTION_CALL,这是一个系统内置的
* 打电话的动作,然后在 data 部分指定了协议是 tel,号码是10086
* 注:Intent.ACTION_DIAL:表示打开拨号界面,这个不需要声明权限
* Intent.ACTION_CALL:表示直接拨打电话,必须声明权限
*/
private void call() {
try {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
} catch (SecurityException e) {
/*
这里如果没有 try catch,startActivity 会有红线,
AS建议我们用运行时权限,因为我们已经写在其他方法
里面,所以用 try catch(注意是SecurityException)
将其包裹起来,去掉红线
*/
e.printStackTrace();
}
}
/**
* 调用完 requestPermission 方法后,会弹出一个对话框,不管用户点击同意还是拒绝,
* 都会回调到 onRequestPermissionResult 方法中。
* @param requestCode 授权请求码
* @param permissions 权限名
* @param grantResults 包含了授权的结果
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
//表明有授权结果并且授权结果是用户授权了请求授权的权限
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
call();
} else {//表明用户没有同意请求授权的权限
Toast.makeText(this, "用户拒绝(denied)了请求", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
三、访问其他应用中的数据
内容提供器的用法一般有两种,一种是使用现有的内容提供器来读取和操作相应程序中的数据;另一种是创建自己的内容提供器给我们程序的数据提供外部访问接口。
第一种访问其他应用程序的内容提供器
1.ContentResolver 的基本用法
对于每个应用来说,想要访问内容提供器中共享的数据,就一定要借助 ContentResolver 类,可以通过 Context 中的 getContentResolver()方法获取到该类的实例。ContentResolver 中提供了一系列的方法用于对数据进行 CRUD操作,比如:insert()、update()、delete()、query().
不同于 SQLiteDatabase,ContentResolver 中的增删改查方法不需要接收表名参数,而是使用一个 Uri 参数代替,这个参数叫做内容URI
1.1 内容 URI:
内容 URI 给内容提供器中的数据建立了唯一标识符,它主要由两部分组成:authority 和 path。
1.1.1 authority:
authority 是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用包名来进行命名,比如:如果程序的包名是com.example.app,那么 authority 就可以命名为 com.example.app.provider
1.1.2 path:
path 是用于对同一应用程序中不同的表做区分的,通常会添加到 authority 的后面,比如某个程序的数据库里存在两张表:table1和 table2,这时可以将 path 分别命名为 /table1和/table2
最后将 authority 和 path 进行组合,然后在组合后的字符串头部加上协议声明,内容 URI 就变成了:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
得到了内容 URI 字符串后,还要将它解析成 Uri 对象才能作为参数传入:
Uri uri = Uri.parse("content://com.example.app.proivder/table1");
使用该 Uri 对象查询 table1表中的数据:
/**
* uri 对应 from table_name, 指定查询某个表
* projection 对应 select column1,column2 指定查询哪几列
* selection 对应 shere column = value 指定 where 的约束条件
* selectionArgs 指定 where 中占位符具体的值
* orderBy 对应 order by column1,column2 指定查询结果的排序方式
*/
Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder);
增加一条数据:
ContentValues contentValues = new ContentValues();
contentValues.put("column1","text");
contentValues.put("column2",1);
getContentResolver().insert(uri,contentValues);
更新这条数据:
ContentValues contentValues = new ContentValues();
contentValues.put("column1","");
getContentResolver().update(uri,contentValues,"column = ? and column2 = ?",new String[]{"text","1"});
删除这条数据:
getContentResolver().delete(uri,"column2 = ?",new String[]{"1"});
1.1.3 读取系统联系人(这里只有重点代码):
//读取联系人
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
} else {
readContacts();
}
/**
* 读取联系人
*/
private void readContacts() {
Cursor cursor = null;
try {
/*
这里的内容 URI 用 ContactsContract.CommonDataKinds.Phone.CONTENT_URI 来代替,
因为ContactsContract.CommonDataKinds.Phone 类已经帮我们做好了封装,提供了一个
CONTENT_URI 常量,而这个常量就是使用 Uri.parse()方法解析出来的结果
*/
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, null, null, null);
if (cursor != null) {
while (cursor.moveToNext()) {
/*
获取联系人姓名,这里联系人姓名这一列对应的常量是:
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
获取联系人手机号,这里联系人手机号一列对应的常量是:
ContactsContract.CommonDataKinds.Phone.NUMBER
*/
String displayName = cursor.getString(
cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String number = cursor.getString(
cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.d(TAG, "readContacts: displayName = " + displayName + " --- number = " + number);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
//千万不要忘记关闭 cursor
cursor.close();
}
}
}
onRequestPermissionsResult()方法的代码就不写了,区别不大。
注意:别忘了在 AndroidMainifest.xml 文件中声明权限
第二种创建自己的内容提供器
1.创建内容提供器:
新建一个类继承自 ContentProvider,需要重写6个抽象方法:
/**
* 自定义内容提供器
*/
public class MyContentProvider extends ContentProvider{
/**
* 初始化内容提供器的时候调用,通常会在这里完成数据库的创建和升级
*
* 注意:只有当 contentResolver 尝试访问我们程序中的数据时,内容提供器才会被初始化
* @return true 表示内容提供器初始化成功,false 表示失败
*/
@Override
public boolean onCreate() {
return false;
}
/**
* 从内容提供器中查询数据
* @param uri 查询哪张表
* @param projection 查询哪几列
* @param selection where 后面的约束条件
* @param selectionArgs where 后面占位符的具体的值
* @param sortOrder 查询结果的排序
* @return 查询结果存放在 Cursor 对象中
*/
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
/**
* 向内容提供器中添加一条数据
* @param uri 查询哪张表
* @param contentValues 待添加的数据保存在 ContentValues 中
* @return 返回用于表示这条新纪录的 URI(自己重新拼接的字符串解析成 Uri 对象)
*/
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
return null;
}
/**
* 更新内容提供器中已有的数据
* @param uri 查询哪张表
* @param contentValues 要更新的数据保存在 ContentValues 中
* @param selection where 后面的约束条件
* @param selectionArgs where 后面占位符的具体的值
* @return 受影响的行数(更新了n条数据,就返回n)
*/
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues,
@Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
/**
* 从内容提供器中删除数据
* @param uri 查询哪张表
* @param selection where 后面的约束条件
* @param selectionArgs where 后面占位符具体的值
* @return 被删除了 n 行就返回 n
*/
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
/**
* 根据传入的内容 URI 来返回相应的 MIME 类型
*/
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
}
非常非常非常重要的内容
一个标准的内容 URI 是这样写的:
content://com.example.app.provider/table1
这就表示调用方期望访问 com.example.app 应用的 table1表中的数据,除此之外,还可以在后面加上一个 id,如下所示:
content://com.example.app.provider/table1/1
这就表示调用方期望访问 com.example.app 应用的 table1表中的 id 为1的数据。内容 URI 的格式主要就只有以上两种,以路径结尾表示期望访问表中的所有数据,以 id 结尾就表示期望访问该表中拥有相应 id 的数据,因此我们可以使用通配符的方式来匹配这两种内容 URI:
* 表示匹配任意长度的任意字符
# 表示匹配任意长度的数字
所以,能够匹配任意表的内容 URI 格式就可以写成:
content://com.example.app.provider/*
能够匹配 table1表中的任意一行数据的内容 URI 格式就可以写成:
content://com.example.app.provider/table1/#
接着我们再借助UriMatcher这个类就可以轻松实现匹配内容 URI 的功能。UriMatcher 中提供了一个 addURI()方法,这个方法接收3个参数,可以分别把 authority、path 和一个自定义代码传进去。当调用 UriMatcher 的 match()方法时,就可以将一个 Uri 对象传入,返回值是某个能匹配这个 Uri 对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。
不明白?上代码,我们就先以 query()方法做一个示范
public class MyContentProvider extends ContentProvider{
/*
下面这几个常量就是刚才所说的自定义代码
*/
public static final int TABLE1_DIR = 0;
public static final int TABLE1_ITEM = 1;
public static final int TABLE2_DIR = 2;
public static final int TABLE2_ITEM = 3;
private static UriMatcher uriMatcher;
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
/*
调用 addURI 方法将期望匹配的内容 URI 格式传递进去,这里传入的路径参数可以使用通配符
*/
uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR);
uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM);
uriMatcher.addURI("com.example.app.provider","table2",TABLE2_DIR);
uriMatcher.addURI("com.example.app.provider","table2/#",TABLE2_ITEM);
}
/**
* 从内容提供器中查询数据
* @param uri 查询哪张表
* @param projection 查询哪几列
* @param selection where 后面的约束条件
* @param selectionArgs where 后面占位符的具体的值
* @param sortOrder 查询结果的排序
* @return 查询结果存放在 Cursor 对象中
*/
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
/*
当 UriMatcher 中某个内容 URI 成功匹配到了该 Uri 对象,
则会返回对应的自定义代码
*/
switch (uriMatcher.match(uri)){
case TABLE1_DIR:
//查询 table1 表中的所有数据
break;
case TABLE1_ITEM:
//查询 table1 表中单条数据
break;
case TABLE2_DIR:
//查询 table2 表中的所有数据
break;
case TABLE2_ITEM:
//查询 table2 表中的单条数据
break;
default:
break;
}
return null;
}
...
}
getType()方法是获取内容 URI 对象所对应的 MIME 类型,一个内容 URI 对应的 MIME 字符串主要由3部分组成,Android 对这3部分做了如下格式规定:
1 必须以 vnd 开头
2 如果内容 URI 以路径结尾,则后接 android.cursor.dir/ ,如果内容 URI 以 id 结尾,则后接 android.cursor.item/
3 最后接上 vnd..
所以对于content://com.example.app.provider/table1
这个内容 URI,它所对应的 MIME 类型就可以写成:
vnd.android.cursor.dir/vnd.com.example.app.provider.table1
对于content://com.example.app.provider/tabl1/1
这个内容 URI,它所对应的 MIME 类型就可以写成:
vnd.android.cursor.item/vnd.com.example.app.provider.table1
@Nullable
@Override
public String getType(@NonNull Uri uri) {
String mimeType;
switch (uriMatcher.match(uri)){
case TABLE1_DIR:
mimeType = "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
break;
case TABLE1_ITEM:
mimeType = "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
break;
case TABLE2_DIR:
mimeType = "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
break;
case TABLE2_ITEM:
mimeType = "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
break;
default:
mimeType = null;
break;
}
return mimeType;
}
或许你有疑问,如何才能保证隐私不被泄漏出去呢?其实,所有的 CRUD 操作都一定要匹配到相应的内容 URI 格式才可以进行,而 UriMatcher 中添加的数据 URI 是我们自己添加的,如果我们不添加隐私数据,外部程序根本不会访问到。
第二种实现跨程序数据共享
我们提供内容提供器给外部访问接口,首先要创建一个内容提供器
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.support.annotation.NonNull;
public class DataBaseProvider extends ContentProvider {
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;
public static final String AUTHORITY = "com.example.databasetest.provider";
private static UriMatcher uriMatcher;
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY,"book",BOOK_DIR);
uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);
uriMatcher.addURI(AUTHORITY,"category",CATEGORY_DIR);
uriMatcher.addURI(AUTHORITY,"category/#",CATEGORY_ITEM);
}
private MyOpenHelper dbHelper;
public DataBaseProvider() {
}
@Override
public boolean onCreate() {
/*
创建 SQLiteOpenHelper,返回 true 代表创建内容提供器初始化成功
*/
dbHelper = new MyOpenHelper(getContext(),"BookStore.db",null,1);
return true;
}
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
/*
先获取 SQLiteDatabase 对象,然后根据 uri 判断调用方要访问哪张表,
再调用 SQLiteDatabase 的 query() 方法进行查询。
注意:当查询单条数据的时候,我们调用了 Uri 对象的 getPathSegments()方法,
它会将内容 URI 权限之后的部分以"/"分割,并把分割后的结果放到字符串列表中,
这个列表第0个位置存放的就是路径,第一个位置存放的就是 id 了
*/
SQLiteDatabase sqLiteDatabase = dbHelper.getReadableDatabase();
Cursor cursor = null;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
cursor = sqLiteDatabase.query("Book",projection,selection,selectionArgs,null,null,sortOrder);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
sqLiteDatabase.query("Book",projection,"id = ?",new String[]{bookId},null,null,sortOrder);
break;
case CATEGORY_DIR:
cursor = sqLiteDatabase.query("Category",projection,selection,selectionArgs,null,null,sortOrder);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
cursor = sqLiteDatabase.query("Category",projection,"id = ?",new String[]{categoryId},null,null,sortOrder);
break;
default:
break;
}
return cursor;
}
@Override
public Uri insert(@NonNull Uri uri, ContentValues values) {
/*
注意:该方法需要返回一个能够表示这条数据的 Uri 对象,因此我们还
需要调用 Uri.parse()将一个内容 URI 解析成一个 Uri 对象,当然
这个内容 URI 是以新增数据的 id 结尾的。
*/
SQLiteDatabase sqLiteDatabase = dbHelper.getReadableDatabase();
Uri returnUri = null;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = sqLiteDatabase.insert("Book", null, values);
returnUri = Uri.parse("content://"+AUTHORITY+"/book/"+newBookId);
break;
case CATEGORY_DIR:
case CATEGORY_ITEM:
long newCategoryId = sqLiteDatabase.insert("Category", null, values);
returnUri = Uri.parse("content://"+AUTHORITY+"/category/"+newCategoryId);
break;
default:
break;
}
return returnUri;
}
@Override
public int update(@NonNull Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase sqLiteDatabase = dbHelper.getReadableDatabase();
int updateRows = 0;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
updateRows = sqLiteDatabase.update("Book", values, selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
updateRows = sqLiteDatabase.update("Book",values,"id = ?",new String[]{bookId});
break;
case CATEGORY_DIR:
updateRows = sqLiteDatabase.update("Category",values,selection,selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
updateRows = sqLiteDatabase.update("Category",values,"id = ?",new String[]{categoryId});
break;
default:
break;
}
return updateRows;
}
@Override
public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase sqLiteDatabase = dbHelper.getReadableDatabase();
int deleteRows = 0;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
deleteRows = sqLiteDatabase.delete("Book", selection, selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deleteRows = sqLiteDatabase.delete("Book","id = ?",new String[]{bookId});
break;
case CATEGORY_DIR:
deleteRows = sqLiteDatabase.delete("Category",selection,selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
deleteRows = sqLiteDatabase.delete("Category","id = ?",new String[]{categoryId});
break;
default:
break;
}
return deleteRows;
}
@Override
public String getType(@NonNull Uri uri) {
switch (uriMatcher.match(uri)){
case BOOK_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
case CATEGORY_ITEM:
return "vnd.android.cursor.item/vnd.example.databasetest.provider.category";
default:
return null;
}
}
}
OK,剩下的就等调用方调用就行了,调用方的代码跟前面我们说过的第一种内容提供器的代码一样。
四、小结与点评
每次在创建内容提供器的时候,我们需要提醒一下自己,是否应该这样做?只有真正需要将数据共享出去的时候我们才应该创建内容提供器,仅仅是用于程序内部访问的数据就没有必要这么做,千万不要滥用。
下一篇文章:https://www.jianshu.com/p/7314b44f93dd