Android初学之十八:内容提供器

一、内容提供器简介

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

二、运行时权限

1、Android的权限机制详解
(1)、普通权限:指的是那些不会直接威胁到用户的安全和隐私的权限,对这部分权限的申请,系统会自动帮我们进行授权,而不需要用户再去手动操作了。
(2)、危险权限:则表示那些可能会触及用户或者设备安全性造成影响的权限,如获取设备联系人信息,定位设备地理位置等,对这部分权限申请,必须要由用户手动点击授权才可以,否则程序就无法使用相应的功能。

下表列出了Android中所有危险的权限,一共是9组24个权限
在这里插入图片描述

官网:http://developer.android.google.cn/reference/android/Manifest.permission.html可以查看Android系统中完整的权限列表。

三、在程序运行时申请权限

示例:CALL_PHONE这个权限是编写拨打电话功能的时候需要声明的,因为拨打电话会涉及用户手机的资费问题,因而被列为了危险权限,下面我们定一个按钮,当点击按钮时就去触发直接拨打电话的逻辑:
1、添加直接拨打电话的权限,第五行
在这里插入图片描述
2、新建一个直接拨打电话的按钮
在这里插入图片描述

四、访问其他程序中的数据

1、内容提供器的用法一般有 两种,一种是使用现有的内容提供器来读取和操作相应程序中的数据,另一种是创建自己的内容提供器给我们程序的数据提供外部访问接口。那么接下来我们就一个个开始学习吧,首先从使用现有的内容提供器开始。
2、如果个应用程序通 过内容提供器对其数据提供了外部访问接口,那么任何其他的应用程序就都可以对这部分数据进行访问。Android 系统中自带的电话簿、短信、媒体库等程序都提供了类似的访问接口,这就使得第三方应用程序可以充分地利用这部分数据来实现更好的功能。下面我们就来看一看,内容提供器到底是如何使用的。

五、ContentResolver的基本用法

1、对于每一个应用程序来说,如果想要访问内容提供器中共享的数据,就一定要
借助ContentResolver类,可以通过Context中的getContentResolver()方法获取到该类的实例。ContenResolver中提供了系列的方法用
于对数据进行CRUD操作,其中insert()方法用于添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数据。
2、不同于SQLiteDatabase,ContentResolver 中的增删改查方法都是不接收表名参数的,而是使用一个Uri参数代替,这个参数被称为内容URI。内容URI给内容提供器中的数据建立了唯一标识符,它主要由两部分组成: authority 和path。 authority 是用于对不同的应用程序做区分的,一般为了 避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名是com.example.app,那么该程序对应的authority就可以命名为com.example.app. provider。path 则是用于对同一应用程序中不同的表做区分的,通常都会添加到authority的后面。比如某个程序的数据库里存在两张表: tablel和table2,这时就可以将path分别命名为/tablel和/table2,然后把authority和path进行组合,内容URI就变成了com.example app povider/tablel和com. example. app.pprovider/table2
不过,目前还很难辨认出这两个字符串就是两个内容URI,我们还需要在字符串的头部加上协议声明。因此,内容URI最标准的格式写法如下:
content://com. example. app. provider/table1
content://com. example. app. provider/table2
3、有没有发现,内容URI可以非常清楚地表达出我们]想要访问哪个程序中哪张表里的数据。也正是因此,ContentResolver 中的增删改查方法才都接收Uri对象作为参数,因为如果使用表名的话,系统将无法得知我们期望访问的是哪个应用程序里的表。
在得到了内容URI字符串之后,我们还需要将它解析成Uri对象才可以作为参数传入,解析的方法如下:

Uri uri = Uri.parse("content://com.example.app.provider/table")
只需要调用Uri.parse()方法,就可以将内容URI字符串解析成Uri对象了。
现在我们就可以使用这个Uri对象来查询tabel表中的数据了,代码如下:
Cursor cursor = getContentResolver().query(
uri,
projection,
selection,
selectionArgs,
sortOder);

这些参数和SQLiteDatabase中query()方法里的参数很像,但总体来说要简单一些,毕竟这是在访问其他程序中的数据,没必要构建过于复杂的查询语句,下表对使用到的这部分参数进行了详细的解释。

