您的位置:首页 > 移动开发 > Android开发

Android ContentProvider 完全解析及简单DEMO

2016-05-03 14:48 591 查看
Android应用程序运行在不同的进程空间中,因此不同应用程序的数据是不能够直接访问的。为了增强程序之间的数据共享能力,Android系统提供了像SharedPreferences这类简单的跨越程序边界的访问方法,但这些方法都存在一定的局限性。

ContentProvider(数据提供者)是应用程序之间共享数据的一种接口机制,是一种更为高级的数据共享方法。

ContentProvider可以指定需要共享的数据,而其他应用程序则可以在不知道数据来源、路径的情况下,对共享数据进行增删改查等操作。

在Android系统中,许多Android系统内置的数据也是通过ContentProvider提供给用户使用,例如通讯录、音视频文件和图像文件等。

ContentProvider机理

-

调用关系



在创建ContentProvider前,首先要实现底层的数据源,数据源包括数据库、文件系统或网络等,然后继承ContentProvider类中实现基本数据操作的接口函数。调用者不能直接调用ContentProvider的接口函数,需要通过ContentResolver对象,通过URI间接调用ContentProvider。

-

ContentResolver对象与ContentProvider的交互

在ContentResolver对象与ContentProvider进行交互时,通过URI确定要访问的ContentProvider数据集。在发起的一个请求的过程中,Android系统根据URI确定处理这个查询的ContentProvider,然后初始化ContentProvider所有需要的资源,这个初始化的工作是Android系统完成的,无需我们参与。一般情况下只有一个ContentProvider对象,但却可以同时与多个ContentResolver进行交互。

-

ContentProvider的屏蔽性

ContentProvider完全屏蔽了底层数据源的数据存储方法。数据提供者通过ContentProvider提供了一组标准的数据操作接口,但却无须知道数据提供者的内部数据的存储方法。数据提供者可以使用SQLite数据库存储数据,也可以通过文件系统或SharedPreferences存储数据,甚至是使用网络存储的方法,这些数据的存储方法和存储设备对数据使用者都是不可见的。同时,也正是这种屏蔽模式,很大程度上简化了ContentProvider的使用方法,使用者只要调用ContentProvider提供的接口函数,即可完成所有的数据操作,而数据存储方法则是ContentProvider设计者需要考虑的问题。

- ContentProvider提供的数据形式

ContentProvider的数据集类似于数据库的数据表,每行是一条记录,每列具有相同的数据类型。每条记录都包含一个长整型的字段 _ID,用来唯一标识每条记录。ContentProvider可以提供多个数据集,调用者使用URI对不同数据集的数据进行操作。

-

通用资源标识符(Uniform Resource Identifier)

URI是一个用于标识某一互联网资源名称的字符串。 该种标识允许用户对任何(包括本地和互联网)的资源通过特定的协议进行交互操作。在ContentProvider机制中,使用ContentResolver对象通过URI定位ContentProvider提供的资源。

ContentProvider使用的URI语法结构如下:

content://<authority>/<data_path>/<id>


content:// 是通用前缀,表示该UIR用于ContentProvider定位资源。

< authority > 是授权者名称,用来确定具体由哪一个ContentProvider提供资源。因此一般< authority >都由类的小写全称组成,以保证唯一性。

< data_path > 是数据路径,用来确定请求的是哪个数据集。如果ContentProvider近提供一个数据集,数据路径则可以省略;如果ContentProvider提供多个数据集,数据路径必须指明具体数据集。数据集的数据路径可以写成多段格式,例如people/girl和people/boy。

< id > 是数据编号,用来唯一确定数据集中的一条记录,匹配数据集中_ID字段的值。如果请求的数据不只一条,< id >可以省略。

如请求整个people数据集的URI为:

content://com.example.peopleprovider/people


而请求people数据集中第3条数据的URI则应写为:

content://com.example.peopleprovider/people/3


创建数据提供者

1. 创建一个类让其继承ContentProvider,并重载6个函数

onCreate()

一般用来初始化底层数据集和建立数据连接等工作

getType()

用来返回指定URI的MIME数据类型,若URI是单条数据,则返回的MIME数据类型以vnd.android.cursor.item开头;若URI是多条数据,则返回的MIME数据类型以vnd.android.cursor.dir/开头。

