内容提供器(Content Provider)主要用于在不同的应用程序之间共享数据,虽然Android提供的文件存储和SharePreferences都可以设置全局读写模式共享数据,但由于安全性问题,已经在Android4.2中被废弃。内容提供器将提供一种安全的数据共享方式。当一个程序通过内容提供器提供了外部访问接口,其他任何程序都可以通过该接口来对这部分数据进行访问,例如:联系人数据,短信数据。
一、访问其他应用程序中的数据
1 . 获得ContentResolver类的实例
对于每一个应用程序,如果想要访问内容提供器中的数据,就必须要借助于ContentResolver类,可以通过Context中的getContentResolver()放法获得该类的实例。
ContentResolver类中提供了类似于SQLite中类似的insert()、delete()、update()、query()等方法实现对数据的增删改查,但是参数与SQLite中的参数略有不同。
注:ContentResolver中的增删改查方法都是不接受表名参数的,而是使用Uri参数代替,这个参数被称为内容URI。
2 . 获得内容URI
内容URI给内容提供器中的参数建立了唯一标识符,它主要由权限和路径两部分组成,权限主要是为了区分不同的应用程序,一般使用程序包名。比如:com.example.app.provider。路径则是对同一应用程序中不同的表进行区分,以/+表名的方式接在权限的后面。最后我饿还需要加上协议声明才组成了完整的内容URI。
内容URI标准格式
content://com.example.app.provider/table1
content://com.example.app.provider/table2
3 . 将URI字符串解析为Uri对象
Uri uri = Uri.parse("content://com.example.app.provider/table1");
4 . 对数据进行增删改查
//插入数据
ContentValues values1 = new ContentValues();
values1.put("column1", "text1");
values1.put("column2", "text2");
getContentResolver().insert(uri, values1);
//删除数据
getContentResolver().delete(uri, "cloumn2=?", new String[]{"1"});
//修改数据
ContentValues values2 = new ContentValues();
values2.put("column1", "t");
getContentResolver().update(uri, values2, "column1 = ? and column2=?", new String[]{"text", "1"});
//查询数据
Cursor cursor = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
if (cursor != null){
while (cursor.moveToNext()){
String column1 = cursor.getString(cursor.getColumnIndex("column1"));
int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
}
cursor.close();
}
上面的操作和SQLite的操作十分类似,只是传入的参数不再是表名,而是解析出来的Uri对象。
下面重点介绍query()中各参数的意义
query方法参数 | 对应SQL部分 | 描述 |
---|---|---|
uri | from table_name | 指定要查询的应用程序下的某一张表 |
projection | select column1,column2 | 指定查询的列名 |
selection | where column=value | 指定where的约束条件 |
selectionArgs | - | 为where中的占位符提供具体的值 |
orderBy | order by column1,column2 | 指定查询结果的排序方式 |
下面放上一个获取联系人信息的小例子
MainActivity.java中的代码
public class MainActivity extends AppCompatActivity {
private ListView contactsView;
private ArrayAdapter<String> adapter;
private List<String> contactsList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
contactsView = (ListView) findViewById(R.id.contacts_view);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contactsList);
contactsView.setAdapter(adapter);
readContacts();
}
private void readContacts() {
Cursor cursor = null;
try{
//查询联系人数据
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
while(cursor.moveToNext()){
//获取联系人姓名
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
//获取联系人手机号
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName + "\n" + number);
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (cursor != null){
cursor.close();
}
}
}
}
activity_main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/contacts_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello World!" />
</LinearLayout>
上面的代码中的Uri对象并不是通过parse()方法解析,是因为官方向我们直接提供了解析好的Uri对象。我们获取到手机联系人姓名和电话号码后简单地显示在了ListView中。
二、创建自己的内容提供器
上面我们使用别人提供的内容提供器来获取其他程序共享的数据,下面我们将创建自己的内容提供器来共享自己应用程序中的数据。下面是基本的步骤
1 . 创建一个类去继承ContentProvider,重写其中的6个抽象方法。
下面简单介绍这6个方法(与SQLite中的方法很类似)
1 . onCreate() ——- 初始化内容提供器的时候调用。通常在这里完成数据库的额创建或升级等操作。返回true表示内容提供器初始化成功,返回false表示失败。
注:只有当有ContentResolver尝试访问我们程序中的数据时,内容提供器才会被初始化。
2 . query() —–从内容提供器中来查询数据,query方法的参数已经在上面的表格中做了详细说明。
3 . insert() —– 向内容提供器中添加一条数据,使用方法咋在上面的代码中已经有介绍。
4 . update() —– 更新内容提供器中已有的数据。
5 . delete() —– 从内容提供器中删除数据
6 . getType() —– 根据传入的URI来返回响应的MIME类型。
2 . 分析内容URI。
标准的内容URI(以路径结尾,表示期望访问表中的所有数据):
content://com.example.app.provider/table
含有id的内容URI(以id结尾,表示访问表中拥有相应id的数据):
content://com.example.app.provider/table/1
为了下一步准确解析这两种内容URI,我们还必须使用通配符来匹配上面两种模式。
1 . * ——- 表示匹配任意长度的任意字符
2 . # ——- 表示匹配任意长度的数字
//匹配上面第一行内容URI(所有数据)
content://com.example.app.provider/table/*
//匹配上面第二种URI(相应id的数据)
content://com.example.app.provider/table/#
3 . 借助于UriMatcher类实现匹配内容URI。
上面我们已经分析了内容URI,接下来我们借助于UriMatcher类提供的addURI()方法,分别依次传入三个参数(权限、路径,自定义代码(标识))。最后我们再调用UriMatcher的match()方法,传入一个Uri对象,就会返回我们刚刚传入的自定义代码(标识),我们以此来判断调用方想访问数据的意图。
public class MyProvider extends ContentProvider {
//自定义代码
public static final int TABLE1_DIR = 0;
public static final int TABLE1_ITEM = 1;
private static UriMatcher uriMatcher;
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//期望查询所有数据
uriMatcher.addURI("com.example.app.provider", "table1", TABLE1_DIR);
//期望查询相应id的数据
uriMatcher.addURI("com.example.app.provider", "table1/#", TABLE1_ITEM);
}
@Nullable
@Override
public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
switch (uriMatcher.match(uri)){
case TABLE1_DIR:
//查询所有数据
break;
case TABLE1_ITEM:
//查询单条数据
break;
default:
break;
}
return null;
}
}
上面的代码只是简单演示了在query()方法中的重写,对于insert()、update()、delete()中的重写都是类似的,都是从Uri对象中获得自定义代码,得到访问数据者的意图后写相应的逻辑。
4 . 重写getType()方法。
getType()方法是所有内容提供器都必须提供的一个方法,用于获取Uri对象所对应的MIME类型,一个内容URI所对应的MIME类型字符串有三部分组成。Android做了如下规定:
1 . 必须以vnd开头
2 . 如果内容URI以路径结尾,则后面接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/
3 . 最后接vnd.<权限>.<路径>
//内容URI以路径结尾
vnd.android.cursor.dir/vnd.com.example.app.provider.table1
//内容URI以id结尾
vnd.android.cursor.item/vnd.com.example.app.provider.table1
@Nullable
@Override
public String getType(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";
}
return null;
}
从上面的内容我们也会发现,内容提供器之所以可以保证数据的安全性,是因为要想对数据进行增删改查,我们都首先需要匹配到内容URI格式才可以,只要我们不把隐私数据添加进UriMatcher中,我们的隐私数据就不会被访问到。