如果你选择用SQLite数据库存储应用程序数据,我建议你创建ContentProvider,即使存储的数据仅供内部使用。原因是Android提供了一些工具类以及UI相关的类,它们的工作在ContentProvider之上,能够简化开发者的工作。此外,这些类还提供了一个简单的机制,一旦数据有更新就会通知客户端,这让开发者保持用户界面和实际内容的同步变得很简单。

创建数据库表时,一定要考虑它们的主要目的。数据库是否主要用于读取以及显示用户界面上?数据库是否主要用于写操作并且在后台运行,如记录运动数据?更具不同的用途,读性能可能会比写操作更重要,反过来也有可能。

1.创建数据库

要讲解优化ContentProvider之前我们必须创建一下讲解必须要用到的数据库,毕竟前言不搭后语的讲解,读者可能很难理解。

下面的数据库很简单,只有一个名称,一个ID,一个完整的文件名dir。

public class ImageProvider extends ContentProvider {
public static final String AUTHORITY="com.example.liyuanjing.dbcppro.provider";
public static final int ALL_IMAGE=;//查询全部信息
public static final int SINGLE_IMAGE=;//查询单个信息
public static final String TABLE_NAME="images";
public static final String[] ALL_COLUMNS=new String[]{"id,","name","dir"};
public static UriMatcher mUriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
public static final String CREATE_TABLE="create table images("+
"id integer primary key autoincrement,"
+"name text,"
+"dir text)";
public MyDatabaseHelper mOpenHelper;
static {
mUriMatcher.addURI(AUTHORITY,"images",ALL_IMAGE);
mUriMatcher.addURI(AUTHORITY,"images/#",SINGLE_IMAGE);
} private class MyDatabaseHelper extends SQLiteOpenHelper{
public MyDatabaseHelper(Context context) {
super(context, "imagedb", null, );
} @Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
} @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }
}

为了使例子更加清晰,代码示例使用String常量声明SQL语句,更好的办法是把这些字符串存储到应用程序的raw资源目录内的文件中。这样会更容易使用,并能简化测试工作。

2.优化ContentProvider的查询方法

查询数据库会调用ContentProvider.query()方法。开发者在实现查询方法时必须解析传入的Uri以决定执行哪个查询,并且还要检查所有传入的参数是安全的。

下面的代码实现的query()方法,以及用于修改selection和selectionArgs参数的两个工具方法。

public static String[] fixSelectionArgs(String[] selectionArgs,String imageId){
if(selectionArgs==null){
selectionArgs=new String[]{imageId};
return selectionArgs;
}else{
String[] newSelectionArgs=new String[selectionArgs.length+];
newSelectionArgs[]=imageId;
System.arraycopy(selectionArgs,,newSelectionArgs,,selectionArgs.length);
return newSelectionArgs;
}
}
public static String fixSelectionString(String selection){
selection=selection==null?"id=?":"id=? AND ("+selection+")";
return selection;
}
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db=mOpenHelper.getReadableDatabase();
switch (mUriMatcher.match(uri)){
case ALL_IMAGE:
return db.query(TABLE_NAME,projection,selection,selectionArgs,null,null,sortOrder);
case SINGLE_IMAGE:
String tableId=uri.getLastPathSegment();
selection=fixSelectionString(selection);
selectionArgs=fixSelectionArgs(selectionArgs,tableId);
return db.query(TABLE_NAME,projection,selection,selectionArgs,null,null,sortOrder);
default:
throw new IllegalArgumentException("error:"+uri);
}
}
只有使用Uri定位数据库中特定的记录时才会使用修改selection和selectionArgs参数的两个工具方法。注意:本例会在现有的selection前面加上ID列。这使得查询速度更快,因为对主键列的比较总是非常快的,从而能加快整个查询。
    编写数据库查询要把WHERE语句中简单的比较放在前面。这样做会加快查询,因为它能尽早决定是否要包含某个记录。
