Android入门笔记(运行时权限与内容提供器)

1 运行时权限

在上几篇文章中,在访问系统的网络状态由于涉及到用户设备的安全性,所以需要使用权限声明,即在 AndroidManifest 中添加 uses-permission 标签,在添加了权限声明后,用户主要在以下两个方面得到保护:

  • 如果用户在低于 6.0 系统的设备中安装该程序,会在该程序的安装界面上出现提醒,让用户从而决定是否安装该程序
  • 安装程序之后,用户可以在应用程序管理界面中查看任意程序的权限申请情况

但有很多常用的软件普遍存在着滥用权限的情况,即不管该软件是否用到该权限,先声明了再说,但由于该软件常用,用户又不得不安装,举例子说,微信它在安装的提醒界面说需要读取手机的短信,即使不认可,但难道就拒绝安装吗?

所以 Android 在 6.0 系统中加入了 运行时权限功能,也就说用户不需在安装软件的时候一次性授权所有申请的权限,而是可以在软件使用的时候再对某项权限申请进行授权,从而达到即使拒绝了某个权限,还能使用到该程序的其他功能。
并不是所有的权限都是 运动时权限,Android 将所有的权限归为两类,分别是 普通权限和危险权限

  • 普通权限指的是不会直接威胁到用户的安全和隐私的权限,这部分权限申请,系统会自动帮我们进行授权,比如访问系统的网络状态。
  • 危险权限表示可能触及到用户隐私或者设备安全性造成影响的权限,如获取设备联系人信息,定位等

所有的危险权限如下,即如果不在这个表中,那么只需在 AndroidManifest.xml 文件中添加权限声明即可:
在这里插入图片描述

注:表格中每个危险权限都属于一个权限组,在进行运行时权限处理时使用的是权限名,但用户一旦同意授权,那么该权限所对应的权限组中所有的其他权限也会同时被授权。

1.1 运行时申请权限

在程序运行时申请权限,举 CALL_PHONE 权限例子说明,在 Android 6.0 系统出现之前,拨打电话功能的实现很简单,直接在 AndroidManifest.xml 文件中声明权限,再使用 Intent 对象即可。
但在 Android 6.0 系统之后,系统在使用危险权限就必须进行运行时权限处理了。
所以当在 6.0 系统之后逻辑的步骤就变为:

  • 判断用户是否已授权
  • 如果已授权,直接执行相关逻辑
  • 如果没有授权,向用户申请授权

ContextCompat.checkSelfPermission 方法可以获取就判断用户是否已经授权,该方法接收两个参数,分别是:

  • Context 实例
  • 具体的权限名,如 Manifest.permission.CALL_PHONE

判断该方法返回的数值是否与 PackageManager.PERMISSION_GRANTED 相同,如果相同即用户已授权。

ActivityCompat.requestPermission 方法可以向用户申请授权,该方法接受三个参数,分别是:

  • Activity 实例
  • String 数组,把要申请的权限名放在数组中即可
  • 申请码,用于在回调函数 onRequestPermissionsResult 中使用,只要是唯一值即可

onRequestPermissionsResult 方法是当用户无论点击同意或拒绝的回调函数,授权的结果会封装在 grantResults 参数中。
例子代码如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button9 = (Button) findViewById(R.id.make_call);
        button9.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();
        }
    }

    /**
     * 授权回调
     * @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, "没有授权", Toast.LENGTH_LONG).show();
                }
                break;
            default:
                break;
        }

    }
}

当用户完成授权操作之后,在点击相关按钮就不会再弹出对话框寻求权限申请

2 访问其他程序中的数据

如果一个程序通过 内容提供器 对其数据提供了外部访问接口,那么任何其他的应用程序都可以对这部分数据进行访问,如系统自带的电话簿、短信等。

2.1 ContentResolver

应用想要访问内容提供器中共享的数据,就需要借助 ContentResolver 类,通过 Context 中的 getContentResolver 方法可以获取该类的实例,而 ContentResolver 实例提供了一系列方法用于数据进行 CRUD 操作。
ContentResolver 类的增删改查方法不接收表名参数,而是使用一个 Uri 参数代替,这个参数称为 内容 URI,如:

content:top.seiei.appoftest.provider/table1

内容 URI 给内容提供器中的数据建立了唯一标识符,它有三个部分组成,分别是:

  • 协议声明:如上的 content://
  • authority:用于对不同的应用程序做区分,如上的 top.seiei.appoftest
  • path:用于对同一个应用程序中不同的表做区分,如上的 /table1

内容 URI 的格式主要有两种,分别是:

  • 以路径结尾,表示期望访问该表中所有的数据,如:content://top.seiei.appoftest/table1
  • id 结尾,表示期望访问该表中拥有相应 id 的数据,如:content://top.seiei.appoftest/table1/1

可以使用通配符 *# 来分别匹配两种格式的 内容 URI

  • content://top.seiei.appoftest/*:能够匹配任意表的 内容 URI
  • content://top.seiei.appoftest/table1/#:能匹配 table1 表中的任意数据的 内容 URI

在得到 内容 URI 字符串之后,还需要作为参数传入 Uri.parse 方法解析成 Uri 对象,获取到 Uri 对象之后就可以对相应的表进行操作了。

2.1.1 查询

使用 ContentResolver 类的 query 方法可以查询相应数据 ,query 的参数为:

  • uriUri 对象,指定了哪个程序中的哪张表
  • projection:指定查询的列表文本数组
  • selection:指定筛选条件
  • selectionArgs:为筛选条件的占位符提供具体的数值
  • sortOrder:指定结果排序方式

如下例子为查询手机联系人的所有数据(需要请求权限):

private void readContacts() {
     Cursor cursor = null;
     try {
     	 // ContactsContract.CommonDataKinds.Phone.CONTENT_URI 封装了 uri 对象
         cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
         if (cursor != null) {
             while (cursor.moveToNext()) {
                 String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                 String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                 Log.d("联系人信息:" , displayName + " " + number);
             }
         }
     } catch (Exception e) {
         e.printStackTrace();
     } finally {
         if (cursor != null) {
             cursor.close();
         }
     }
}

2.1.2 增删改

如同操作 SQLite 一样,组装数据到 ContentValues 中,再调用 ContentResolverupdateinsertdelete 方法即可,如:

ContentValues values = new ContentValues();
values.put("column1", "test");
getContentResolver().insert(uri, values);

2.2 创建内容提供器

创建内容提供器的推荐方法是:通过新建一个类去继承 ContentProvider 的方式创建一个自己的内容提供器。 而 ContentProvider 类中有六个抽象方法,分别是:

  • onCreate:初始化内容提供器时调用,通常会在这里完成对数据库的创建和升级等操作,返回 true 表示创建成功
  • query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):查询数据,使用 uri 参数来确定查询哪个表,还有一些参数用于组成 SQL 语句
  • insert(Uri uri, ContentValues values):添加数据,返回一个能表示该新记录的 Uri
  • update(Uri uri, ContentValues values, String selection, String[] selectionArgs):更新数据,返回受影响的行数
  • delete(Uri uri, String selection, String[] selectionArgs):删除数据,返回删除的条数
  • getType(Uri uri):根据传入的 内容 URI 来返回相应的 MIME 类型

其中 getType 方法它是所有的内容提供器都必须提供的方法,用于获取 Uri 对象对应的 MIME 类型,一个 内容 URI 所对应的 MIME 字符串主要由三个部分组成:

  • 必须以 vnd 开头
  • 如果 内容 URI 以路径结尾,则后接 android.cursor.dir/,如果 内容 URIid 结尾,则后接 android.cursor.item/
  • 最后接上 vnd.<authority>.<path>

2.2.1 UriMatcher

通过重写 ContentProvider 的六个抽象方法,分析规定格式的 uri 对象可以达到数据的安全性,使得隐私数据不会泄漏出去,而 UriMatcher 类能够轻松的实现匹配 内容 URI 的功能。
UriMatcher 提供了 addURI 方法,它接收三个参数,分别是:

  • authority:匹配 uri 对象的 authority
  • path:匹配 uri 对象的 path
  • 自定义代码:在调用 UriMatchermatch() 方法,传入的 Uri 对象匹配的时候返回这个自定义代码

在使用 addURI 方法添加完对应的信息之后,就可以使用 match 方法检测 uri 对象是否匹配了。
下面使用 Android Studio 创建一个 provider 类,右键项目 New -> Other -> Content Provider 即可,例子代码如下:

public class MyProvider extends ContentProvider {

    public static final int TABLE1 = 0;

    public static final String AUTHORITY = "top.seiei.appoftest.provider";
    
    private static UriMatcher uriMatcher;
    
    private MyDatabaseHelper dbHelper;

    // 初始化 UriMatcher
    static {
        uriMatcher = new UriMatcher(uriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "table1", TABLE1);
    }

    /**
     * 初始化数据库
     * @return
     */
    @Override
    public boolean onCreate() {
        dbHelper = new MyDatabaseHelper(getContext(), "msgOfStudent.db", null,2);
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        switch (uriMatcher.match(uri)) {
            case TABLE1:
                cursor = db.query("table1", projection, selection, selectionArgs, null, null, sortOrder);
                break;
        }
        return cursor;
    }

	/**
     * 返回 MIME 类型
     * @param uri
     * @return
     */
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)) {
            case TABLE1:
                return "vnd.android.cursor.dir/vnd.top.seiei.appoftest.provider.table1";
            default:
                break;
        }
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }    
}

编写完内容提供器一定要在 AndroidManifest.xml 文件中注册才能使用,不过如果使用 Android Studio 的快捷方式创建,这一步就会被自动完成。打开 AndroidManifest.xml 文件的 application 标签会出现一个 provider 标签:

<provider
    android:name=".providers.MyContentProvider"
    android:authorities="top.seiei.appoftest.provider"
    android:enabled="true"
    android:exported="true"></provider>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值