Android开发 - 权限和内容提供器

内容提供其用于在不同程序之前实现数据共享的功能,提供了一整套完整机制,允许一个程序从另一个程序中访问数据。内容提供器可以选择只对部分数据进行共享,保证了数据安全。

运行时权限

要先了解一下这个东西,不仅在这里能用到,以后也常用。
在Android 6.0之前,程序的权限是在AndroidManifest.xml中写好了的,在安装软件的时候会显示出来,安装软件就表示授权了这些权限,但是有很多软件会申请一些并不需要的权限,而你不同意的话又没法使用软件,就很不方便。
因此Android 从6.0以后开始针对这个问题提出了运行时权限的功能,不需要再安装软件的时候授权所有权限,在使用过程中需要什么权限进行申请,这样即便拒绝了这个权限,也只是这个功能用不成,其他功能还是可以使用的。
但是并不是所有权限都需要在运行的时候一个一个授权,因此分为两类,普通权限和危险权限,普通权限是指不会直接威胁用户的安全和隐私的权限,这些权限系统会自动帮我们授权。比如前面用到的BroadcastTest里面的两个权限就是普通权限,危险权限就是指会触及用户隐私或设备安全的权限,比如联系人信息、地理位置信息等等,需要手动授权才可以,否则无法使用相关功能。
Android所有的危险权限一共有九组24个,当权限在这里面的时候,就需要进行运行时权限处理,如果需要的权限不在这里面就只需要在AndroidManifest.xml中注册就行了。
image-1
每个危险权限都属于一个权限组,处理的时候用的是权限名但是一旦授权,组内其他权限会一同被授予。

这里举个打电话的栗子,CALL_PHONE这个权限是打电话的时候需要声明的,理由是打电话要掏钱,很危险
布局文件还是那个按钮,给按钮设置监听,当点击按钮的时候就打电话,写完代码后她就开始报错了,把权限写一下。

TestButton.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);
            }
        });

那就写一下权限吧,编辑AndroidManifest.xml,添加CALL_PHONE权限

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.k.androidpractice">
    <uses-permission android:name="android.permission.CALL_PHONE"/>
    ...

此时如果运行程序,点击按钮以后,会报错,因为还是没有给他授权。
image-2
稍微做一下修改。
如下,这是运行时权限的所有流程
首先通过ContextCompat.checkSelfPermission()判断是否已经授权了该权限,参数第一个是Context,第二个是权限名,如打电话是Manifest.permission.CALL_PHONE,将返回值和PackageManager.PERMISSION_GRANTED作比较,如果相等就说明已经授权了,否则没授权。
如果未授权,调用ActivityCompat.requestPermissions()方法申请权限,三个参数,第一个是Activity实例,第二个是String数组,存放权限名,第三个请求码只要唯一就行。
如果用户授权就可以打电话,否则会弹窗。

TestButton.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, @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 the permission",Toast.LENGTH_SHORT).show();
            }
            break;
        }
    }

运行程序,点击按钮会有授权提示
image-3
如果选择DENY就会返回,选择ALLOW会打电话
image-4

内容提供器

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

ContentResolver

应用程序要想访问内容提供器中的数据就必须要借助这个类,通过Context的getContentResolver()方法取得实例,同时还提供了一些CRUD操作方法

  • insert
  • delete
  • update
  • query
    和数据库的不同,使用的时候不接收表名参数没使用Uri参数代替,由两部分组成,authority和path。前者区分不同应用程序,为了避免冲突一般是包名,后者区分同一应用程序中的不同表。如有table1和table2,则path为/table1和/table2。组合起来就是com.example.k.androidpractice/table1,再加上协议声明,内容URI标准写法如下
    content://com.example.k.androidpractice/table1
    Uri uri=Uri.parse(“com.example.k.androidpractice/table1”)
查询

查询代码如下所示,参数分别代表某个应用的某个表、指定字段列、where约束条件、占位符具体数值以及指定排序方式。返回结果仍然是一个Cursor对象。。

Cursor cursor=getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder);

读物数据的思路还是通过移动游标来遍历每一行

if (cursor!=null){
	while (cursor.moveToNext()){
		String column1=cursor.getString(cursor.getColumnIndex("column1"));
	}
	cursor.close();
}
添加

和数据库的操作很像,使用ContentValues对象存放数据然后作为参数传入。

ContentValues values=new ContentValues();
values.put("colume1","sss");
getContentResolver.insert(uri,values);
更新

像更新数据,把colume的值清空,调用update()方法

ContentValues values=new ContentValues();
values.put("column1","");
getContentResolver().update(uri,values,"column1= ? and column2= ?",new String[]{"text","1"});
删除

通过delete()方法可以删除数据。

getCntentResolver.delete(uri,"column2=?",new String[]{"1"});

然后ContentResolver的增删改查就完了。

读取联系人

一个例子,读取一下联系人数据。
给avd里面添加几个联系人数据,需要将获取的数据展示出来,用ListView来显示数据,修改acivity_layout.xml,添加一个控件就行。

<ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/ShowContact"></ListView>