3.数据库事务
    每次在SQLite数据库执行一条SQL语句都会执行一次数据库事务操作。除非自己专门管理事务,否则每条语句都会自动创建一个事务。因为大多数ContentProvider调用最终只会生成一条SQL语句,这样情况下几乎没有必要去手动处理事务。但是,如果应用程序将执行多条SQL语句,比如一次插入很多条记录,记得总是自己管理事务。
    ContentProvider类提供了两个用于管理事务的方法:ContentProvider.bulkInsert()和ContentProvider.applyBatch(),下面的代码演示了如何实现bulkInsert()方法,它会在一个事务中插入多条记录。相比每次有新数据都调用ContentProvider.insert(),这种方法会明显快的多。
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db=mOpenHelper.getWritableDatabase();
Uri result=doInsert(uri,values,db);
return result;
} private Uri doInsert(Uri uri,ContentValues values,SQLiteDatabase db){
Uri result=null;
switch (mUriMatcher.match(uri)){
case ALL_IMAGE:
long id=db.insert(TABLE_NAME,"",values);
if(id==-) Log.i("ImageProvider","insert error");
result=Uri.withAppendedPath(uri,String.valueOf(id));
}
return result;
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
SQLiteDatabase db=mOpenHelper.getWritableDatabase();
int count=;
try{
db.beginTransaction();
for (ContentValues value:values){
Uri resultUri=doInsert(uri,value,db);
if(resultUri!=null){
count++;
}else{
count=;
throw new SQLException("error bulk");}
}
db.setTransactionSuccessful();
}finally {
db.endTransaction();
}
return count;
}
事务的语义很简单。首先调用SQLiteDatabase.beginTransaction()开始一个新的事务。当成功插入所有记录后调用SQLiteDatabase.setTransactionSuccessful(),然后使用SQLiteDatabase.endTransaction()结束本次事务。如果某条记录插入失败,会抛出SQLExcetion,而之前所有的插入都会回滚,因为在成功之前没有调用过SQLiteDatabase.setTransactionSuccessful()。
    强烈建议在继承ContentProvider时实现该方法,因为它显著提高数据插入的性能。但是,由于此方法只适用于插入操作,开发者可能需要实现另一个方法来处理更复杂的操作。
    如果要在一次事务中执行多次update()或则delete()语句,必须实现ContentProvider.applyBatch()方法。
@Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) throws OperationApplicationException {
SQLiteDatabase db=mOpenHelper.getWritableDatabase();
ContentProviderResult[] result=new ContentProviderResult[operations.size()];
try{
db.beginTransaction();
for (int i = ; i < operations.size(); i++) {
ContentProviderOperation operation=operations.get(i);
result[i]=operation.apply(this,result,i);
}
db.setTransactionSuccessful();
}finally {
db.endTransaction();
}
return super.applyBatch(operations);
}
 正如bulkInsert()方法,首先开始一个事务,执行操作,设置本事务执行成功,最后结束本次事务。该API是为了ContentProvider等较复杂的ContentProvider设计的,它们有许多的连接的表,每个都有自己的Uri。另外,如果要批量插入多个表,该API仍然有效。
4.在ContentProvider中存储二进制数据
    二进制数据包括不能用java中的简单数据类型表示的任何对象,通常是图像或其他一些媒体文件,但它可以是任何类型的专有格式文件。和二进制数据打交道可能会非常棘手,但幸好ContentProvider提供了许多用于处理这个问题的方法。比方说,上表的每条记录都存储了一张图片。
    覆写ContentProvider.openFile()方法返回ParcelFileDescriptor对象。然后就可以使用该对象直接读写文件了。
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
if(mUriMatcher.match(uri)==SINGLE_IMAGE){
String imageId=uri.getLastPathSegment();
File file=new File(Environment.getExternalStorageDirectory().toString()+TABLE_NAME+
imageId
);int fileMode = ;if (mode.contains("w")) fileMode |= ParcelFileDescriptor.MODE_WRITE_ONLY;if (mode.contains("r")) fileMode |= ParcelFileDescriptor.MODE_READ_ONLY;if (mode.contains("+")) fileMode |= ParcelFileDescriptor.MODE_APPEND;return ParcelFileDescriptor.open(file, fileMode);}else { return super.openFile(uri, mode);}}
 实际调用下面的方法将结果传递给调用客户端。当从ContentProvider中读取某条记录的Bitmap对象时,可以简单地使用该记录的Uri,接下来的事情给框架去处理:
public static Bitmap readBitmapFromProvider(Uri uri,ContentResolver resolver)throws FileNotFoundException{
return BitmapFactory.decodeStream(resolver.openInputStream(uri));
}
    存储文件跟正确的Uri调用ContentResolver.openOutputStream()相似。如果要从ContentProvider读取数据,并在ListView中展示包含文本和图片的数据,它会非常有用,前面的例子也展示了这一点。

