Android核心组件之ContentProvider(二)--创建自己的ContentProvider
2014-07-21 11:40
573 查看
转载请注明出处:/article/7809129.html
首先,我们来确定一下我们在哪些情况下需要自定义Content Provider:
(1)当本应用需要向其它应用提供大量的数据和文件时,需要自定义Content Provider,为其它应用访问数据提供统一的数据接口;
(2)当本应用允许用户拷贝大量数据到其它应用时,需自定义Content Provider;
(3)当需要使用搜索框架来创建一个搜索引擎时,需自定义Content Provider。
需要注意的是,在实际开发中,当对SQLite数据库中的数据操作只在本应用使用时,我们不需要另外自定义Content Provider,直接操作SQLite数据库就行。我们知道,Content Provider它本身并不负责数据的存储,它的作用主要就是为其它应用访问数据提供一个统一的接口,那Content
Provider一般支持哪些数据作为其访问接口呢?一般来讲,支持两种数据存储类型:
(1)文件数据:包括SharedPreference存储的xml格式的数据、文件设备存储的文件数据等;
(2)结构化数据:一般包括存储在数据库、数组和类结构化的数据。
(1)content://:该字符串为content uri的特殊标识,此字符串为Content Uri所必须的头部信息
(2)授权(Authority):用于唯一标识这个Content Provider,外部访问者可以根据这个标识找到它。
(3)路径(Path):所需要访问数据的路径,根据业务而定。
在Content Uri组成部分中,Authority字符串我们可以任意命名,但每一个Content Provider的Authority字符串必须具有唯一性,因为该Authority就像数据库中表字段中的主键一样,系统是根据该Authority来定位到具体的Content Provider,从而使外部访问者获取到指定的信息。不过一般来讲,Authority的命名有一定组成规则,为确保Content
Provider的Authority字符串的唯一性,一般按以下命名规则来命名Authority:com.company-name.ContentProvider-name。比如说我们下面将学习的一个例子中,自定义的一个MyContentProvider的内容提供者,company-name为androidleaf。那么在AndroidManifest.xml中注册provider时的代码如下:
content://com.androidleaf.MyContentProvider/persons,访问Company信息的Path(路径)可以定义为company,那访问Company的Content Uri为 content://com.androidleaf.MyContentProvider/company。那如果要访问Persons中的具体的某一条person数据,则需怎么定义Content Uri呢,其实很简单,只需定义清楚所需要查询的数据的具体路径就行,比如需要查询person信息集中的第3条数据,则Content
Uri应该定义如下:
该应用负责创建数据库表,初始化表数据以及自定义Content Provider数据接口。
该应用负责访问自定义的Content Provider接口数据,实现跨进程的数据访问,并对person表的数据执行CRUD操作。
文不如表,表不如图,为了加深读者对自定义Content Provider及使用的理解,我用一个图来表现我们实例执行的操作流程及关系,如图下:
接下来我将一步一步编写代码,实现以上图示的访问操作,首先,在SQLite数据库中创建person表,并向person中添加若干条数据。首先创建自己的OpenHelper类,代码列表如下:
(1)为访问不同的数据集定义不同的CONTENT_URI,并使用UriMatcher类来实现Uri的匹配器,用于匹配外部访问者传递过来的Uri,从而根据不同的Uri执行目标操作;
(2)创建一个继承Content Provider抽象类的子类(示例中为MyContentProvider),同时分别重写onCreate()、query()、getType()、delete()、update()和insert()抽象方法;
(3)分别实现自定义Content Provider中的5个方法,在这里主要实现对Uri的匹配,以及对SQLite数据库中表的数据的CRUD操作;
(4)在外部访问者中(可以是在本进程,也可以在不同的进程中)调用getContentResolver()方法通过传递不同的CONTENT_URI参数,执行不同的操作,系统会自动访问目标Content Provider,如果访问是在不同的进程中进行,那系统会自动执行跨进程操作。
(5)获取访问目标Content Provider返回的结果。
接下来我们一一按步骤进行自定义Content Provider操作,首先开始第一步,我们定义一个MyContentProviderMetaData类,用于创建不同的CONTENT_URI,具体的代码如下:
uri,int id)方法来替换我们需要所需查询的具体单条Person数据的_id。而程序中UriMatcher对象主要作用则是使用本身已经注册的Uri匹配从外部访问者传递过来的Uri,每一个传递过来的Uri和注册的时的Uri一一对应,若匹配成功,则执行目标操作,反之则返回一个NO_MATCH常量。在为UriMatcher注册Uri过程中,我们可以使用addURI(String authority,String path,int code)方法来很方便地注册所需要匹配的Uri。
接下来我们来实现第2、3步骤的操作,就是创建Content Provider的子类,并实现5个抽象方法,具体的代码列表如下:
步骤的操作了,我们需要做的是另外创建一个新的应用(在实例中名为05_AndroidContentProvider_Access_Blog),通过调用getContentResolver()方法来访问刚刚我们自定义的MyContentProvider类的方法(当然,我们需要传递相关的Uri参数),完成跨进程的操作,从而获取SQLite数据库中的Person信息集合,并执行对Person信息的CRUD操作。我们来看一下具体的实例代码,首先看一下MainActivity类中的代码列表,如下:
然后Person的entity类,代码如下:
最后是PersonListAdapter适配器类,具体代码如下:
依据以上图示可知,我们成功获取了SQLite数据库中的Person数据,并且可以执行对person表中数据的CRUD操作。
总结:自定义Content Provider在实际的开发中其实用到的不算很多,因为我们开发的大多数APP的数据都是私有化的,很少会暴露数据接口给外部的应用,但Content Provider作为Android重要组成部分,其承担的角色的重要性不言而喻。学习自定义Content
Provider有助于我们对Android系统的内部的机制和实现更进一步的了解。我们总结一下我们今天学习的主要知识点:(1)Content Uri的组成方式及使用;(3)UriMatcer类的作用及使用;(3)自定义Content Provider的步骤及实现方式。
源代码下载,请戳这里!
自定义Content Provider初步分析
前几篇博文我们基本了解了Android系统自身所提供的Content Provider的特点,并学习了如何访问其所提供的数据接口。今天我们将来学习如何自定义Content Provider,以及如何访问、操作自定义Content Provider所封装的数据。首先,我们来确定一下我们在哪些情况下需要自定义Content Provider:
(1)当本应用需要向其它应用提供大量的数据和文件时,需要自定义Content Provider,为其它应用访问数据提供统一的数据接口;
(2)当本应用允许用户拷贝大量数据到其它应用时,需自定义Content Provider;
(3)当需要使用搜索框架来创建一个搜索引擎时,需自定义Content Provider。
需要注意的是,在实际开发中,当对SQLite数据库中的数据操作只在本应用使用时,我们不需要另外自定义Content Provider,直接操作SQLite数据库就行。我们知道,Content Provider它本身并不负责数据的存储,它的作用主要就是为其它应用访问数据提供一个统一的接口,那Content
Provider一般支持哪些数据作为其访问接口呢?一般来讲,支持两种数据存储类型:
(1)文件数据:包括SharedPreference存储的xml格式的数据、文件设备存储的文件数据等;
(2)结构化数据:一般包括存储在数据库、数组和类结构化的数据。
Content Uri
在(Android四大组件只Content Provider--初步学习)这篇博客中我们初步学习一下Content Uri组成方式及作用,今天我们学习自定义Content Provider需要频繁用到Content Uri,因此需要对其进一步深入学习。那么通过前面的学习,我们知道了Content Uri一般是由三部分组成:(1)content://:该字符串为content uri的特殊标识,此字符串为Content Uri所必须的头部信息
(2)授权(Authority):用于唯一标识这个Content Provider,外部访问者可以根据这个标识找到它。
(3)路径(Path):所需要访问数据的路径,根据业务而定。
在Content Uri组成部分中,Authority字符串我们可以任意命名,但每一个Content Provider的Authority字符串必须具有唯一性,因为该Authority就像数据库中表字段中的主键一样,系统是根据该Authority来定位到具体的Content Provider,从而使外部访问者获取到指定的信息。不过一般来讲,Authority的命名有一定组成规则,为确保Content
Provider的Authority字符串的唯一性,一般按以下命名规则来命名Authority:com.company-name.ContentProvider-name。比如说我们下面将学习的一个例子中,自定义的一个MyContentProvider的内容提供者,company-name为androidleaf。那么在AndroidManifest.xml中注册provider时的代码如下:
<provider android:name="com.androidleaf.contentprovider.myprovider.MyContentProvider" <!--自定义的Content Provider名称 --> android:authorities="com.androidleaf.MyContentProvider" <!-- 定义Authority字符串名称 --> android:exported="true" <!--是否允许其它应用访问 --> />假设上面自定义的MyContentProvider既提供了一系列Persons个人信息,又提供了Company个人所属的公司信息,那么外部访问者需要各自访问两者的信息,就必须根据不同的路径进行访问,那访问Persons信息的Path(路径)可以定义为persons,那访问Persons的Content Uri为
content://com.androidleaf.MyContentProvider/persons,访问Company信息的Path(路径)可以定义为company,那访问Company的Content Uri为 content://com.androidleaf.MyContentProvider/company。那如果要访问Persons中的具体的某一条person数据,则需怎么定义Content Uri呢,其实很简单,只需定义清楚所需要查询的数据的具体路径就行,比如需要查询person信息集中的第3条数据,则Content
Uri应该定义如下:
MIME类型
使用Content Provider可以返回数据文件的MIME类型,有关Android中MIME类型的更多知识,可以学习(Android MIME类型)这篇文章。跨应用访问自定义Content Provider的数据
好了,学习完自定义Content Provider所需要预备知识后,我们来通过一个实例来具体地学习如何创建自己的Content Provider。在动手实现之前,我们先整理一下实例所需完成的功能及实现思路。首先在SQLite数据库中创建一个person表,并添加数条Person数据,然后自定义一个Content Provider为访问person表中的数据提供一个标准的访问接口,然后创建另一个新的应用,并在新的应用中通过getContentResolve()方法获取person表中的数据,并实现对Person信息的增删改查操作。下面是我们实例程序的列表清单,如下图:该应用负责创建数据库表,初始化表数据以及自定义Content Provider数据接口。
该应用负责访问自定义的Content Provider接口数据,实现跨进程的数据访问,并对person表的数据执行CRUD操作。
文不如表,表不如图,为了加深读者对自定义Content Provider及使用的理解,我用一个图来表现我们实例执行的操作流程及关系,如图下:
接下来我将一步一步编写代码,实现以上图示的访问操作,首先,在SQLite数据库中创建person表,并向person中添加若干条数据。首先创建自己的OpenHelper类,代码列表如下:
/** * 该类是SQLiteDatabase的帮助类,主要管理数据库的创建和版本的更新 * @author AndroidLeaf * */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class DatabaseOpenHelper extends SQLiteOpenHelper { private static DatabaseOpenHelper mDatabaseOpenHelper; /** * 数据库版本号 */ private static final int DATABASE_VERSION = 1; /** * 数据库名称 */ private static final String DATABASE_NAME = "manager.db"; /** * 定义一个事件监听回调,将创建表和更新数据库表的操作让子类实现 */ public SQLiteDataTable mDataTable; public interface SQLiteDataTable{ public void onCreate(SQLiteDatabase mSqLiteDatabase); public void onUpgrade(SQLiteDatabase mSqLiteDatabase); } public void setOnSQLiteDataTable(SQLiteDataTable mDataTable){ this.mDataTable = mDataTable; } /** * 初始化数据库信息 * @param context 应用程序上下文 * @param name 数据库名称 * @param factory cursor工厂对象 * @param version 数据库版本号 * @param errorHandler 数据库错误处理对象 */ public DatabaseOpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION, new DatabaseErrorHandler() { @Override public void onCorruption(SQLiteDatabase dbObj) { // TODO Auto-generated method stub } }); // TODO Auto-generated constructor stub } /** * 使用单例模式,获取数据库唯一实例 * @param mContext 应用程序上下文 * @return mDatabaseOpenHelper 该对象用于获取SQLiteDatabase实例 */ public synchronized static DatabaseOpenHelper getDatabaseOpenHelper(Context mContext){ if(mDatabaseOpenHelper == null){ mDatabaseOpenHelper = new DatabaseOpenHelper(mContext); } return mDatabaseOpenHelper; } /** * 创建数据库时调用,一般执行创建表的操作 */ @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub //创建一系列的数据库表 mDataTable.onCreate(db); } /** * 当数据库需要修改的时系统会自动调用此方法。一般我们在这个方法里边删除数据库表, * 并建立新的数据库表,当然是否还需要其它操作,完全取决于应用的需求 */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub mDataTable.onUpgrade(db); onCreate(db); } }然后创建逻辑层接口IDatabaseManager,定义了一些主要的CRUD方法,具体代码如下:
public interface IDatabaseManager { public long insert(ContentValues mContentValues); public Cursor query(String sql); public Cursor query(int id); public int update(ContentValues mContentValues); public int delete(int id); public void execSQL(String sql); public void closeDB(); }最后创建逻辑层实现类DatabaseManager,具体代码如下:
public class DataBaseManager implements IDatabaseManager{ private static final String TAG = "sqlite_log"; private static final String PERSON_TABLE = MyContentProviderMetaData.ProviderMetaData.Persons.PERSON_TABLE_NAME; private static final String PERSON_ID = MyContentProviderMetaData.ProviderMetaData.Persons._ID; private SQLiteDatabase mSqLiteDatabase; DatabaseOpenHelper mDatabaseOpenHelper; public DataBaseManager(Context mContext){ mDatabaseOpenHelper = DatabaseOpenHelper.getDatabaseOpenHelper(mContext); //获取SQLiteDatabase对象,创建或打开数据库 mSqLiteDatabase = mDatabaseOpenHelper.getWritableDatabase(); } /** * 插入单条数据操作 * @param mContentValues 插入的数据集合 * @return long 插入数据的行号 */ @Override public long insert(ContentValues mContentValues) { // TODO Auto-generated method stub mSqLiteDatabase.beginTransaction(); try { long rowId = mSqLiteDatabase.insertOrThrow(PERSON_TABLE,PERSON_ID, mContentValues); mSqLiteDatabase.setTransactionSuccessful(); return rowId; } catch (Exception e) { // TODO: handle exception Log.e(TAG, "The insert operation failed"); }finally{ mSqLiteDatabase.endTransaction(); } return -1; } /** * 更新单条数据操作 * @param mContentValues 需要更新的数据集合 * @return int 更新数据的数量 */ @Override public int update(ContentValues mContentValues) { // TODO Auto-generated method stub mSqLiteDatabase.beginTransaction(); try { int rows = mSqLiteDatabase.update(PERSON_TABLE, mContentValues, PERSON_ID +"= ?", new String[]{String.valueOf(mContentValues.get(PERSON_ID))}); mSqLiteDatabase.setTransactionSuccessful(); return rows; } catch (Exception e) { // TODO: handle exception Log.e(TAG, "The update operation failed"); }finally{ mSqLiteDatabase.endTransaction(); } return 0; } /** * 更新多条数据操作 * @param mContentValues 需要更新的数据集合 * @param whereClause * @param whereArgs * @return int 更新的数据数量 */ public int update(ContentValues mContentValues,String whereClause,String[] whereArgs) { // TODO Auto-generated method stub mSqLiteDatabase.beginTransaction(); try { int rows = mSqLiteDatabase.update(PERSON_TABLE, mContentValues, whereClause, whereArgs); mSqLiteDatabase.setTransactionSuccessful(); return rows; } catch (Exception e) { // TODO: handle exception Log.e(TAG, "The update operation failed"); }finally{ mSqLiteDatabase.endTransaction(); } return 0; } /** * 删除单条数据操作 * @param mContentValues 需要更删除的数据选项ID * @return int 删除的数据数量 */ @Override public int delete(int id) { // TODO Auto-generated method stub mSqLiteDatabase.beginTransaction(); try { int rows = mSqLiteDatabase.delete(PERSON_TABLE, PERSON_ID +"= ?", new String[]{String.valueOf(id)}); mSqLiteDatabase.setTransactionSuccessful(); return rows; } catch (Exception e) { // TODO: handle exception Log.e(TAG, "The delete operation failed"); }finally{ mSqLiteDatabase.endTransaction(); } return 0; } /** * 删除多条数据操作 * @param whereClause * @param whereArgs * @return int 删除的数据数量 */ public int delete(String whereClause,String[] whereArgs) { // TODO Auto-generated method stub mSqLiteDatabase.beginTransaction(); try { int rows = mSqLiteDatabase.delete(PERSON_TABLE, whereClause, whereArgs); mSqLiteDatabase.setTransactionSuccessful(); return rows; } catch (Exception e) { // TODO: handle exception Log.e(TAG, "The delete operation failed"); }finally{ mSqLiteDatabase.endTransaction(); } return 0; } /** * 使用标准SQL语句查询数据列表 * @param <T> * @param sql 标准SQL语句 * @return mList 查询后的数据列表 */ @SuppressWarnings("unchecked") @Override public Cursor query(String sql) { return mSqLiteDatabase.rawQuery(sql, null); } /** * 根据ID查询数据 * @param <T> * @param id 需要查询的数据ID * @return T 查询后获取到的对象 */ @Override public Cursor query(int id) { // TODO Auto-generated method stub return mSqLiteDatabase.query(PERSON_TABLE, null, PERSON_ID + "=?", new String[]{String.valueOf(id)}, null, null, null); } /** * 执行一些较为复杂的CRUD操作 */ @Override public void execSQL(String sql) { // TODO Auto-generated method stub } /** * 执行对数据库中数据的操作后关闭数据库 */ @Override public void closeDB() { // TODO Auto-generated method stub mSqLiteDatabase.close(); } }至此,SQLite数据库中person表的创建和数据初始化基本已经完成,对SQLite数据库操作还不熟悉的读者,可以先学习(Android中SQLite数据库介绍和使用)这篇文章。接下来,将介绍本章的重点知识--自定义Content Provider。首先,我们先梳理一下自定义Content Provider的编程步骤:
(1)为访问不同的数据集定义不同的CONTENT_URI,并使用UriMatcher类来实现Uri的匹配器,用于匹配外部访问者传递过来的Uri,从而根据不同的Uri执行目标操作;
(2)创建一个继承Content Provider抽象类的子类(示例中为MyContentProvider),同时分别重写onCreate()、query()、getType()、delete()、update()和insert()抽象方法;
(3)分别实现自定义Content Provider中的5个方法,在这里主要实现对Uri的匹配,以及对SQLite数据库中表的数据的CRUD操作;
(4)在外部访问者中(可以是在本进程,也可以在不同的进程中)调用getContentResolver()方法通过传递不同的CONTENT_URI参数,执行不同的操作,系统会自动访问目标Content Provider,如果访问是在不同的进程中进行,那系统会自动执行跨进程操作。
(5)获取访问目标Content Provider返回的结果。
接下来我们一一按步骤进行自定义Content Provider操作,首先开始第一步,我们定义一个MyContentProviderMetaData类,用于创建不同的CONTENT_URI,具体的代码如下:
public class MyContentProviderMetaData { /** * 定义基本授权Authority */ public static final String AUTHORITY = "com.androidleaf.MyContentProvider"; public static final String URI_AUTHORITY = "content://"+ AUTHORITY +"/"; public static final int COLLECTION_INDICATOR = 1; public static final int SINGLE_INDICATOR = 2; public static final class ProviderMetaData{ public static final class Persons implements BaseColumns{ /** * 表的名称 */ public static final String PERSON_TABLE_NAME = "person"; public static final String PERSON_FIELD_PERSONNAME = "name"; public static final String PERSON_FIELD_EMAIL = "email"; public static final String PERSON_FIELD_HEIGHT = "height"; public static UriMatcher mUriMatcher = null; /** * 创建Person相关的CONTENT_URI,PERSONS_URI用于匹配查询多条Person数据集, * PERSON_URI用于匹配查询单条Person的数据 */ public static final Uri PERSONS_URI = getContentUri("persons"); public static final Uri PERSON_URI = getContentUri("persons/#"); static{ /** * 实例化UriMatcher对象,注册需要匹配的URI */ mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); mUriMatcher.addURI(AUTHORITY, "persons", COLLECTION_INDICATOR); mUriMatcher.addURI(AUTHORITY, "persons/#", SINGLE_INDICATOR); } public static Uri getContentUri(String volumeName){ return Uri.parse(URI_AUTHORITY + volumeName); } } } }在此段代码中,我们创建了Person相关的CONTENT_URI,其中path为"persons"的PERSONS_URI是用于匹配查询多条Person数据集,而path为“persons/#”的PERSON_URI是用于匹配查询单条Person的数据,在开发中,“#”字符我们可以使用ContentUris.withAppendedId(Uri
uri,int id)方法来替换我们需要所需查询的具体单条Person数据的_id。而程序中UriMatcher对象主要作用则是使用本身已经注册的Uri匹配从外部访问者传递过来的Uri,每一个传递过来的Uri和注册的时的Uri一一对应,若匹配成功,则执行目标操作,反之则返回一个NO_MATCH常量。在为UriMatcher注册Uri过程中,我们可以使用addURI(String authority,String path,int code)方法来很方便地注册所需要匹配的Uri。
接下来我们来实现第2、3步骤的操作,就是创建Content Provider的子类,并实现5个抽象方法,具体的代码列表如下:
/** * 自定义Content Provider,实现对SQLite数据库数据操作的封装 * @author AndroidLeaf */ public class MyContentProvider extends ContentProvider { //获取UriMatcher对象 UriMatcher mUriMatcher = MyContentProviderMetaData.ProviderMetaData.Persons.mUriMatcher; DataBaseManager mDataBaseManager; @Override public boolean onCreate() { // TODO Auto-generated method stub mDataBaseManager = new DataBaseManager(getContext()); return mDataBaseManager == null?false:true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // TODO Auto-generated method stub Cursor mCursor = null; switch (mUriMatcher.match(uri)) { case MyContentProviderMetaData.SINGLE_INDICATOR: //得到最后"/"字符串的数值ID String rowId = uri.getLastPathSegment(); mCursor = mDataBaseManager.query(Integer.parseInt(rowId)); break; case MyContentProviderMetaData.COLLECTION_INDICATOR: //查询所有数据 mCursor = mDataBaseManager.query("select * from person"); break; default: throw new IllegalArgumentException("Unkonw Uri "+ uri); } return mCursor; } @Override public String getType(Uri uri) { // TODO Auto-generated method stub return null; } @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub if(mUriMatcher.match(uri) != MyContentProviderMetaData.COLLECTION_INDICATOR){ throw new IllegalArgumentException("Unkonw Uri "+ uri); } long rowId = mDataBaseManager.insert(values); if( rowId!= -1){ Uri retUri = ContentUris.withAppendedId(MyContentProviderMetaData.ProviderMetaData.Persons.PERSONS_URI, rowId); notifySetChanged(uri); return retUri; } return null; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub int count = 0; //UriMatcher.match(uri)方法用于匹配uri,匹配成功后返回注册时设置的code switch (mUriMatcher.match(uri)) { case MyContentProviderMetaData.SINGLE_INDICATOR: //得到最后"/"字符串的数值ID String rowId = uri.getLastPathSegment(); count = mDataBaseManager.delete(Integer.parseInt(rowId)); break; case MyContentProviderMetaData.COLLECTION_INDICATOR: count = mDataBaseManager.delete(selection, selectionArgs); break; default: throw new IllegalArgumentException("Unkonw Uri "+ uri); } if(count > 0){ notifySetChanged(uri); } return count; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub int count = 0; switch (mUriMatcher.match(uri)) { case MyContentProviderMetaData.SINGLE_INDICATOR: //得到最后"/"字符串的数值ID String rowId = uri.getLastPathSegment(); values.put(MyContentProviderMetaData.ProviderMetaData.Persons._ID, rowId); count = mDataBaseManager.update(values); break; case MyContentProviderMetaData.COLLECTION_INDICATOR: count = mDataBaseManager.update(values, selection, selectionArgs); default: throw new IllegalArgumentException("Unkonw Uri "+ uri); } if(count > 0){ notifySetChanged(uri); } return count; } public void notifySetChanged(Uri uri){ //更新数据时,通知其它的ContentObserver this.getContext().getContentResolver().notifyChange(uri, null); } }完成第2、3步骤后,我们基本上已经完成了使用自定义的Content Provider对SQLite数据库中的数据的封装,并提供了一个统一的访问接口,以供外部访问者使用。那么接下来我们就需要执行第4、5
步骤的操作了,我们需要做的是另外创建一个新的应用(在实例中名为05_AndroidContentProvider_Access_Blog),通过调用getContentResolver()方法来访问刚刚我们自定义的MyContentProvider类的方法(当然,我们需要传递相关的Uri参数),完成跨进程的操作,从而获取SQLite数据库中的Person信息集合,并执行对Person信息的CRUD操作。我们来看一下具体的实例代码,首先看一下MainActivity类中的代码列表,如下:
public class MainActivity extends ListActivity { PersonListAdapter mAdapter = null; public static final int INSERT_ACTION = 0; public static final int UPDATE_ACTION = 1; public static final int DELETE_ACTION = 2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } public void init(){ ArrayList<Person> mArrayList = getAllPersons(); if(mArrayList != null && mArrayList.size() > 0){ mAdapter = new PersonListAdapter(getApplicationContext(),mArrayList); //适配器绑定数据 setListAdapter(mAdapter); //将ListView列表添加到上下文Menu中 this.registerForContextMenu(getListView()); } } public ArrayList<Person> getAllPersons(){ ArrayList<Person> mList = new ArrayList<Person>(); Cursor mCursor = getContentResolver().query(MyContentProviderMetaData.ProviderMetaData.Persons.PERSONS_URI, null, null, null, null); if(mCursor != null && mCursor.getCount() > 0){ mCursor.moveToFirst(); while(!mCursor.isAfterLast()){ int id = mCursor.getInt(mCursor.getColumnIndex(MyContentProviderMetaData.ProviderMetaData.Persons._ID)); String name = mCursor.getString(mCursor.getColumnIndex(MyContentProviderMetaData.ProviderMetaData.Persons.PERSON_FIELD_PERSONNAME)); String email = mCursor.getString(mCursor.getColumnIndex(MyContentProviderMetaData.ProviderMetaData.Persons.PERSON_FIELD_EMAIL)); float height = mCursor.getFloat(mCursor.getColumnIndex(MyContentProviderMetaData.ProviderMetaData.Persons.PERSON_FIELD_HEIGHT)); mList.add(new Person(id, name, email, height)); mCursor.moveToNext(); } return mList; }else{ ((TextView)findViewById(R.id.empty_textview)).setText("获取的数据为空!!!"); return null; } } /** * 删除数据 * @param id 列表ID */ public void deletePerson(final int id){ AlertDialog.Builder mBuilder = new AlertDialog.Builder(MainActivity.this) .setTitle("是否删除?") .setPositiveButton("确定", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub Uri mUri = ContentUris.withAppendedId(MyContentProviderMetaData.ProviderMetaData.Persons.PERSON_URI, id); getContentResolver().delete(mUri, null, null); //重新设置数据 mAdapter.setData(getAllPersons()); //更新数据列表 mAdapter.notifyDataSetChanged(); } }) .setNegativeButton("取消", null); mBuilder.show(); } /** * 更新数据 * @param id 列表ID */ public void updatePerson(int id,Person mPerson){ createDialog("更新", UPDATE_ACTION, id,mPerson); } /** * 插入数据 */ public void insertPerson(){ createDialog("添加", INSERT_ACTION, -1,null); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { insertPerson(); return super.onOptionsItemSelected(item); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { // TODO Auto-generated method stub menu.setHeaderTitle("文件操作"); menu.add(0, 1, Menu.NONE, "修改"); menu.add(0, 2, Menu.NONE, "删除"); } @Override public boolean onContextItemSelected(MenuItem item) { // TODO Auto-generated method stub // 得到当前被选中的item信息 AdapterContextMenuInfo menuInfo = (AdapterContextMenuInfo) item .getMenuInfo(); int id = -1; Person mPerson = null; if(mAdapter != null){ mPerson = (Person)mAdapter.getItem(menuInfo.position); id = mPerson.getId(); } switch (item.getItemId()) { case UPDATE_ACTION: updatePerson(id,mPerson); break; case DELETE_ACTION: deletePerson(id); break; default: break; } return true; } /** * 创建Dialog实例 * @param title 设置Dialog标题 * @param action 设置操作类型 ,UPDATE_ACTION为更新操作,INSERT_ACTION为插入操作 * @param id 被选中的选项ID */ public void createDialog(String title,final int action,final int id,final Person mPerson){ final View mView = LayoutInflater.from(MainActivity.this).inflate(R.layout.layout_operation, null); String titlName = getResources().getString(R.string.operation, new Object[]{title}); final EditText mEditText_name = (EditText)mView.findViewById(R.id.name); final EditText mEditText_email = (EditText)mView.findViewById(R.id.email); final EditText mEditText_height = (EditText)mView.findViewById(R.id.height); //初始化数据 if(action == UPDATE_ACTION){ mEditText_name.setText(mPerson.getName()); mEditText_email.setText(mPerson.getEmail()); mEditText_height.setText(String.valueOf(mPerson.getHeight())); } //创建Dialog实例对象 AlertDialog.Builder mBuilder = new AlertDialog.Builder(MainActivity.this) .setTitle(titlName) .setView(mView) .setPositiveButton("确定", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub String name = mEditText_name.getText().toString().trim(); String email = mEditText_email.getText().toString().trim(); float height = Float.parseFloat(mEditText_height.getText().toString().trim()); if(!TextUtils.isEmpty(name) && !TextUtils.isEmpty(email) && !TextUtils.isEmpty(mEditText_height.getText().toString().trim())){ ContentValues mValues = new ContentValues(); mValues.put(MyContentProviderMetaData.ProviderMetaData.Persons.PERSON_FIELD_PERSONNAME, name); mValues.put(MyContentProviderMetaData.ProviderMetaData.Persons.PERSON_FIELD_EMAIL, email); mValues.put(MyContentProviderMetaData.ProviderMetaData.Persons.PERSON_FIELD_HEIGHT, height); switch (action) { case INSERT_ACTION: MainActivity.this.getContentResolver().insert(MyContentProviderMetaData.ProviderMetaData.Persons.PERSONS_URI, mValues); break; case UPDATE_ACTION: Uri mUri = ContentUris.withAppendedId(MyContentProviderMetaData.ProviderMetaData.Persons.PERSON_URI, id); MainActivity.this.getContentResolver().update(mUri, mValues, null, null); break; default: break; } //重新设置数据 mAdapter.setData(getAllPersons()); //更新数据列表 mAdapter.notifyDataSetChanged(); } } }) .setNegativeButton("取消", null); mBuilder.show(); } }
然后Person的entity类,代码如下:
public class Person { private int id; private String name; private String email; private float height; public Person(){} public Person(int id,String name,String email,float height){ this.id = id; this.name = name; this.email = email; this.height = height; } public int getId(){ return id; } public void setId(int id){ this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public float getHeight() { return height; } public void setHeight(float height) { this.height = height; } }
最后是PersonListAdapter适配器类,具体代码如下:
public class PersonListAdapter extends BaseAdapter { private Context mContext; private ArrayList<Person> mList; public PersonListAdapter(Context mContext,ArrayList<Person> mList){ this.mContext = mContext; this.mList = mList; } public void setData(ArrayList<Person> mList){ this.mList = mList; } @Override public int getCount() { // TODO Auto-generated method stub return mList.size(); } @Override public Object getItem(int position) { // TODO Auto-generated method stub return mList.get(position); } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub ViewHolder mViewHolder = null; if(convertView == null){ convertView = LayoutInflater.from(mContext).inflate(R.layout.layout_item, null); mViewHolder = new ViewHolder(); mViewHolder.mTextView_id = (TextView)convertView.findViewById(R.id.person_id); mViewHolder.mTextView_name = (TextView)convertView.findViewById(R.id.person_name); mViewHolder.mTextView_height = (TextView)convertView.findViewById(R.id.person_height); mViewHolder.mTextView_email = (TextView)convertView.findViewById(R.id.person_email); convertView.setTag(mViewHolder); }else{ mViewHolder = (ViewHolder)convertView.getTag(); } mViewHolder.mTextView_id.setText(mList.get(position).getId()+""); mViewHolder.mTextView_name.setText(mList.get(position).getName()); mViewHolder.mTextView_height.setText(mList.get(position).getHeight()+""); mViewHolder.mTextView_email.setText(mList.get(position).getEmail()); return convertView; } class ViewHolder{ TextView mTextView_id; TextView mTextView_name; TextView mTextView_height; TextView mTextView_email; } }至此,我们便全部编写完成了实例代码,我来先后在模拟器上运行一下两个应用实例,运行效果如下图:
依据以上图示可知,我们成功获取了SQLite数据库中的Person数据,并且可以执行对person表中数据的CRUD操作。
总结:自定义Content Provider在实际的开发中其实用到的不算很多,因为我们开发的大多数APP的数据都是私有化的,很少会暴露数据接口给外部的应用,但Content Provider作为Android重要组成部分,其承担的角色的重要性不言而喻。学习自定义Content
Provider有助于我们对Android系统的内部的机制和实现更进一步的了解。我们总结一下我们今天学习的主要知识点:(1)Content Uri的组成方式及使用;(3)UriMatcer类的作用及使用;(3)自定义Content Provider的步骤及实现方式。
源代码下载,请戳这里!
相关文章推荐
- Android中创建自己的ContentProvider
- Android开发之内容提供者——创建自己的ContentProvider(详解)
- Android核心基础-8.Android四大组件之Activity-1.创建、启动
- Android开发之内容提供者——创建自己的ContentProvider(详解)
- Android笔记(五十六) Android四大组件之一——ContentProvider,实现自己的ContentProvider
- Android核心技术之(9)---四大应用组件之ContentProvider
- Android四大核心组件之ContentProvider
- Android四大核心组件之ContentProvider
- Android核心组件之ContentProvider(一)--初步学习
- 10天学通Android开发(2-2)-核心组件Service创建
- Android开发之内容提供者——创建自己的ContentProvider(详解)
- Android四大核心组件之ContentProvider
- android 应用五大核心组件之五(ContentProvider)
- Android开发之内容提供者——创建自己的ContentProvider(详解)
- android基础总结篇之八:创建及调用自己的ContentProvider
- 【Android基础】(4)四大核心组件之ContentProvider
- 解读Android之ContentProvider(2)创建自己的Provider
- Android四大核心组件之contentProvider
- android基础总结篇之八:创建及调用自己的ContentProvider
- android 四大重要的组件之ContentProvider