《第一行代码》 第七章:跨程序共享数据-内容提供器

一,运行时权限的理解

Android现在将所有的权限归成了两类,一类是普通权限,一类是危险权限。普通权限指的是那些不会直接威胁到用户的安全和隐私的权限,对于这部分权限申请,系统会自动帮我们进行授权、而不需要用户再去手动操作了。

1,普通权限

系统自动授权的,无需申请。

这样一来,我们在安装程序的时候,就会告知用户,该程序会获取设备的啥权限。

2,危险权限

例如打开摄像头,麦克风等的权限,就需要在已经安装app后,用户使用时弹窗让用户授权。
对于危险权限,我们只要在AndroidManifest.xml文件中加两句权限声明:
在这里插入图片描述

3,在程序运行时申请权限

做个例子,申请拨打电话的。
第一步:新建项目runtimepermissiontest
第二步:修改布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/make_call"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="拨打电话"/>

</LinearLayout>

第三步:Menifest文件申请权限

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button makeCall=(Button) findViewById(R.id.make_call);
        makeCall.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View view) {
                //判断是否授权
                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) {
        switch(requestCode){
            case 1:
                if(grantResults.length>0 && grantResults[0]== PackageManager.PERMISSION_GRANTED){
                    call();
                }else{
                    Toast.makeText(this, "你拒绝了授权", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
        }
    }
}

二,内容提供器

内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。目前,使用内容提供器是 Android 实现跨程序共享数据的标准方式。
不同于文件存储和 SharedPreferences存储中的两种全局可读写操作模式,内容提供器可以选择只对哪一部分数据进行共享,从而保证我们程序中的隐私数据不会有泄漏的风险。
内容提供器的用法一般有两种:

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

1,ContentResolver的基本用法,使用现有的内容提供器

对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要借助 Content.Resolver 类,可以通过 Context 中的 getContentResolver()方法获取到该类的实例。Content-Resolver 中提供了一系列的方法用于对数据进行CRUD操作,

insert()方法用于添加数据
update()方法用于更新数据,
delete()方法用于删除数据,
query()方法用于查询数据。

ContentResolver 中的增删改查方法都是不接收表名参数的,而是使用一个 Uri 参数代替,这个参数被称为内容 URI。内容 URI给内容提供器中的数据建立了唯一标识符,它主要由两部分组成:authority和 path。authority 是用于对不同的应用程序做区分的一般为了避免冲突,都会采用程序包名的方式来进行命名。
不过,目前还很难辨认出这两个字符串就是两个内容 URI,我们还需要在字符串的头部加上协议声明。因此,内容URI最标准的格式写法如下:

content://com.example.app.provider/tablel
content://com.example.app.provider/table2

在得到了内容 URI字符串之后,我们还需要将它解析成 Uri 对象才可以作为参数传人。解析的方法也相当简单,代码如下所示:

Uri uri = Uri.parse("content://com.example.app.provider/tablel")

只需要调用Uriparse()方法,就可以将内容 URI字符串解析成Uri对象了现在我们就可以使用这个 Uri 对象来查询 table1 表中的数据了,代码如下所示:

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

在这里插入图片描述
查询完成后返回的仍然是一个 Cursor 对象,这时我们就可以将数据从 Cursor 对象中逐个读取出来了。读取的思路仍然是通过移动游标的位置来遍历 Cursor 的所有行,然后再取出每行中相应列的数据,代码如下所示:

if (cursor != null) {
	while (cursor.moveToNext()) {
		String column]= cursor.getString(cursor.getColumnIndex("column1"));
		int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
	}
	cursor.close();
}

我们先来看看如何向 table1 表中添加一条数据,代码如下所示:

ContentValues values = new ContentValues();
values.put("column]"“text");
values.put("column2"1);
getContentResolver().insert(uri, values);

现在如果我们想要更新这条新添加的数据,把column1的值清空,可以借助ContentResolver的 update()方法实现,代码如下所示:

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

注意上述代码使用了 selection 和 selectionArgs 参数来对想要更新的数据进行约束,以防止所有的行都会受影响。
最后,可以调用 ContentResolver的 delete()方法将这条数据删除掉,代码如下所示:

getContentResolver(),delete(uri,"column2 = ?"new Stringl]["");

2,创建自己的内容提供器

上文说了如何在自己的程序中访问其他应用程序的数据。那提供给其他程序使用的访问接口的数据又如何实现呢?
需要我们创建自己的内容提供器。
如果想要实现跨程序共享数据的功能,官方推荐的方式就是使用内容提供器,可以通过新建一个类去继承 ContentProvider 的方式来创建一个自己的内容提供器。ContentProvider 类中有6个抽象方法,我们在使用子类继承它的时候,需要将这 6个方法全部重写。新建 MyProvider 继承自 ContentProvider,代码如下所示:

public class MyProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

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

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

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }
}

1.onCreate()

初始化内容提供器的时候调用。通常会在这里完成对数据库的创建和升级等操作,返回 true表示内容提供器初始化成功,返回 false 则表示失败。注意,只有当存在 ContentResolver 尝试访问我们程序中的数据时,内容提供器才会被初始化。

2.query()

从内容提供器中查询数据。使用uri参数来确定查询哪张表,proiection 参数用于确定查询哪些列,selection 和selectionArgs 参数用于约束查询哪些行,sortOrder 参数用于对结果进行排序,查询的结果存放在 Cursor 对象中返回。

3.insert()

向内容提供器中添加一条数据。使用 uri 参数来确定要添加到的表,待添加的数据保存在values 参数中。添加完成后,返回一个用于表示这条新记录的URI。

4.update()

更新内容提供器中已有的数据。使用 uri 参数来确定更新哪一张表中的数据新数据保存在values 参数中,selection 和 selectionArgs 参数用于约束更新哪些行,受影响的行数将作为返回值返回。

5.delete()

从内容提供器中删除数据。使用 uri 参数来确定删除哪一张表中的数据,selection 和selectionArgs 参数用于约束删除哪些行,被删除的行数将作为返回值返回

6.getType()

根据传人的内容URI来返回相应的MIME类型

可以看到,几乎每个方法都会带Uri这个参数,这个参数也正是调用ContentResolver的增删改查方法时传递过来的,而现在,我们需要对传入的uri进行解析,从中分析出调用方期望访问的表和数据。
一个标准的uri有以下两种情况:

以路径结尾就表示期访问该表中所有的数据:content://com.example.app.provider/tablel
id 结尾就表示期望访问该表中拥有相应 id 的数据。content://com.example.app.provider/tablel/1

我们可以通过通配符的方式来分别匹配这两种格式的内容uri,规则如下:

*:表示匹配任意长度的任意字符
#:表示匹配任意长度的数字

所以,一个能够匹配任意表的内容 URI格式就可以写成:

content://com.example.app.provider/*

而一个能够匹配 table1表中任意一行数据的内容URI格式就可以写成

content://com.example.app.provider/tablel/#

接着,我们再借助 UriMatcher 这个类就可以轻松地实现匹配内容 URI的功能。UriMatcher中提供了一个adduRI()方法,这个方法接收3个参数可以分别把authority、path 和一个自定义代码传进去。
这样,当调用 UriMatcher 的 match()方法时,就可以将一个Uri 对象传入,返回值是某个能够匹配这个 Uri 对象所对应的自定义代码,利用这个代码,我们就可以判断出调用方期望访问的是哪张表中的数据了。修改 MyProvider 中的代码,如下所示:

public class MyProvider extends ContentProvider {
    public static final int TABLE1_RIR=0;
    public static final int TABLE1_TIME=1;
    public static final int TABLE2_DIR=2;
    public static final int TABLE2_TIME=3;
    private static UriMatcher uriMatcher;
    static {
        //静态代码块,
        uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.app.provider","table1",TABLE1_RIR);
        uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_TIME);
        uriMatcher.addURI("com.example.app.provider","table2",TABLE2_DIR);
        uriMatcher.addURI("com.example.app.provider","table1",TABLE2_TIME);
    } 
    @Override
    public boolean onCreate() {
        return false;
    }
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
        switch(uriMatcher.match(uri)){
            case TABLE1_RIR:
                //查询table1表中的所有数据
                break;
            case TABLE1_TIME:
                //查询table1表中的单条数据
                break;
            case TABLE2_DIR:
                //查询table2表中的所有数据
                break;
            case TABLE2_TIME:
                //查询table2表中的单条数据
                break;
            default:
                break;
        }
        return null;
    }
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        return null;
    }
    @Override
    public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }
}

除此之外,还有一个方法你会比较陌生,即 getType()方法。它是所有的内容提供器都必须提供的一个方法,用于获取Uri 对象所对应的 MIME类型。一个内容 URI所对应的 MIME 字符串主要由3部分组成,Android 对这3个部分做了如下格式规定。

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

所以,对于 content://com.exampleapp.provider/tablel 这个内容 URI,它所对应的 MIME类型就可以写成:

vnd.android.cursor.dir/vnd.com.example.app.provider.tablel

对于 content://comexampleappprovider/tablel/1 这个内容 URI,它所对应的MIME类型就可以写成:

vnd.android.cursor.item/vnd.com.example.app.provider.tablel

现在我们可以继续完善MyProvider 中的内容了,这次来实现getType()方法中的逻辑,代码如下所示:

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch(uriMatcher.match(uri)){
            case TABLE1_RIR:
                return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
            case TABLE1_TIME:
                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_TIME:
                return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
            default:
                break;
        }
        return null;
    }

到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用ContentResolver 来访问我们程序中的数据。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值