Android开发 - ContentProvider(内容提供者)

目录

1.概述

1.1 引入

1.2 简介

1.3 工作原理

1.6 用法

1.7 分类

2.URI

2.1 概念

2.2 类别

2.3 通配符

2.4 使用UriMatcher匹配内容URI

2.5 解析

3.创建ContentResolver

3.1 简介

3.2 获取实例

3.3 方法

3.4 query() 方法

3.5 insert()方法

3.6 update()方法

3.7 delete()方法

3.8 ContentResolver使用案例:读取系统联系人

3.8.1 效果图

3.8.2 布局

3.8.3 代码

3.8.4 添加权限

4.创建自己的ContentProvider

4.1 创建原理

4.2 分析  

4.3 使用UriMatcher

4.3.1 示例

4.3.2 getType()方法    

5.ContentObserver

5.1 内容观察者简介


1.概述

1.1 引入

         在数据储存中学习了Android数据持久化技术,包括文件储存SharedPreferences储存以及数据库储存,这些储存技术所保存的数据都只能在当前应用程序中访问,但在Android开发中,有时会访问到其他应用程序中的数据。例如:使用支付宝转账时需要填写收款人的电话号码,此时就需要获取到系统联系人的信息。ContentProvider(内容提供者)就可以实现这种跨程序共享数据的功能。

        文件和SharedPreferences储存中提供了MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE这两种操作模式,用于供给其他应用访问当前应用程序,但是这两种模式在Android4.2版本中就废弃了,因为它们是全局的可读写操作模式,不安全。因此,为了能够允许一个程序访问另外一个程序中的数据,而且还能保证被访问的数据的安全性,需要使用ContentProvider。

1.2 简介

          ContentProvider(内容提供者)是Android系统四大组件之一,标准API,其功能是在不同程序之间实现数据共享,为储存和获取数据提供了统一的接口,具体的数据来源可以是表,也可以是文件,甚至网络等等。为应用程序之间共享数据提供了可能,比如读取电话薄里面的联系人

          不仅可以允许一个程序访问另一个程序中的数据,同时还可以选择只对哪一部分数据进行共,从而保证程序只能国的隐私数据不被泄露

         对于ContentProvider而言,无论数据的来源是什么,它都认为是一种,然后把数据组织成表,以数据库的形式操作数据。

1.3 工作原理

         工作原理如下图:

Android开发 - ContentProvider(内容提供者)_第1张图片

        从上图可以看出,A程序需要使用ContentProvider共享数据,才能被其他程序操作。B程序必须通过ContentResolver操作程序A共享出来的数据,而程序A会将操作结果返回给ContentResolver,然后ContentResolver再将操作结果返回给程序B。

1.5 应用场景

1.想要提供完整的数据和文件给其他应用程序。
2.想要允许用户从应用程序中复制完整的数据到其他的应用程序。
3.想要使用搜索框架来提供自定义的搜索建议。

1.6 用法

        内容提供器的用法一般有两种:

1.使用现有的内容提供器来读取和操作相应程序中的数据。
2.创建自己的内容提供器给程序的数据提供外部访问的接口。

1.7 分类

1.ContentResolver(内容访问者) 用于访问内容提供器中共享的数据。
2.ContentProvider(内容提供者) 用于提供外部访问数据的接口。

 

2.URI

2.1 概念

       ContentResolver与SQLiteDatabase类似,提供了一系列增、删、改、查的方法对数据进行操作。不同的是,ContentResolver的增、删、改、查方法都不接收表名参数的,而是使用一个Uri参数代替,也称为内容URI。这个URI为内容提供者中的数据建立了唯一标识符,它主要由scheme、authorities和path三部分组成。一个应用程序可能包含多个ContentProvider,所以需要授权来自各个标识,外部通过匹配这些标识来确定唯一的一个ContentProvider。如下所示:

Android开发 - ContentProvider(内容提供者)_第2张图片

      解释如下:

shcheme shcheme部分“content://”是一个标准的前缀,表明这个数据被ContentProvider所控制,它不会被修改。协议声明
authority authority部分"cn.itcast.mycontentprovider"是在创建内容提供者时指定的authority属性值,主要迎来区分不同应用程序
path

path部分"/person"部分代表资源(或者数据),当访问者需要操作不同的数据时,这个部分可以动态的改变。主要用于

对同一个应用程序中不同的表做区分

2.2 类别

       一个标准的内容URI的写法:以包名加类名的形式

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

      如果在这个内容URI后面加上一个id,如下所示:就表示要访问这个应用的table1表中id为1的数据

content://com.example.app.provider/table1/1

    因此URI的格式主要有以下两种:

1.以路径结尾,就表示期望访问该表中所有的数据。
2.以id结尾,就表示期望访问该表中拥有相应id的数据

2.3 通配符

       可以使用通配符来分别匹配上述两种格式的内容URI,其规则如下:

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

     如下所示:

匹配任意表的内容URI:content://com.example.app.provider/*
匹配table1表中任意一行数据的内容URI:content://com.example.app.provider/table1/#