版权声明:本文为博主原创文章,未经博主允许不得转载。

ContentProvider深度探索的更多相关文章

  1. 读书笔记《深度探索c++对象模型》 概述

    <深度探索c++对象模型>这本书是我工作一段时间后想更深入了解C++的底层实现知识,如内存布局.模型.内存大小.继承.虚函数表等而阅读的:此外在很多面试或者工作中,对底层的知识的足够了解也 ...

  2. Delphi深度探索-CodeSite应用指南

    Delphi深度探索-CodeSite应用指南 Delphi虽然为我们提供极其强大的调试功能,查找Bug仍然是一项艰巨的工作,通常我们写代码和调试代码的所消耗的时间是大致相同的,甚至有可能更多.为了减 ...

  3. 柔性数组-读《深度探索C++对象模型》有感 (转载)

    最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...

  4. 柔性数组-读《深度探索C++对象模型》有感

    最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...

  5. [读书系列] 深度探索C++对象模型 初读

    2012年底-2014年初这段时间主要用C++做手游开发,时隔3年,重新拿起<深度探索C++对象模型>这本书,感觉生疏了很多,如果按前阵子的生疏度来说,现在不借助Visual Studio ...

  6. 拾遗与填坑《深度探索C++对象模型》3.3节

    <深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...

  7. 拾遗与填坑《深度探索C++对象模型》3.2节

    <深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...

  8. Socket深度探索 4 PHP(转)

    [连载] Socket 深度探索 4 PHP (一) [连载] Socket 深度探究 4 PHP (二) [连载] Socket 深度探究 4 PHP (三)

  9. 深度探索C++对象模型

    深度探索C++对象模型 什么是C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各个支持的底层实现机制. 抽象性与实际性之间找出平衡点, 需要知识, 经验以及许多思考. 导读 这本书是C+ ...

随机推荐

  1. C++中动态申请二维数组并释放方法

    C/C++中动态开辟一维.二维数组是非常常用的,以前没记住,做题时怎么也想不起来,现在好好整理一下. C++中有三种方法来动态申请多维数组 (1)C中的malloc/free (2)C++中的new/ ...

  2. SOS 调试扩展 (SOS.dll)

    http://blog.csdn.net/cslie/article/details/2158780 SOS 调试扩展 (SOS.dll) 提供公共语言运行时(CLR)内部环境的有关信息,帮助你在Wi ...

  3. iOS类别(Category)

    iOS类别(Category)与扩展(Extension) 苹果的官方文档 Category在iOS开发中使用非常频繁.尤其是在为系统类进行拓展的时候,我们可以不用继承系统类,直接给系统类添加方法,最 ...

  4. Centos 卸载OpenJdk

    [root@localhost ~]# rpm -qa|grep jdkjava-1.6.0-openjdk-devel-1.6.0.0-1.50.1.11.5.el6_3.i686java-1.7. ...

  5. Mysql中关于 group_concat函数详解

    group_concat()主要功能:能将相同的行组合起来 完整的语法如下: group_concat([DISTINCT] 要连接的字段 [Order BY ASC/DESC 排序字段] [Sepa ...

  6. Oracle超出最大连接数问题及解决

    用过Oracle的应该都熟悉如何查看和设置Oracle数据库的最大连接数.这里就再啰嗦一遍. 查看当前的连接数,可以用select count(*) from v$process;设置的最大连接数(默 ...

  7. Codeforces Gym 100203G G - Good elements 暴力

    G - Good elementsTime Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.hust.edu.cn/vjudge/contest/ ...

  8. 解决mac的日历问题:服务器响应一个错误

    出了一个问题好久,平时也不用这个同步不靠谱的日历.今晚花点时间解决了下. 参考Apple 官网日历的问题解答. 当出现如下情况时: 退出日历和提醒事项. 从 Apple () 菜单中选取“系统偏好设 ...

  9. iOS常用的存储方式介绍

    在iOS App开发过程中经常需要操作一些需要持续性保留的数据,比如用户对于App的相关设置.需要在本地缓存的数据等等.本文针对OC中经常使用的一下存储方式做了个整理. 常用的存储工具/方式: NSU ...

  10. 双系统下,Windows如何正确删除Linux系统

    一般电脑装了双系统,特别是Windows加Linux的电脑,不可以在Windows中直接删了linux,因为一般安装linux的时候,grub都写进了mbr,直接删了Windows就进不了了,除非原来 ...