insert()、delete()、update()、query()

用于对数据集的增删改查操作。

2. 声明CONTENT_URI,实现UriMatcher

示例:

public static final String AUTHORITY = "com.example.peopleprovider";
public static final String PATH_SINGLE = "people/#";
public static final String PATH_MULTIPLE = "people";
public static final String CONTENT_URI_STRING = "content://" + AUTHORITY + "/" + PATH_MULTIPLE;
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING);
public static final int MULTIPLE_PEOPLE = 1;
public static final int SINGLE_PEOPLE = 2;
public static final UriMatcher uriMatcher;
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, PATH_SINGLE, SINGLE_PEOPLE );
uriMatcher.addURI(AUTHORITY, PATH_MULTIPLE , MULTIPLE_PEOPLE );
}


其中UriMatcher类引用官方文档中的解释:

Utility class to aid in matching URIs in content providers.

可见UriMatcher本质上是一个文本过滤器,用在contentProvider中帮助我们过滤,分辨出查询者想要查询哪个数据表。

UriMatcher的构造函数中,UriMatcher.NO_MATCH是URI无匹配时的返回代码,值为-1。 addURI() 方法用来添加新的匹配项,语法为:

public void addURI(String authority, String path, int code)


其中authority表示匹配的授权者名称,path表示数据路径(#代表任何数字),code表示返回代码。

关于UriMatcher的使用

switch(uriMatcher.match(uri)){
case MULTIPLE_PEOPLE:
//多条数据的处理
break;
case SINGLE_PEOPLE:
//单条数据的处理
break;
default:
throw new IllegalArgumentException("不支持的URI:" + uri);
}


3. 注册ContentProvider

在AndroidManifest.xml文件中的 application节点下使用< provider >标签注册。示例:

<provider
android:authorities="com.example.peopleprovider"
android:name=".Peopleprovider" />


上例中注册了一个授权者名称为com.example.peopleprovider的ContentProvider,其实现类为 Peopleprovider 。

使用数据提供者

每个Android组件都有一个ContentResolver对象,通过调用getContentResolver() 方法可得到ContentResolver对象。

准备工作:

ContentResolver resolver = getContentResolver();
String KEY_ID = "_id";
String KEY_NAME = "name";
String KEY_AGE = "age";
String KEY_HEIGHT = "height";


1. 添加操作

通过insert()函数添加单条数据

(returns : the URL of the newly created row.)

ContentValues values = new ContentValues();
values.put(KEY_NAME, "Tom");
values.put(KEY_AGE, 21);
values.put(KEY_HEIGHT, 1.81f);
Uri newUri = resolver.insert(CONTENT_URI, values);


通过bulkInsert()函数添加多条数据

(returns:the number of newly created rows.)

ContentValues[] arrayValues = new ContentValues[10];
//实例化每一个ContentValues...
int count = resolver.bulkInsert(CONTENT_URI, arrayValues );


2. 删除操作

指定ID删除单条数据

Uri uri = Uri.parse(CONTENT_URI_STRING + "/" +"2");
int result = resolver.delete(uri, null, null);


通过selection语句删除多条数据

String selection = KEY_ID + ">4";
int result = resolver.delete(CONTENT_URI, selection, null);


3. 更新操作

ContentValues values = new ContentValues();
values.put(KEY_NAME, "Tom");
values.put(KEY_AGE, 21);
values.put(KEY_HEIGHT, 1.81f);
Uri rui = Uri.parse(CONTENT_URI_STRING + "/" + "7");
int result = resolver.update(uri, values, null, null);


4. 查询操作

Uri uri = Uri.parse(CONTENT_URI_STRING + "/" + "2");
Cursor cursor = resolver.query(uri, new String[]{KEY_ID, KEY_NAME, KEY_AGE, KEY_HEIGHT}, null, null, null);


在URI中定义了需要查询数据的ID后,在query()函数中没有必要再加入其他的查询条件,如果要获取数据集全部数据,则可以直接使用CONTENT_URI且不加查询条件。

在Android系统中,数据库查询结果的返回值并不是数据集合的完整拷贝,而是返回数据集的指针,这个指针就是Cursor类。ContentProvider的数据集类似数据库的数据表,其查询结果的返回值同样是数据集的指针:Cursor类。在提取Cursor数据中的数据前,推荐测试Cursor中的数据数量,避免在数据获取中产生异常。示例如下:

public people[] getPeople(Cursor cursor){
int resultCounts = cursor.getCount();
if(resultCounts == 0 !cursor.moveToFirst()){
return null;
}
People[] peoples = new People[resultCounts];
for(int i=0; i<resultCounts; i++){
peoples[i] = new People();
peoples[i].ID = cursor.getInt(0);
peoples[i].Name = cursor.getString(cursor.getColumnIndex(KEY_NAME));
peoples[i].Age = cursor.getInt(cursor.getColumnIndex(KEY_AGE));
peoples[i].Height= cursor.getFloat(cursor.getColumnIndex(KEY_HEIGHT));
cursor.moveToNext();
}
return peoples;
}


ContentProvider Demo

Demo结构如下:





People.java

public class People {
public static final String MIME_DIR_PREFIX = "vnd.android.cursor.dir";
public static final String MIME_ITEM_PREFIX = "vnd.android.cursor.item";
public static final String MIME_ITEM = "vnd.example.people";

public static final String MIME_TYPE_SINGLE = MIME_ITEM_PREFIX + "/" + MIME_ITEM ;
public static final String MIME_TYPE_MULTIPLE = MIME_DIR_PREFIX + "/" + MIME_ITEM ;

public static final String AUTHORITY = "com.example.peopleprovider";
public static final String PATH_SINGLE = "people/#";
public static final String PATH_MULTIPLE = "people";
public static final String CONTENT_URI_STRING = "content://" + AUTHORITY + "/" + PATH_MULTIPLE;
public static final Uri CONTENT_URI = Uri.parse(CONTENT_URI_STRING);

public static final String KEY_ID = "_id";
public static final String KEY_NAME = "name";
public static final String KEY_AGE = "age";
public static final String KEY_HEIGHT = "height";

}


PeopleProvider.java

package com.example.contentproviderdemo;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.support.annotation.Nullable;

/**
* Created by yinghao on 2016/5/3.
*/
public class PeopleProvider extends ContentProvider {

private static final String DB_NAME="people.db";
private static final String DB_TABLE="peopleinfo";
private static final int DB_VERSION = 1;

private SQLiteDatabase db;
private DBOpenHelper dbOpenHelper;

private static final int MULTIPLE_PEOPLE = 1;
private static final int SINGLE_PEOPLE = 2;
private static final UriMatcher uriMatcher ;

static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(People.AUTHORITY, People.PATH_MULTIPLE, MULTIPLE_PEOPLE);
uriMatcher.addURI(People.AUTHORITY, People.PATH_SINGLE, SINGLE_PEOPLE);
}

@Override
public boolean onCreate() {
Context context = getContext();
dbOpenHelper = new DBOpenHelper(context, DB_NAME, null, DB_VERSION);
db = dbOpenHelper.getWritableDatabase();
if(db == null){
return false;
}else{
return true;
}
}

@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(DB_TABLE);
switch (uriMatcher.match(uri)){
case SINGLE_PEOPLE:
qb.appendWhere(People.KEY_ID+"="+uri.getPathSegments().get(1));
break;
default:
break;
}
Cursor cursor = qb.query(db,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}

@Nullable
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)){
case MULTIPLE_PEOPLE:
return People.MIME_TYPE_MULTIPLE;
case SINGLE_PEOPLE:
return People.MIME_TYPE_SINGLE;
default:
throw new IllegalArgumentException("Unkown uro:"+uri);
}
}