在这里插入图片描述

五、添加、更新、删除数据

1、向table表中添加一条数据

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

可以看到,仍然是将添加的数据组装到ContentValues中,然后调用ContentResolver的insert()方法,将Uri和ContentValues作为参数传入即可。
2、现在如果我们想要更新这条新添加的数据,把column1的值清空,可以借助ContentResolver的update()方法实现,代码如下:

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

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

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

六、读取系统联系人

1、添加ListView控件的布局
在这里插入图片描述

2、接着在清单文件添加读取系统联系人的权限

在这里插入图片描述
3、修改代码:
在这里插入图片描述

七、创建自己的内容提供器的步骤

如果想要实现跨程序共享数据的功能,官方推荐的方式就是使用内容提供器,可以通过新建一个类去继承ContentProvider的方式来创建一个自己的内容提供器,ContentProvider类中有6个抽象方法,我们在使用子类继承它的时候,需要将这6个方法全部重写,新建MyProvider继承自ContentProvider

在这里插入图片描述

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

在这里插入图片描述

getType()方法,它是所有的内容提供器都必须提供的一个方法,用于获取Uri对象所对应的MIME类型,一个内容URI所对应的MIME字符串主要由3部分组成,Android对这三个部分做了如下格式规定:
1、必须以vnd开头
2、如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/
3、最后接上vnd.<authority>.<path>
所以,对于content://com.example.app.provider/table1这个内容URI,它所对应的MIME类型就可以写成:
vnd.android.cursor.dir/vnd.com.example.app.provider.table1
对于content://com.example.app.provider/table1/1这个内容URI,它所对应的MIME类型就可以写成:
vnd.adnroid.cursor.item/vnd.com.example.app.provider.table1
现在我们可以继续完善MyProvider中的内容了,这次来实现getType()方法中的逻辑,代码如下所示:
在这里插入图片描述

到这里,一个完整的内容提供器就创建完成了,现在任何一个应用程序都可以使用ContentResolver来访问我们程序中的数据,那么前面所提到的,如何才能保证隐私数据不会泄露出去呢?其实多亏了内容提供器的良好机制,这个问题在不知不觉中已经被解决了,因为所有的CRUD操作都一定要匹配到相应的内容URI格式才能进行的,而我们当然不可能向UriMatcher中添加隐私数据的URI,所以这部分数据根本无法被外部程序访问到,所有安全问题也就不存在了。

八、实现跨程序数据共享

在上面的DatabaseTest进行修改,先把MyDatabaseHelper中使用的Toast弹出数据库创建成功的提示去掉,因为跨程序访问的时候我们不能直接使用Toast,然后创建一个内容提供器,右击“com.example.databasetest包—New—Other—ContentProvider”,
将内容提供器命名DatabaseProvider,authority指定为com.example.databasetest.provider,Exported属性表示是否允许外部程序访问我们的内容提供器,Enabled属性表示是否启用这个内容提供器,将两个属性都选中,点击Finish完成创建。
修改DatabaseProvider的代码

package com.example.databasetest;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

public class DatabaseProvider extends ContentProvider {

    //1、首先在类的一开始,同样是定义了4个常量,分别用于表示访问Book表中的所有数据、访问Book表中的单条数据
    //访问Category表中的所有数据和访问Category表中的单条数据
    public static final int Book_DIR = 0;
    public static final int BooK_ITEM = 1;
    public static final int CATEGORY_DIR = 2;
    public static final int CATEGORY_ITEM = 3;
    public static final String AUTHORITY = "com.example.databasetest.provider";
    private static UriMatcher uriMatcher;

    private MyDatabaseHelper dbHelper;
    //2、然后在静态代码块里对UriMatcher进行了初始化操作,将期望匹配的几种URI格式添加了进去
    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY, "book", Book_DIR);
        uriMatcher.addURI(AUTHORITY, "book/#", BooK_ITEM);
        uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);
    }
    //3、创建了一个MyDatabaseHelper的实例,然后返回true表示内容提供器初始化成功,这时数据库就已经完成了创建和升级操作
    @Override
    public boolean onCreate() {
        dbHelper = new MyDatabaseHelper(getContext(), "BookStore.ab", null, 2);

        return true;
    }
    //4、在这个方法中先获取到了SQLiteDatabase的实例,然后根据传入的Uri参数判断出用户想要访问哪张表,再调用SQLiteDatabase的query()进行
    //查询,并将Cursor对象返回就好了。注意访问单条数据的时候有一个细节,这里调用了Uri对象getPathSegments()方法,它会将内容URI权限之后的部分
    //以“/”符合进行分割,并把分割后的结果将放入到一个字符串列表中,那这个列表的第0个位置存放的就是路径,第1个位置存放的就是id了,得到id之后,
    //再通过selection,selectionArgs参数进行约束,就实现了查询单条数据的功能。
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        //查询数据
        SQLiteDatabase db = dbHelper.getReadableDatabase();
        Cursor cursor = null;
        switch (uriMatcher.match(uri)) {
            case Book_DIR:
                cursor = db.query("Book", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case BooK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                cursor = db.query("Book", projection, "id = ? ", new String[]{bookId}, null, null, sortOrder);
                break;
            case CATEGORY_DIR:
                cursor = db.query("Category", projection, selection, selectionArgs, null, null, sortOrder);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                cursor = db.query("Category", projection, "id = ? ", new String[]{categoryId}, null, null, sortOrder);
                break;
        }
        return cursor;
    }
    public DatabaseProvider() {
    }

    //5、先获取到SQLiteDatabase的实例,然后根据传入的Uri参数判断用户想要往哪张表里添加数据,再调用SQLiteDatabase的insert()方法进行
    //添加就可以了,注意insert()方法要求返回一个能够表示这条新增数据的URI,所以我们还需要调用Uri.parse()方法来将一个内容URI解析成ur对象,
    //当然这个内容URI是以新增数据的id结尾的
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (uriMatcher.match(uri)) {
            case Book_DIR:
            case BooK_ITEM:
                long newBookId = db.insert("Book", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newBookId);
                break;
            case CATEGORY_DIR:
            case CATEGORY_ITEM:
                long newCategoryId = db.insert("Category", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + newCategoryId);
                break;
            default:
                break;

        }
        return uriReturn;
    }


    //6、先获取SQLiteDatabase的实例,然后根据传入的Uri参数判断出用户想要更新哪张表里的数据,再掉用SQLiteDatabase的update()
    //方法进行更新就好了,受影响的行数将作为返回值返回。
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        //更新数据
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int updatedRows = 0;
        switch (uriMatcher.match(uri)) {
            case Book_DIR:
                updatedRows = db.update("Book", values, selection, selectionArgs);
                break;
            case BooK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                updatedRows = db.update("Book", values, "id = ?", new String[]{bookId});
                break;
            case CATEGORY_DIR:
                updatedRows = db.update("Category", values, selection, selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                updatedRows = db.update("Category", values, "id = ?", new String[]{categoryId});
                break;
            default:
                break;
        }
        return updatedRows;
    }

    //7、先获取SQLiteDatabase的实例,然后根据传入的Uri参数判断出用户想要删除哪张表里的数据,再调用SQLiteDatabase的delete()方法
    //进行删除就好了,被删除的行数将作为返回值返回。
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        //删除数据
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        int deletedRows = 0;
        switch (uriMatcher.match(uri)){
            case Book_DIR:
                deletedRows = db.delete("Book",selection,selectionArgs);
                break;
            case BooK_ITEM:
                String bookId = uri.getPathSegments().get(1);
                deletedRows = db.delete("Book","id = ?",new String[] {bookId});
                break;
            case CATEGORY_DIR:
                deletedRows = db.delete("Category",selection,selectionArgs);
                break;
            case CATEGORY_ITEM:
                String categoryId = uri.getPathSegments().get(1);
                deletedRows = db.delete("Category","id = ?",new String[] {categoryId});
                break;
            default:
                break;
        }
        return deletedRows;
    }
    //8、按照上面一节规则所写
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)){
            case Book_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";
            case BooK_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
            case CATEGORY_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";
        }
        return null;
    }






}

需要注意的是,内容提供器一定要在清单文件里进行注册才可以使用,由于我们直接通过快捷键创建的内容提供器已经在清单文件里注册好了。
在这里插入图片描述

