Android ContentProvider 完全解析及简单DEMO

来源:互联网 发布:考研英语时间分配知乎 编辑:程序博客网 时间:2024/05/22 12:29

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

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

  • ContentProvider可以指定需要共享的数据,而其他应用程序则可以在不知道数据来源、路径的情况下,对共享数据进行增删改查等操作。
  • 在Android系统中,许多Android系统内置的数据也是通过ContentProvider提供给用户使用,例如通讯录、音视频文件和图像文件等。

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()

1 0
原创粉丝点击