Android ContentProvider 完全解析及简单DEMO
来源:互联网 发布:考研英语时间分配知乎 编辑:程序博客网 时间:2024/05/22 12:29
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 ObjectThis 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()
- Android ContentProvider 完全解析及简单DEMO
- Android ContentProvider 完全解析及简单DEMO
- Android ContentProvider 完全解析及简单DEMO
- Android ContentProvider 完全解析及DEMO(最具说服力)
- Android PinnedHeaderListView Demo及SectionIndexer里面方法简单解析
- Android MVVM实战Demo完全解析
- Android Custom ContentProvider Demo
- Android 自定义ContentProvider Demo
- Android--ContentProvider小demo
- android ContentProvider简单介绍及使用
- ContentProvider总结与简单Demo
- android ContentProvider解析使用
- Android ContentProvider全面解析
- Android contentProvider解析
- Android ContentProvider简单实现
- android contentprovider简单讲解
- Android ContentProvider简单使用
- Android四组件之ContentProvider架构&Demo
- LeetCode 第 155 题 (Min Stack)
- 三个二维数组的比较
- 汽水瓶
- Big-Endian and Little-Endian Test in ARM and PowerPC SoC
- java面试经常遇到的
- Android ContentProvider 完全解析及简单DEMO
- 基于wpa_supplicant库的WIFI连接功能实现--wpa_cli命令代码改写
- CSS伪类before,after制作左右横线中间文字效果
- fastjson 序列化枚举问题
- 使用contiperf进行压测
- RxJava的坑
- 【Github教程】史上最全github使用方法:github入门到精通
- 希望zf 严厉整一下百度,不能只是收钱就给谁都做广告
- php编译安装添加pdo_mysql.so扩展