0%

第7章: 数据存储全方案——跨程序共享数据:探究内容提供器

可能你会有些疑惑,为什么要将我们程序中的数据共享给其他程序呢?当然,这是视情况而定的,比如账号密码之类的隐私数据显然是不能共享给其他程序的,不过一些可以让其他程序进行二次开发的基础性数据,我们还是可以选择共享。例如联系人程序、短信程序、多媒体库等,它们的数据库中保存了很多基础数据,如果不允许其他应用进行访问,则方便性就会大打折扣。

运行时权限

Android的权限机制在一开始就存在,但是在6.0以前保护隐私方面比较有限,因为像微信这种大家都离不开的软件,容易“店大欺客”,不同意它所有的权限只能不安装,这并不合理。

权限机制详解

开发者在AndroidManifest.xml中声明权限,一种情况是,用户如果在低于6.0的系统上安装该程序,会在安装时列出该应用所需要的权限,从而决定是否要安装这个程序,并且在用户安装成功之后,还能在设置中查看程序所申请的权限,但是对于那些离不开的程序(比如微信)来说,要么全部同意它申请的权限,要么不安装,这不太合理;如果在6.0及以上的系统中安装,则用户不必在安装时一次性授权所有申请的权限,而是在软件使用的过程中再对危险权限进行授权,就算拒绝了这个权限,仍然可以使用应用的其他功能,而不是以前那样直接无法安装。

Android 6.0 及以上将所有权限分为两类,普通权限和危险权限,普通权限是指不会直接威胁用户的安全和隐私的权限,这部分权限系统自动帮我们授权,避免用户不停地手动授权;危险权限则表示会触及用户隐私或者设备安全性的权限,如获取联系人、定位设备位置等,必须由程序员动态申请,由用户手动点击授权才可以,否则无法使用相应功能。目前为止,Android中的危险权限有9组共24个权限,如下列表所示(图片来自官网):

危险权限列表

这张表格无需记住,在使用的时候作为参照,如果权限在这张表中,则进行运行时处理就好。另外注意一下,表格中每个危险权限都属于一个权限组,我们在进行运行时权限处理时使用的是权限名,但是用户一旦同意授权了,那么该权限对应的权限组中所有的其他权限也会同时被授权

在程序运行时申请权限

以拨打电话的权限为例来说明权限的申请,点击一个按钮,就拨打指定的号码,在6.0以前可能是这样实现的:

  1. 在AndroidManifest.xml中申请权限:
    1
    <uses-permission android:name="android.permission.CALL_PHONE"/>
  2. 在代码中实现:
1
2
3
4
5
6
7
8
btnCall.setOnclickListener(new View.OnclickListener(){
@Override
public void onClick(View v){
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(Intent);
}
});

在6.0以下系统上能正常拨打电话,但是在6.0或者以上系统运行,会报错Permission Denial,可以看出是由于权限被禁止导致的,因此我们应该尝试使用以下方式来申请权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
btnCall.setOnclickListener(new View.OnclickListener(){
@Override
public void onClick(View v){

if(ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.CALL_PHONE) !=
PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermission(MainActivity.this,new
String[]{Manifest.permission.CALL_PHONE},1);
}else{
call();
}
}
});

private void call(){
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(Intent);
}

@Override
public void onRequestPermissionResult(int requestCode,String[] permissions,int[] grantResults){

switch(requestCode){
case 1:
if(grantResults.lenght > 0 && grantResults(0) == PackageManager.PERMISSION_GRANTED){
call();
}else{
Toast.makeText(this,"You denied the permission",Toast.LEGHTH_SHORT).show();
}

break;

}

}

上述第一步先判断用户是不是已经给我们授权了,使用的是ContextCompat.checkSelfPermission,如果已经授权,直接拨打电话,否则调用ActivityCompat.requestPermission方法向用户申请授权,这时候用户可以选择同意或者拒绝我们的申请,不论哪种结果,都会通过回调onRequestPermissionResult告知,在回调中根据不同的结果做不同的处理。记住,在动态声明权限后,AndroidManifest中还得添加 声明

访问其他程序中的数据

内容提供器的用法一般有两种,一是使用现有的内容提供器来读取和操作响应程序中的数据,另一种是创建自己的内容提供器给我们的数据提供外部访问接口。

ContentResolver的基本使用

如果想要访问内容提供器共享的数据,就一定要借助ContentResolver类,可以通过Context中的getContentResolver方法获取到该类的实例。可以对内容进行CRUD操作,不同于SQLiteDatabase,ContentResolver增删改查不接收表名参数,而是使用Uri参数代替,该Uri主要由两部分组成:authority和path,前者用于对不同的应用程序做区分,一般采用程序包名形式,如某个程序的包名是com.example.app,那么对应的authority就可以命名为com.example.app.provider;path则是对同一应用程序中不同表做区分的,通常会添加到authority后面,所以内容Uri的形式一般如下所示(带协议声明):

content://com.example.app.provicer/table1
content://com.example.app.provicer/table2

正式查询的时候,将Uri作为参数传入,代码如下:

1
2
3
Uri uri = Uri.parse("content://com.example.app.provicer/table1");
Cuisor cursor = getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder);

其中,query方法中各个参数对应的含义如下所示:

参数对应的含义

接下来便可以进行相应的增删改查操作,代码如下:

1
2
3
4
5
6
7
//查
if(cursor != null){
while(cursor.moveToNext()){
String colomn1 = cursor.getString(cursor.getColumnIndex("column1"));
int colomn2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
}
1
2
3
4
5
//增
ContentValues values = new ContentValues();
values.put("column1","text");
values.put("column2",1);
getContentResolver().insert(uri,values);
1
2
3
4
//改,把column1的值清空
ContentValues values = new ContentValues();
values.put("column1","");
getContentResolver().update(uri,values,"column1 = ? and column2 = ?",new String[]{"text","1"});
1
2
//删除
getContentResolver().delete(uri,"column2 = ?",new String[]{"1"});

其实整体就相当于sql语句,因此并不太难。

创建自己的内容提供器

因为基本上没有这样的需求,暂时略后续补上

谢谢你的鼓励