2.4 使用UriMatcher匹配内容URI

      UriMatcher中提供了一个addURI()方法,这个方法接收三个参数,分别把authority、path和一个自定义代码传递进去。这样,当调用UriMatcher的match()方法时,就可以将一个Uri对象传入,返回值是某个能够匹配这个Uri对象所对应的自定义代码。然后利用这个代码,就能判断出期望访问的是那张表中的数据了

      详细使用见后面创建自定义的ContentProvider。

2.5 解析

      URI很清楚地表示出想要访问那个程序中那张表的数据,有了内容URI字符串后,还需要将它解析成Uri对象才可以作为参数传入,方法如下所示:

Uri uri = Uri.parse("content://cn.itcast.mycontentprovider/person");

 

3.创建ContentResolver

3.1 简介

        对于每一个应用程序来说,如果想要访问ContentProvider中的共享数据,就需要借助ContentResolver。

3.2 获取实例

       可以通过Context中的getContentResolver()方法获取ContentResolver实例。

3.3 方法

       ContentResolver中提供了一系列的方法用于对数据进行CRUD操作,如下:

1.insert() 添加数据
2.update() 更新数据
3.delete() 删除数据
4.query() 查询数据

3.4 query() 方法

          有了Uri对象就可用查询table表中的数据了,如下所示:

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

   参数解释如下所示:

query()方法参数 对应SQL部分 描述
uri from table_name 指定查询某个应用程序下的某一张表
projection select column1,column2 指定查询的列名
selection where column = value 指定where的约束条件
selectionArgs - 为where中的占位符提供具体的值
sortOrder order by column1,column2 指定查询结果的排序方式

      查询完成后返回的是一个Cursor对象,然后将数据从Cursor对象中逐个读取出来即可。读取的思路是通过移动游标的位置遍历Cursor的所有行,然后再取出每一行中相应列的数据,示例如下所示:

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

3.5 insert()方法

      添加数据的示例代码如下所示:

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

    注意:这里需要将需要添加的数据组装到ContentValues中去

3.6 update()方法

     假设需要将column1的值清空,代码如下所示:

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

3.7 delete()方法

     示例代码如下:

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

3.8 ContentResolver使用案例:读取系统联系人

3.8.1 效果图

     1)联系人

Android开发 - ContentProvider(内容提供者)_第3张图片

     2)得到联系人信息

Android开发 - ContentProvider(内容提供者)_第4张图片

3.8.2 布局




    
    

3.8.3 代码

public class MainActivity extends AppCompatActivity {
    ArrayAdapter adapter;
    //储存联系人
    List contactsList = new ArrayList<>();
    ListView contactsView;

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

        contactsView = findViewById(R.id.contacts_view);
        adapter = new ArrayAdapter<>(this,android.R.layout.simple_list_item_1,contactsList);
        contactsView.setAdapter(adapter);

        //判断使用拥有读取权限
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED){
            //请求权限
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},1);
        }else {
            //读取联系人
            readContacts();
        }
    }

    //读取联系人信息
    private void readContacts() {
        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));
                    contactsList.add("姓名:"+name + "\n"+"号码:"+number);
                }
                //通知刷新ListView
                adapter.notifyDataSetChanged();
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(cursor != null){
                //关闭Cursor
                cursor.close();
            }
        }
    }

    //处理权限请求结果
    @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){
                    readContacts();
                }else{
                    Toast.makeText(this,"You denied the permisssion",Toast.LENGTH_SHORT).show();;
                }
                break;
            default:
                break;
        }
    }
}

       这里ContactsContract.CommonDataKinds.Phone已经封装好了,提供了一个CONTENT_URI的内容URI字符常量,这个常量就是使用Uri.parse()解析出来的结果。

3.8.4 添加权限

      在AndroidManifest.xml中,添加如下代码:

 

4.创建自己的ContentProvider

4.1 创建原理

          如果想要创建自己的ContentProvider,需要新建子类继承自ContentProvider,需要重写其6个抽象方法。

          点击【new】->【other】->【ContentPrivider】选项,创建的如下所示:

          java文件:

public class MyContentProvider extends ContentProvider {
    
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public String getType(Uri uri) {     
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {      
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public boolean onCreate() {
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {    
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

          如上所示,onCreate()方法是在内容提供者创建时调用,insert()、delete()、update()、query()分贝用于根据指定的URI对数据进行增、删、改、查。getType()用于返回指定URI代表的数据的MIME类型,例如Windows系统中的.txt文件和.jpg文件就是两种不同的MIME类型。

         注意:insert()和update()方法,里面的新数据都保存在ContentValues里面。

        清单文件AndroidManifest.xml中注册如下:


    
    

4.2 分析  

         ContentProvider是一个抽象类,是Android应用程序中主要的组成部分之一,为应用程序提供对外的内容,它封装了数据然后提供给那些通过实现ContentResolver接口的应用程序,这些数据是可以跨应用访问的。

4.3 使用UriMatcher

4.3.1 示例

       示例代码如下:

public class MyProvider extends ContentProvider{
    //表示查询table1表中的所有数据
    public static final int TABLE1_DIR = 0;
    //表示查询table1表中的单条数据
    public static final int TABLE1_ITEM = 1;
    //表示查询table2表中的所有数据
    public static final int TABLE2_DIR = 2;
    //表示查询table2表中的单条数据
    public static final int TABLE2_ITEM = 3;

    private static UriMatcher uriMatcher;

    static{
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.app.prodiver","table1",TABLE1_DIR);
        uriMatcher.addURI("com.example.app.prodiver","table1/#",TABLE1_ITEM);
        uriMatcher.addURI("com.example.app.prodiver","table2",TABLE2_DIR);
        uriMatcher.addURI("com.example.app.prodiver","table2/#",TABLE2_ITEM);
    }

    @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) {
        switch (uriMatcher.match(uri)){
            case TABLE1_DIR:
                //查询table1表中的所有数据
                break;
            case TABLE1_ITEM:
                //查询table1表中的单条数据
                break;
            case TABLE2_DIR:
                //查询table2表中的所有数据
                break;
            case TABLE2_ITEM:
                //查询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 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;
    }
}

4.3.2 getType()方法    

      用于获取Uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串主要由3部分组成,规定如下所示:

1.必须以vnd开头
2.如果内容URI以路径结尾,则vnd后接android,cursor,dir/;如果内容URI以id结尾,则vnd后接android.cursor.item/
3.最后接上vnd..

   如下所示:

1.对于content://com.example.app.provider/table1这个内容URI,对应的MIME类型为:
vnd.android.cursor.dir/vnd.content://com.example.app.provider/table1

2.对于content://com.example.app.provider/table1/1这个内容URI,对应的MIME类型为:
vnd.android.cursor.item/vnd.content://com.example.app.provider/table1

     完善getType()代码如下所示:

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

 

5.ContentObserver

        对于每一个应用程序来说,如果想要访问内容提供器中的共享数据,那么就需要使用Android系统提供的内容观察者ContentObserver

5.1 内容观察者简介

        内容观察者ContentObserver是用来观察指定Uri所代表的数据。当ContentObserver观察到指定Uri代表的数据发生变化时,就会触发ContentObserver的onChange()方法,此时在onChange()方法里使用ContentObserver就可以查询到变化的数据。如下图所示:

Android开发 - ContentProvider(内容提供者)_第5张图片

        下面是ContentObserver的两个常用方法:

       》public void ContentObserver(Handler handler):ContentObserver的派生类都调用该构造方法,参数可以是主线程Handler,也可以说是任意Handler对象。

      》public void onChange(boolean selfChange):当观察到的Uri代表的数据发生变化时,就会触发该方法。  

       用于ContentProvider是通过delete()、insert()、update()这几个方法让数据发生变化的,因此要使用ContentObserver观察数据变化,就必须在ContentProvider的delete()、insert()、update()方法中调用ContentResolver的notifyChanger()方法,示例代码如下:

//添加数据
public Uri insert(Uri uri,ContentValues values){
     if(matcher.match(uri) == INSERT){    //匹配Uri路径
        //匹配成功,返回查询的结果集
        SQLiteDatabase db = helper.getWritavbleDatabase();
        db.insert("person",null,values);
        getContext().getContentResolver().notifyChange(PersonDao.messageuri,null);
    }else{     //匹配失败
        throw new IllegalArgumentException("路径不匹配,不能执行插入操作");
    }
    return null;
}

        notifyChange()方法实现了将数据变化的消息发送至消息中心,接收两个参数,第一个表示更改内容的Uri对象第二个表示指定某个观察者接收消息,不指定可填写null

        接下来实现在程序中注册内容观察者,并监听数据变化的功能,如下:

public class ContentObserverTest extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //获取ContentResolver对象
        ContentResolver resolver = getContentResolver();
        Uri uri = Uri.parse("content://aaa.bbb.ccc");
        //注册内容观察者
        resolver.registerContentObserver(uri,true,new MyObserver(new Handler()));
    }
    //自定义的内容观察者
    public class MyObserver extends ContentObserver {
        //构造方法
        public MyObserver(Handler handler){
            super(handler);
        }
        //当内容观察者观察到数据库的内容发生变化时调用该方法
        public void onChange(boolean selfChange){
            super.onChange(selfChange);
            Toast.makeText(ContentObserverTest.this, "数据库内容变化了", Toast.LENGTH_SHORT).show();
            Uri uri = Uri.parse("content://aaa.bbb.ccc");
            //获取ContentResolver对象
            ContentResolver resolver = getContentResolver();
            //通过ContentResolver对象查询出变化的数据
            Cursor cursor = resolver.query(uri,new String[]{"address","date","type","body"},null,null,null);
            cursor.moveToFirst();
            String address = cursor.getString(0);
            String body = cursor.getString(3);
            Log.v("MyObserver",body);
            cursor.close();
        }
    }
}

     

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Android)