内容提供者(Content Provider)
内容提供者主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供器是Android实现跨进程共享数据的标准方式。
运行时权限
Android开发团队在6.0系统中加入了运行时权限功能。也就是说,用户不需要在安装软件的时候一次性授权所有申请的权限,而是可以在软件的使用过程中再对某一项权限申请进行授权。
Android现在将所有的权限归成了两类,一类是普通权限,一类是危险权限。普通权限指的是那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮我们进行授权,而不需要用户再去手动操作了。危险权限则表示那些可能会触及用户隐私,或者对设备安全性造成影响的权限,如获取设备联系人信息、定位设备的地理位置等,对于这部分权限申请,必须要由用户手动点击授权才可以,否则程序就无法使用相应的功能。
程序运行时申请权限的代码:
makeCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE},1);
}else {
call();
}
}
});
private void call() {
try {
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}catch (SecurityException e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,int[] grantResults){
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
call();
}else {
Toast.makeText(this, “You denied the permission”, Toast.LENGTH_SHORT).show();
}
break;
default;
}
}
上面的代码将运行时权限的完整流程逗覆盖了,下面我们来具体解析一下。说白了,运行时权限的核心就是在程序运行过程中由用户授权我们去执行某些危险操作,程序是不可以擅自做主去执行这些危险操作的。因此,第一步就是先判断用户是不是已经给我们授权了,借助的是ContextCompat.checkSelfPermission()方法。checkSelfPermission()方法接收两个参数,第一个参数是Context,这个没什么好说的,第二个参数是具体的权限名,比如打电话的权限名就是Manifest.permission.CALL_PHONE,然后我们使用方法的返回值和PackageManager,PERMISSION_GRANTED做比较,相等就说明用户已经授权,不等就表示用户没有授权。
ContentResolver的基本用法
对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助ContentResolver类,可以通过Context中的getContentResolver()方法获取到该类的实例。ContentResolver中提供了一系列的方法用于队数据进行CRUD操作,其中insert()方法用于查询数据,和SQLiteDatabase很相似。
不同于SQLiteDatabase,ContentResolver中的增删该查方法都是不接收表名参数的,而是使用一个Uri参数代替,这个参数呗成为内容Uri。内容URI给内容提供其中的数据建立了唯一标识符,它主要由两部分组成:authority和path。一般包名用作authority,path是authority加上表格名字。
内容URI的格式写法如下:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
在得到了内容URI字符串之后,我们还需要将它解析成Uri对象才可以作为参数传入。代码如下:
Uri uri = Uri.parse("content://com.example.app.provider/table1");
只需要调用Uri.parse()方法,就可以将内容URI字符串解析成Uri对象了。
现在我们就可以使用这个Uri对象来查询table1表中的数据了,代码如下所示:
Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOrder
);
下面看看参数表格:
query()方法参数 | 对应SQL部分 | 描述 |
---|---|---|
uri | from talbe_name | 指定查询某个应用程序下的某一张表 |
projection | select column1,column2 | 指定查询的列名 |
selection | where column = value | 指定where的约束条件 |
selectionArgs | - | 为where中的展位符提供具体的值 |
orderBy | order by column1,column2 | 指定查询结果的排序方式 |
查询完成后返回的仍然是一个Cursor对象,这时我们就可以将数据从Cursor对象中逐个读取出来了。读取的思路仍然是通过移动游标的位置来遍历Cursor的所有行,然后再取出每一行相应列的数据,代码如下所示:
if (cursor != null) {
while (curosr.moveToNext()) {
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
往table添加数据,代码如下:
ContentValues values = new ContentValues();
values.put("column1", "text");
values.put("column2", 1);
getContentResolver().insert(uri,values);
更新数据:
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", new String[] {"text","1"});
删除数据:
getContentResolver().delete(uri, "column2 = ?",new String[] {"1"});
创建自己的内容提供器
ContentProvider类中有6个抽象方法,我们在使用子类继承它的时候,需要将这6个方法全部重写。新建MyProvider继承自ContentProvider,代码如下所示:
public class MyProvider extends ContentProvider {
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public String getType(Uri uri) {
return null;
}
}
内容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对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的哪张表中的数据了。修改MyProvider的代码,如下所示:
public class MyProvider 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);
uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR);
uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM);
uriMatcher.addURI("com.example.app.provier","table2",TABLE2_DIR);
uriMatcher.addURI("com.example.app.provider","table2/#",TABLE2_ITEM);
}
...
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
// 查询table1表中的所有数据
break;
case TABLE1_ITEM:
// 查询talbe1表中的单条数据
break;
case TABLE2_DIR:
// 查询talbe2表中的所有数据
break;
case TABLE2_ITEM:
// 查询table2表中的单条数据
break;
default:
break;
}
...
}
...
}
上述代码只是以query()方法为例做了个示范,其实insert()、update()、delete()这几个方法的实现也是差不多的,它们都会携带Uri这个参数,然后同样利用UriMatcher的match()方法判断出调用方期望访问的是哪张表,再队该表中的数据进行相应的操作就可以了。
除此之外,还有一个方法,即getType()方法。它是所有的内容提供器都鼻血提供的一个方法,用于获取Uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串主要由3部分组成,Android对这3个部分做了如下格式规定。
- 必须以vnd开头。
- 如果内容URI以路径结尾,则后接android.curosr.dir/,如果内容URI以id结尾,则后接android.cursor.item/。
- 最后接上vnd..。
所以,对于content://com.example.app.provider/table1这个内容URI,它所对应的MIME类型就可以写成:
vnd.android.cursor.dir/vnd.com.example.app.provider.table1
对于content://com.example.app.provider/table1/1这个内容URI,它所对应的MIME类型就可以写成:
vnd.android.cursor.item/vnd.com.example.app.provider.table1
getType()方法的代码如下所示:
public class MyProvider extends ContentProvider {
...
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
case TABLE1_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
case TABLE2_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
default:
break;
}
return null;
}
}