修改MainActivity.java,获取ListView实例,设置适配器,在使用前进授权询问,如果给权限了就抓紧时间进行处理。
这里没有使用Uri解析,是因为在ContentResolver.CommonDataKinds.Phone类中已经封装好了,里面有一个CONTENT_URI,这就是Uri.parse()解析后的结果。
然后湖区通讯录数据,表示联系人姓名的常量是ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,相当于数据库中查询用到的列名,这里封装好了。进行拼装,添加到ListView数据源中,然后同值ListViw刷新一下,最后关闭cursor。

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ShowContactListView=findViewById(R.id.ShowContact);
        adapter=new ArrayAdapter<String>(this,R.layout.list_item_1,ContactList);
        ShowContactListView.setAdapter(adapter);
        if (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_CONTACTS)!=PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},1);
        }else{
            readContact();
        }
    }

private void readContact(){
        Cursor cursor=null;
        try{
            cursor=getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
            if (cursor!=null){
                while (cursor.moveToNext()){
                    String Name=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                    String Number=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                    ContactList.add(Name+"\n"+Number);
                }
                adapter.notifyDataSetChanged();
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            if (cursor!=null)
                cursor.close();
        }
    }

    @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){
                readContact();
            }else{
                Toast.makeText(this,"Denied the permission",Toast.LENGTH_SHORT).show();
            }
            break;
        }
    }

还需要再AndroidManifest.xml中添加权限

<uses-permission android:name="android.permission.READ_CONTACTS"/>

运行程序,ALLOW授权,可以看到列表了。
image-5

ArrayAdapter

这是我看这个代码发现的,泛型设为String,可以存储字符串,参数分别为Context,布局,数据List。
adapter=new ArrayAdapter(this,android.R.layout.simple_list_item_1,ContactList);
布局可以使用安卓自带的android.R.layout.simple_list_item_1
然后直接给ListView调用setAdapter()就行了,不用重写那乱七八糟的东西。

自己的内容提供器

可以通过一个继承ContentProvider类的类实现自己的内容提供器,一共有六个抽象类,需要全部重写,新建一个MyProvider类,继承ContentProvider类,在继承以后按alt+insert然后选择implement,自动填充需要实现的方法。

  • onCreate():初始化提供器,完成对数据库的创建和升级,成功返回true
  • query():查询数据,使用uri确定查询哪张表,projection确定查询的列,selection和selectionArgs约束哪些行,sortOrder对结果排序,结果作为Cursor对象返回
  • insert():向提供器添加一条数据,使用uri确定哪个表,添加的数据保存在values中,添加完成后返回一个用于表示新纪录的URI。
  • update():更新数据,使用uri表示哪个表,数据在values中,selection和selectionArgs表示约束条件,返回值是影响的行数
  • delete():uri指定哪个表,selection和selectionArgs约束删除哪些行,返回值是删除的行数
  • getType():根据传入的内容URI返回响应的MIME类型
public class MyProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        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;
    }
}

标准URI是这样的content://com.example.k.androidpractice/table1,还可以再添加,content://com.example.k.androidpractice/table1/1,表示table1表中id为1的数据。
URI只有这两种,一个是指定表,一个是指定id的数据。
还可以使用通配符。

  • *:任意长度的任意字符
  • #:任意长度的数字
    content://com.example.ss/*表示匹配任意表的内容,content://com.example.k.androidpractice/table1/#则表示匹配table1表中的任意一行的数据。
    使用UriMatcher类可以实现匹配内容URI的功能,有一个addUri()方法,有三个参数,authority(类名)、path(Uri)和一个自定义代码(标识符的功能)。当调用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.k.androidpractice","table1",TABLE1_DIR);
        uriMatcher.addURI("com.example.k.androidpractice","table1/#",TABLE1_ITEM);
        uriMatcher.addURI("com.example.k.androidpractice","table2",TABLE2_DIR);
        uriMatcher.addURI("com.example.k.androidpractice","table2/#",TABLE2_ITEM);
    }
    ...
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        switch (uriMatcher.match(uri)){
            case TABLE1_DIR:
                //查询table1所有数据
                break;
            case TABLE1_ITEM:
                //查询table1所有数据
                break;
            case TABLE2_DIR:
                break;
            case TABLE2_ITEM:
                break;

        }
        return null;
    }
    ...

其他方法也都差不多。
getType()方法,用于获取Uri对象对应的MIME类型,这个字符串主要由三部分组成。
必须以vnd开头,如果内容URI以路径结尾,则接andoid.cursor.dir/,如果是id结尾,则接android.cursor.item/,然后再接上vnd..
如对于com.example.k.androidpractice/table1可以写成vnd.android.cursor.dir/vnd.com.example.k.androidpractice.table1
继续完善代码,修改getType()代码,需要做的就是构造对应的MIME字符串,这里我只写了一个,懒得写了,太麻烦了,其他的照着写就行。

public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)) {
            case TABLE1_DIR:return "vnd.android.cursor.dir/vnd.com.example.k.androidpractice.table1";break;

        }
    }

说白了就是相当于在这里实现各种关于数据库的增啥改查操作,然后另一个应用访问接口,间接访问这个应用的数据库。
有个小栗子,太麻烦不写了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值