@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
long id =db.insert(DB_TABLE, null, values);
if(id>0){
Uri newUri = ContentUris.withAppendedId(People.CONTENT_URI,id);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
throw new SQLException("failed to insert row into " + uri);
}

@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
switch (uriMatcher.match(uri)){
case MULTIPLE_PEOPLE:
count = db.delete(DB_TABLE, selection, selectionArgs);
break;
case SINGLE_PEOPLE:
String segment = uri.getPathSegments().get(1);
count = db.delete(DB_TABLE, People.KEY_ID + "=" + segment, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unsupported URI:" + uri);
}
getContext().getContentResolver().notifyChange(uri,null);
return count;
}

@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count;
switch (uriMatcher.match(uri)){
case MULTIPLE_PEOPLE:
count = db.update(DB_TABLE, values, selection, selectionArgs);
break;
case SINGLE_PEOPLE:
String segment = uri.getPathSegments().get(1);
count = db.update(DB_TABLE, values, People.KEY_ID + "=" + segment, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknow URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}

private static class DBOpenHelper extends SQLiteOpenHelper {

private static final  String DB_CREATE = "create table "+
DB_TABLE+"("+People.KEY_ID+" integer primary key autoincrement, "+
People.KEY_NAME+" text not null, "+People.KEY_AGE+" integer, "+
People.KEY_HEIGHT+" float);";

public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}

@Override
public void onCreate(SQLiteDatabase db) {

db.execSQL(DB_CREATE);

}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
onCreate(db);

}
}

}


