欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

内容提供者(ContentProvider)

程序员文章站 2022-07-08 10:42:45
...

ContentProvider是Android四大组件中唯一不需要使用Intent来启动的组件

一般来说,应用的私有数据是不允许其他应用访问的,而ContentProvider的作用就是把应用的私有数据暴露给其他应用访问。使用ContentProvider可以自己定义访问规则,选择哪些私有数据暴露出去,哪些不暴露。

自定义ContentProvider(很少使用)

自定义一个ContentProvider将friend数据库中的私有数据暴露出去,步骤非常简单:

  1. 定义MyFriendProvider继承自ContentProvider,实现onCreate()insert()delete()update()query()getType(),代码如下

    /**
     * Created by wangshiyi on 2016/11/18.
     */
    
    public class MyFriendProvider extends ContentProvider {
    
        private static final String AUTHORITY = "edu.neu.steve.provider.friend";
        private static final int URI_CODE_CLASSMATES = 1;
        private static final int URI_CODE_TEAMMATES = 2;
        private static final int URI_CODE_CLASSMATES_QUERY_BY_ID = 3;
    
        private SQLiteDatabase db;
    
        // 创建URI匹配器,用于判断一条URI跟指定的多条URI中的哪条匹配从而实现操作不同的表
        private static UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    
        // 初始化块在构造器之前执行,在这里添加匹配规则
        static {
            // arg0:指定ContentProvider的authorities
            // arg1:路径
            // arg2:匹配码
            sUriMatcher.addURI(AUTHORITY, "classmates", URI_CODE_CLASSMATES);// content://edu.neu.steve.provider.friend/classmates
            sUriMatcher.addURI(AUTHORITY, "teammates", URI_CODE_TEAMMATES);  // content://edu.neu.steve.provider.friend/teammates
            sUriMatcher.addURI(AUTHORITY, "classmates/#", URI_CODE_CLASSMATES_QUERY_BY_ID);// content://edu.neu.steve.provider.friend/classmates/数字,多用于具体查询
        }
    
        /**
         * 第一次创建ContentProvider时回调,在Application的onCreate()之前执行
         * 一般在这里打开数据库
         *
         * @return
         */
        @Override
        public boolean onCreate() {
            MyOpenHelper oh = new MyOpenHelper(getContext(), "friend.db", null, 1);
            db = oh.getWritableDatabase();  // 打开friend数据库
            return false;
        }
    
        /**
         * 把数据插入数据库,values为其他应用要插入的数据
         *
         * @param uri
         * @param values
         * @return
         */
        @Nullable
        @Override
        public Uri insert(Uri uri, ContentValues values) {
            switch (sUriMatcher.match(uri)) {
                case URI_CODE_CLASSMATES:
                    db.insert("classmates", null, values);    // 向classmates表中插入数据
                    break;
                case URI_CODE_TEAMMATES:
                    db.insert("teammates", null, values);     // 向teammates表中插入数据
                    break;
                default:
                    throw new IllegalArgumentException("无匹配的URI");
            }
            return uri;
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            int i = 0;
            switch (sUriMatcher.match(uri)) {
                case URI_CODE_CLASSMATES:
                    i = db.delete("classmates", selection, selectionArgs); // 从classmates表中删除数据
                    break;
                case URI_CODE_TEAMMATES:
                    i = db.delete("teammates", selection, selectionArgs);  // 从teammates表中删除数据
                    break;
                default:
                    throw new IllegalArgumentException("无匹配的URI");
            }
            return i;
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
            int i = 0;
            switch (sUriMatcher.match(uri)) {
                case URI_CODE_CLASSMATES:
                    i = db.update("classmates", values, selection, selectionArgs);  // 更改classmates表中的数据
                    break;
                case URI_CODE_TEAMMATES:
                    i = db.update("teammates", values, selection, selectionArgs);   // 更改teammates表中的数据
                    break;
                default:
                    throw new IllegalArgumentException("无匹配的URI");
            }
            return i;
        }
    
        @Nullable
        @Override
        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            Cursor cursor = null;
            switch (sUriMatcher.match(uri)) {
                case URI_CODE_CLASSMATES:
                    cursor = db.query("classmates", projection, selection, selectionArgs, null, null, sortOrder, null);// 查询classmates表中的数据
                    break;
                case URI_CODE_TEAMMATES:
                    cursor = db.query("teammates", projection, selection, selectionArgs, null, null, sortOrder, null); // 查询teammates表中的数据
                    break;
                case URI_CODE_CLASSMATES_QUERY_BY_ID:
                    long id = ContentUris.parseId(uri); // 取出URI末尾携带的数字,作为查询的id
                    cursor = db.query("classmates", projection, "_id = ?", new String[]{id + ""}, null, null, sortOrder, null); // 查询classmates表中指定id的数据
                    break;
                default:
                    throw new IllegalArgumentException("无匹配的URI");
            }
            return cursor;
        }
    
    
        /**
         * @param uri
         * @return 返回通过指定uri获取的数据的MIME类型
         */
        @Nullable
        @Override
        public String getType(Uri uri) {
            String type = null;
            switch (sUriMatcher.match(uri)) {
                case URI_CODE_CLASSMATES:
                    type = "vnd.android.cursor.dir/classmates"; // 返回classmates的多条数据
                    break;
                case URI_CODE_TEAMMATES:
                    type = "vnd.android.cursor.dir/teammates";  // 返回teammates的多条数据
                    break;
                case URI_CODE_CLASSMATES_QUERY_BY_ID:
                    type = "vnd.android.cursor.item/classmates";// 返回classmates的单条数据
                    break;
                default:
                    break;
            }
            return type;
        }
    }
    

上面代码在insert()delete()update()query()方法中实现增删改查数据库的代码,供其他应用中的ContentResolver间接调用。

  1. 在清单文件中配置MyFriendProvider

    <provider
        android:name=".MyFriendProvider"
        android:authorities="edu.neu.steve.provider.friend"
        android:exported="true">
    </provider>
    
    • authorities属性是ContentProvider的主机名(URI),功能类似于地址
    • exported属性设置为true指明该ContentProvider允许被其它应用通过authorities获取
  2. 在其他应用中,获取ContentResolver通过URI访问自定义的MyFriendProvider,实现对friend数据库的操作

在实际开发中很少会用到自定义ContentProvider,因为作为一个普通的第三方应用,你所暴露的私有数据通常没有人感兴趣,除非你是给平台做接口的


系统提供的ContentProvider

实际上,Android系统为开发者提供了大量的ContentProvider,例如短信信息、联系人信息、系统的多媒体信息等,开发者自己开发的APP也可以通过ContentResolver来间接调用系统提供的ContentProvider所实现的insert()delete()update()query()方法,这样开发者就可以获取到Android内部数据了。

获取短信

Android系统提供了Telephony应用程序(com.android.providers.telephony)来管理短信,而且还为短信管理提供了相应的ContentProvider,其他应用程序可以通过ContentResolver访问管理短信的ContentProvider,间接操作短信数据库mmssms.db,即可实现管理短信。

管理短信只涉及短信数据库的sms表,URI为content://sms,通常我们只关注表中的4个字段:

  • address:对方号码
  • date:短信时间
  • type:短信类型,1为收到,2为发送
  • body:短信内容

下面的代码简单的实现了获取全部短信

ContentResolver resolver = getContentResolver();
Cursor cursor = resolver.query(Uri.parse("content://sms"), new String[]{"address", "date", "type", "body"}, null, null, null);
while (cursor.moveToNext()) {
    String address = cursor.getString(0);
    long date = cursor.getLong(1);
    int type = cursor.getInt(2);
    String body = cursor.getString(3);
    System.out.println(address + ";" + date + ";" + type + ";" + body);
}

不要忘记在清单文件中注册读短信权限

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

为了防止第三方应用胡乱写入短信记录,Android 4.4之后只有默认的短信应用才有权限写入短信数据库

获取联系人

Android系统提供了Contacts应用程序(com.android.providers.contacts)来管理联系人,也为联系人管理提供了相应的ContentProvider,其他应用程序同样可以通过ContentResolver访问管理联系人的ContentProvider,间接操作联系人数据库contacts2.db,即可实现管理联系人。

下面列出了管理联系人主要涉及的联系人数据库中的三张表,以及表中需要关注的字段:

  • raw_contacts表

    • contact_id:联系人ID
  • data表:保存联系人的详细信息,一条信息占一行,而不是一个联系人占一行

    • data1:联系人信息的具体内容
    • raw_contact_id:联系人ID,标识该条信息属于哪个联系人
    • mimetype_id:标识该条信息属于什么MIME类型
  • mimetypes表:保存各个mimetype_id对应的MIME类型(姓名、号码、邮箱...)

下面的代码简单的实现了获取全部联系人到JavaBean并输出

ContentResolver resolver = getContentResolver();
// 先查询raw_contacts表获取所有联系人ID(/raw_contacts表示查询raw_contacts表)
Cursor cursorContactId = resolver.query(Uri.parse("content://com.android.contacts/raw_contacts"),
        new String[]{"contact_id"},
        null, null, null);
while (cursorContactId.moveToNext()) {
    String contactId = cursorContactId.getString(0);
    // 使用联系人ID作为where条件去查询属于该联系人的信息(/data实际上是多张表的复合查询)
    Cursor cursorContactInfo = resolver.query(Uri.parse("content://com.android.contacts/data"),
            new String[]{"data1", "mimetype"},
            "raw_contact_id = ?",
            new String[]{contactId}, 
            null);
    ContactBean contact = new ContactBean();    // 联系人JavaBean
    while (cursorContactInfo.moveToNext()) {
        String data1 = cursorContactInfo.getString(0);
        String mimeType = cursorContactInfo.getString(1);
        switch (mimeType) {   // 根据不同的MIME类型把联系人信息保存到JavaBean
            case "vnd.android.cursor.item/name":
                contact.setName(data1);
                break;
            case "vnd.android.cursor.item/phone_v2":
                contact.setPhone(data1);
                break;
            case "vnd.android.cursor.item/email_v2":
                contact.setEmail(data1);
                break;
            default:
                break;
        }
        System.out.println(contact);
    }
}

同样不要忘记在清单文件中注册读联系*限

<uses-permission android:name="android.permission.READ_CONTACTS"></uses-permission>
添加联系人

添加联系人,只需要向上面的三张表中插入新联系人的数据。
向raw_contacts表中插入新联系人的contact_id之前,需要先遍历这个表,以确定新联系人的contact_id是多少,注意这里不应该用已有的contact_id(如果用户删除了某个联系人,这个字段会置空)来确定新联系人的contact_id,只能依据主键_id来确定。新联系人的contact_id插入完成之后,我们就可以插入具体的联系人信息。
该过程的完整的代码如下:

 ContentResolver resolver = getContentResolver();
 // 先查询目前最新的联系人的主键_id,新联系人的ID在此基础上加1即可得到
 Cursor cursorId = resolver.query(Uri.parse("content://com.android.contacts/raw_contacts"),
         new String[]{"_id"},
         null, null, null);
 int _id = 0;
 if (cursorId.moveToLast()) {
     _id = cursorId.getInt(0);
 }
 int contactId = _id + 1;
 // 向raw_contacts表中插入新联系人ID
 ContentValues contentValues = new ContentValues();
 contentValues.put("contact_id", contactId);
 resolver.insert(Uri.parse("content://com.android.contacts/raw_contacts"), contentValues);
 
 // 插入具体的联系人信息
 // 插入名字
 contentValues.clear();
 contentValues.put("raw_contact_id", contactId);
 contentValues.put("data1", et_name.getText().toString());
 contentValues.put("mimetype", "vnd.android.cursor.item/name");
 resolver.insert(Uri.parse("content://com.android.contacts/data"), contentValues);
 
 // 插入号码
 contentValues.clear();
 contentValues.put("raw_contact_id", contactId);
 contentValues.put("data1", et_phone.getText().toString());
 contentValues.put("mimetype", "vnd.android.cursor.item/phone_v2");
 resolver.insert(Uri.parse("content://com.android.contacts/data"), contentValues);

这里需要在清单文件中注册读联系*限和写联系*限

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

内容观察者(ContentObserver)

前面介绍的是当ContentProvider将数据暴露出来之后,ContentResolver会根据业务需要去主动查询ContentProvider所暴露的数据。有些时候,应用程序需要实时监听ContentProvider所暴露数据的变化,并做出响应,这就需要利用ContentObserver了。

ContentProvider在实现insert()delete()update()时,会导致所暴露的数据发生改变,这时会调用如下代码通知所有注册在相应URI上的ContentObserver

getContext().getContentResolver().notifyChange(uri, null);

系统提供的ContentProvider已经在相应位置实现了这行代码,而我们自定义的ContentProvider就需要自己去实现

为了监听短信数据库的变化,定义一个SmsObserver继承ContentObserver,并重写onChange()

class SmsObserver extends ContentObserver {

    public SmsObserver(Handler handler) {
        super(handler);
    }

    /**
     * 当所监听的数据发生改变时回调
     *
     * @param selfChange
     */
    @Override
    public void onChange(boolean selfChange) {
        System.out.println("短信数据库改变了");
    }
}

然后注册这个SmsObserver,即可实现监听短信数据库的变化

// 第一个参数为URI,指定接收哪个ContentProvider发出的通知
// 第二个参数若为true,以第一个参数作为开头(附加任意子路径)的URI上的数据改变了,该ContentObserver也会收到通知
getContentResolver().registerContentObserver(Uri.parse("content://sms"), 
        true, 
        new SmsObserver(new Handler());

这里同样需要在清单文件中注册读短信权限

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