<application>标签内出现了一个新的标签<provider>,我们使用它来对DatabaseProvider这个内容提供器进行注册。android:name属性指定了DatabaseProvider的类名,android:authorities属性指定了DatabaseProvider的authority,而enable和exported属性则是根据我们刚才勾选的状态自动生成的,这里表示允许DatabaseProvider被其他应用程序进行访问。
现在DatabaseTest这个项目已经拥有了跨程序共享数据的功能了,现在创建一个新项目ProviderTest,我们通过这个新程序去访问DatabaseTest中的数据:

1、首先现新建4个按钮的布局,插入、删除、更新、查询

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

   <Button
       android:id="@+id/add_data"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="Add to BOOK"/>
    <Button
        android:id="@+id/query_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Query from Book"
        />
    <Button
        android:id="@+id/update_data"
        android:text="Update_book"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />
    <Button
        android:id="@+id/delete_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Delete from book"/>

</LinearLayout>

2、接着我们进行修改代码:

package com.example.providertest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    private String newId;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button addData = findViewById(R.id.add_data);
        //1、添加数据的时候,首先调用了Uri.parse()方法将一个内容URI解析成Uri对象,然后把要添加的数据都存放到ContentValues对象中去
        //接着调用ContentResolver()的insert()方法执行添加操作就可以了,注意insert()方法会返回一个Uri对象,这个对象中包含了
        //新增数据的id,我们通过getPathSegments()方法将这个id取出
        addData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                ContentValues values = new ContentValues();
                values.put("name","名字");
                values.put("author","作家");
                values.put("pages",1040);
                values.put("price",22.85);
                Uri newUri = getContentResolver().insert(uri,values);
                newId = newUri.getPathSegments().get(1);
        }
        });
        //2、查询数据的时候,同样是调用了Uri.parse()方法将一个内容URI解析成Uri对象,然后调用ContentResolver的query()
        //的方法去查询数据,查询的结果当然还是存放在Cursor对象中的。之后对Cursor进行遍历,从中取出查询结果,并一一打印出来
        Button queryData = findViewById(R.id.query_data);
        queryData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book");
                Cursor cursor = getContentResolver().query(uri,null,null,null,null);
                if (cursor != null){
                    while (cursor.moveToNext()){
                        String name = cursor.getString(cursor.getColumnIndex("name"));
                        String author = cursor.getString(cursor.getColumnIndex("author"));
                        Double pages = cursor.getDouble(cursor.getColumnIndex("pages"));
                        Double price = cursor.getDouble(cursor.getColumnIndex("price"));
                        Log.d("MainActivity",name);
                        Log.d("MainActivity",author);
                        Log.d("MainActivity", String.valueOf(pages));
                        Log.d("MainActivity", String.valueOf(price));
                    }
                }
                cursor.close();
            }
        });
        //3、更新数据的时候,也是将内容URI解析成Uri对象,然后把想要更新的数据存放到ContentValues对象中,再调用ContentResolver的
        //update()方法执行更新数据操作就可以了,注意这里我们不想让Book表中的其他行受到影响,在调用Uri.parse()方法时,给内容URI的尾部
        //增加了一个id,而这个id正是添加数据时返回的。这就是我们只希望更新刚刚添加的那条数据,Book表中的其他行不会受到影响。
        Button updateData = findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/"+newId);
                ContentValues values = new ContentValues();
                values.put("name","大作家");
                values.put("pages",1299);
                values.put("price",21.22);
                getContentResolver().update(uri,values,null,null);

            }
        });
        //4、删除数据的时候,也是使用同样的方法解析了一个id结尾的内容URI,然后调用ContentResolver的delete()方法
        //执行删除操作就可以了。由于我们在内容指定了一个id,因此只会删掉拥有相应id的那行数据,Book表中的其他数据都不会受到影响
        Button deleteData = findViewById(R.id.delete_data);
        deleteData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Uri uri = Uri.parse("content://com.example.databasetest.provider/book/"+newId);
                getContentResolver().delete(uri,null,null);
            }
        });
    }
}

根据郭霖大神的第一行代码学习,敬礼!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值