Android:ContentProvider 数据存取

ContentProvider 机制

Android 系统对应用程序的数据文件设置了读写权限,为了实现跨应用访问数据的功能,Android 提供了 ContentProvider 机制。ContentProvider 是 Android 的四大组件之一,用于对外共享数据,实现跨应用数据共享。ContentProvider 应用程序组件结合文件权限机制,有保护地开放自己的数据给其他应用程序使用。

ContentProvider 的功能

  1. 访问系统资源:开发者利用 ContentProvider 可以访问系统提供的数据,如通讯录、音频、视频、图片等,相当于是中间人,真正的数据源是文件或者 SQLite 等。
  2. 共享自定义资源:开发者可以在应用中编写自定义的 ContentProvider,把自己的数据提供给其他应用访问,也可以在获取写入权限后将数据添加到一个已存在的 ContentProvider。

数据模型

ContentProvider 将其存储的数据以数据表的形式提供给访问者,数据表中每一行为一条记录,每一列为具有特定类型和意义的数据。每一条数据记录都包括一个“ID”数值字段,唯一标识一条数据。ContentProvider 返回的数据结构是 Cursor 对象,即结果集合,类似于 JDBC 中的 ResultSet。

URL

URL 的组成

Universal Resource Identifier(通用资源标志符,URI)代表要操作的数据,Android的每种资源(如图像、视频等)都可以用 URI 来表示。每一个 Content Provider 都对外提供一个能够唯一标识自己数据集(data set)的公开 URI,一个数据源含有多个内容(如多个表),就需要用不同的 URI 进行区分。
URI 由三部分组成:访问资源的命名机制(schema)、存放资源的主机名和资源自身的名称。例如:

content://com.example.provider.NoteProvider/notes/3
组成部分 字符串 说明
访问资源的命名机制 content:// 表示由 ContentProvider 控制数据
存放资源的主机名 com.example.provider.NoteProvider 用来定位 ContentProvider 的唯一标识符
资源自身的名称 /notes/3 为每个 ContentProvider 内部的路径部分,指向一个对象集合,通常是表的名字

如“:/notes”表示一个笔记集合,返回表中的全部记录,如果后续还有路径,则指向特定的记录,如“:/notes/3表”示 id 为 3 的笔记。

创建 URL

URL 的创建使用 Uri.parse()方法,直接给定字符串即可。例如:

Uri uri = Uri.parse("content://com.example.mycontact /people");

操作 URL

Android 系统提供了两个用于操作 URI 的工具类,分别为 UriMatcher 和 ContentUris。

UriMatcher 类

UriMatcher 类的作用是提取数据表,进行下一步的数据操作。比起手动过滤字符串来,使用 UriMatcher 不仅简单,而且可维护性较好。
初始化 UriMatcher 类的构造方法如下,其中参数匹配码是一个大于零的整数(表示匹配根路径),或常量 UriMatcher.NO_MATCH(整数 -1,表示不匹配根路径)。

UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);

注册需要的 URI,增加其他 URI 匹配路径的方法如下,第一个参数为 AUTHORITY 字符串,第二个参数为需要匹配的路径,第三个参数必须为一个大于零的匹配码。

URI_MATCHER.addURI(AUTHORITY, TABLE_A, TABLE_A_MSG);

ContentUris 类

ContentUris 类的作用是获取 URI 路径后面的 ID 部分,通过 withAppendedId() 方法,为该 Uri 加上 ID:

Uri resultUri = ContentUris.withAppendedId(uri, 10);

也可以通过 parseId(uri) 从路径中获取 ID,例如:

Uri uri = Uri.parse("content://com.example.mycontact/people/10")
long personid = ContentUris.parseId(uri);

ContentProvider

ContentProvider 类实现了一组标准的方法接口,让其他的应用通过该接口来操作该应用程序的内部数据。ContentProvider 的主要方法如下:

public boolean onCreate();                                      //在创建 ContentProvider 时调用
public Cursor query(Uri,String[], String, String[], String);    //查询指定 Uri 的 ContentProvider,返回一个 Cursor
public Uri insert(Uri,Content Values);                          //添加数据到指定 Uri 的 ContentProvider 中
public int update(Uri,ContentValues,String,String[]);           //更新指定 Uri 的 ContentProvider 中的数据
public int delete(Uri,String,String[]);                         //从指定Uri 的ContentProvider 中删除数据
public String getType(Uri);                                     //返回指定的 Uri 中的数据的 MIME 类型

大多数 ContentProvider 定义了契约类来包含它们所用到的 MIME 类型,例如联系人 Provider 的契约类 ContactsContract.RawContacts 定义了常量 CONTENT_ITEM_TYPE,它对应于一行原始的联系人数据。

URI 说明
ContactsContract.Contacts.CONTENT_URI 管理联系人
ContactsContract.CommonDataKinds.Phone.CONTENT_URI 管理联系人的电话
ContactsContract.CommonDataKinds.Email.CONTENT_URI 管理联系人的电子邮件
ContactsContract.Contacts.DISPLAY NAME 联系人中显示的姓名
ContactsContract.Contacts.HAS_PHONE_NUMBER 联系人中是否存在电话
ContactsContract.Data.CONTACT_ID 联系人中显示的 ID
ContactsContract.CommonDataKinds.Phone.NUMBER 联系人中显示的电话号码
ContactsContract.CommonDataKinds.Phone.TYPE 联系人中显示的电话号码类型(办公、家庭等)
ContactsContract.CommonDataKinds.Email.DATA 联系人中显示的电子邮件
ContactsContract.CommonDataKinds.Email.TYPE 联系人中显示的电子邮件类型(个人、机构等)

ContentResolver

ContentProvider 的工作原理是单例模式,系统只有一个 ContentProvider 实例,ContentProvider 的用户不能直接访问。想要访问这个实例,必须通过 ContentResolver 来实现对 ContentProvider的操作,令它可以和处于多个程序、多个进程中的 ContentResolver 对象进行通信。
为了实现对数据的操作,ContentResolver 提供的接口与 ContentProvider 提供的接口是一一对应的,主要包括以下方法。
的方法是 getContentResolver();

ContentResolver cr = getContentResolver();                                   //获取一个 ContentResolver 的实例
query(Uri uri,String[] projection, String selection,String[] selectionArgs, String sortOrder)
                                                                             //通过 Uri 进行查询,返回一个 Cursor
insert(Uri uri,Content Values values);                                      //将一组数据插入到 Uri 指定位置。
update(Uri uri,ContentValues values,String where,Stringll selectionArgs);    //更新 Uri 指定位置的数据。
delete(Uri uri,String where,String[] selectionArgs);                         //删除指定 Uri 并且符合一定条件的数据。

ContentProvider 样例

程序需求

输入要拨打的电话号码,根据电话号码查询姓名,如果此号码在通讯录中,则显示电话主人的姓名,否则显示陌生人字样。

代码编写

activity_main

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:gravity="center"
            android:text="拨出电话:" />

        <EditText
            android:id="@+id/editText1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:ems="11" >
        </EditText>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="拨号" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textSize="20dp"
            android:text="" />
    </LinearLayout>

</LinearLayout>

MainActivity

当用户输入电话的时候,首先需要用正则表达式判断电话号码是否合法。如果合法则通过 ContentProvider 获取联系人信息,通过返回的 Cursor 对象查找是否有这个联系人的名字。如果有就显示这个联系人,没有就显示陌生人,最后用 intent 机制拨打出电话。

package com.example.liaison;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract.Data;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

public class MainActivity extends AppCompatActivity {
    private EditText editText1;
    private TextView textView2;
    private Button button1;
    String phoneNumber = "";
    String displayName = "";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        editText1 = (EditText) findViewById(R.id.editText1);
        textView2 = (TextView) findViewById(R.id.textView2);

        button1 = (Button) findViewById(R.id.button1);
        button1.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                phoneNumber = editText1.getText().toString();
                // 输入的电话号码合法
                if (isPhoneLegal(phoneNumber)) {
                    // 根据电话号码查找联系人
                    Uri uri = Uri.parse("content://com.android.contacts/data/phones/filter/" + phoneNumber);
                    ContentResolver resolver = getContentResolver();
                    Cursor cursor = resolver.query(uri, new String[] {Data.DISPLAY_NAME}, null, null, null);
                    // 判断号码是否在通讯录
                    if (cursor.moveToFirst()) {
                        textView2.setText("你将打给:" + cursor.getString(0)); // 显示name
                    } else {
                        textView2.setText("你将打给:陌生人");
                    }
                    // 使用intent拨打电话
                    Intent intent = new Intent();
                    intent.setAction(Intent.ACTION_CALL);
                    intent.setData(Uri.parse("tel:" + phoneNumber));
                    startActivity(intent);
                }
                // 输入的电话号码合法
                else{
                    textView2.setText("请输入正确的电话号码!");
                }
            }
        });

    }

    // 判断
    public static boolean isPhoneLegal(String str) throws PatternSyntaxException {
        String regExp = "^((13[0-9])|(14[5,7,9])|(15[0-3,5-9])|(166)|(17[3,5,6,7,8])|(18[0-9])|(19[8,9]))\\d{8}$";
        Pattern p = Pattern.compile(regExp);
        Matcher m = p.matcher(str);
        return m.matches();
    }
}

申请权限

需要申请读取联系人的权限和打电话的权限:

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

运行效果

首先在联系人当中添加测试数据:

电话号码在联系人中:


电话不在联系人列表中:


输入非法的字符串:

参考资料

《Android 移动应用开发》,杨谊 主编、喻德旷 副主编,人民邮电出版社

posted @ 2022-01-30 00:02  乌漆WhiteMoon  阅读(692)  评论(0编辑  收藏  举报