MainActivity.java

package com.example.contentresolverdemo;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private EditText nameText;
private EditText ageText;
private EditText heightText;
private EditText idEntry;
private TextView labelView;
private TextView displayView;
private Button add;
private Button queryAll;
private Button clear;
private Button del;
private Button query;
private Button deleteAll;
private Button update;
private ContentResolver resolver;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
resolver = this.getContentResolver();
initView();
initEvent();
}

private void initEvent() {

add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContentValues values = new ContentValues();
values.put(People.KEY_NAME, nameText.getText().toString());
values.put(People.KEY_AGE, Integer.parseInt(ageText.getText().toString()));
values.put(People.KEY_HEIGHT, Float.parseFloat(heightText.getText().toString()));
Uri newUri = resolver.insert(People.CONTENT_URI, values);
labelView.setText("添加成功,URI:" + newUri);
}
});

queryAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Cursor cursor = resolver.query(People.CONTENT_URI,
new String[]{People.KEY_ID, People.KEY_NAME, People.KEY_AGE, People.KEY_HEIGHT},
null, null, null);
if (cursor == null) {
labelView.setText("数据库中没有数据");
return;
}
labelView.setText("数据库:" + String.valueOf(cursor.getCount()) + "条记录");
String msg= "";
if (cursor.moveToFirst()) {
do {
msg += "ID: " + cursor.getString(cursor.getColumnIndex(People.KEY_ID)) + ",";
msg += "姓名: " + cursor.getString(cursor.getColumnIndex(People.KEY_NAME)) + ",";
msg += "年龄: " + cursor.getInt(cursor.getColumnIndex(People.KEY_AGE)) + ",";
msg += "身高: " + cursor.getFloat(cursor.getColumnIndex(People.KEY_HEIGHT)) + ",";
} while (cursor.moveToNext());
}
displayView.setText(msg);
}
});

deleteAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
resolver.delete(People.CONTENT_URI, null, null);
String msg = "数据全部删除";
labelView.setText(msg);
}
});

update.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ContentValues values = new ContentValues();
values.put(People.KEY_NAME, nameText.getText().toString());
values.put(People.KEY_AGE, Integer.parseInt(ageText.getText().toString()));
values.put(People.KEY_HEIGHT, Float.parseFloat(heightText.getText().toString()));
Uri uri = Uri.parse(People.CONTENT_URI_STRING + "/" + idEntry.getText().toString());
int result = resolver.update(uri, values, null, null);
String msg = "更新ID为" + idEntry.getText().toString() + "的数据" + (result > 0 ? "成功" : "失败");
labelView.setText(msg);
}
});
}

private void initView() {
}
}


ContentProviderDemo的AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.contentproviderdemo">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<provider
android:authorities="com.example.peopleprovider"
android:name=".PeopleProvider"/>
</application>

</manifest>


ContentResolverDemo的AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.contentresolverdemo">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>


对于以上Demo其中的细节:(API 23)

SQLiteQueryBuilder

public class

SQLiteQueryBuilder

extends Object

This is a convience class that helps build SQL queries to be sent to

SQLiteDatabase objects.

uri.getPathSegments()

public abstract List getPathSegments ()

Added in API level 1

Gets the decoded path segments.

Returns

decoded path segments, each without a leading or trailing ‘/’

uri.getPathSegments().get(position)

public abstract E get (int location)

Added in API level 1

Returns the element at the specified location in this List.

Parameters

location

the index of the element to return.

Returns

the element at the specified location.

Throws

IndexOutOfBoundsException

if location < 0 || location >= size()
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: