在下一节,我们将探讨第12章给出的和视频相关的元数据信息的持久性存储:标题、描述和视频的URL。这块代码驻留在Android的内容提供者中,这对于数据库代码是比较合适的。在这里将不详细说明内容提供者,而将探讨如何编写数据库。第12章将详细说明如何实现内容提供者。后面的代码将帮助说明在Android中如何创建和使用SQLite。这个应用将通过sqlite3命令行工具使用和之前相同的数据库。但是,这次我们将编写代码,使用Android API来操作数据。
SimpleVideoDbHelper类的基础结构
在这个例子中,文件SimpleFinchVideoContentProvider.java封装了所有必要的SQL逻辑,其能够在Android的simple_video数据库中正常工作。需要在该数据库中访问持久性数据的应用和它提供的供应商及游标交互,第12章将解释这些内容。把数据真正是如何存储的详细信息完全在客户端屏蔽。这是良好的编程习惯,应该在所有的使用数据库的Android应用中这样做。
现在,因为我们的重点是在Android中如何使用数据库,了解SimpleVideoDbHelper是provider中的数据库模型:所有和数据库实现相关的,如数据库名、列名、表的定义,在类中都起作用。当然,对于大型、复杂的数据库,helper类可能要复杂得多,有多个组件组成。
SimpleVideoDbHelper类继承自抽象类SQLiteOpenHelper,因此必须重写onCreate方法和onUpgrade方法。当应用第一次启动时会自动调用onCreate方法,其任务是要创建数据库。当提交新版本的应用时,可能需要更新数据库,可能增加表、增加列甚至是完全修改模式。当必要的时候,任务会落到onUpgrade方法上,每当构造函数调用的DATABASE_VERSION版本和保存在数据库中的版本不同时就会调用onUpgrade方法。当提交新版本的数据库时,必须对其版本号执行递增操作:
public static final String VIDEO_TABLE_NAME = /"video/";public static final String DATABASE_NAME = SIMPLE_VIDEO + /".db/";private static int DATABASE_VERSION = 2;public static final int ID_COLUMN = 0;public static final int TITLE_COLUMN = 1;public static final int DESCRIPTION_COLUMN = 2;public static final int TIMESTAMP_COLUMN = 3;public static final int QUERY_TEXT_COLUMN = 4;public static final int MEDIA_ID_COLUMN = 5;private static class SimpleVideoDbHelper extends SQLiteOpenHelper { private SimpleVideoDbHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) { super(context, name, factory, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase sqLiteDatabase) { createTable(sqLiteDatabase); } private void createTable(SQLiteDatabase sqLiteDatabase) { String qs = /"CREATE TABLE /" + VIDEO_TABLE_NAME + /" (/" + FinchVideo.SimpleVideos._ID + /" INTEGER PRIMARY KEY AUTOINCREMENT, /" + FinchVideo.SimpleVideos.TITLE_NAME + /" TEXT, /" + FinchVideo.SimpleVideos.DESCRIPTION_NAME + /" TEXT, /" + FinchVideo.SimpleVideos.URI_NAME + /" TEXT);/"; sqLiteDatabase.execSQL(qs); } @Override public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldv, int newv) { sqLiteDatabase.execSQL(/"DROP TABLE IF EXISTS /" + VIDEO_TABLE_NAME + /";/"); createTable(sqLiteDatabase); }}
和SimpleVideoDbHelper代码相关的基本元素是:
常量
SimpleVideoDbHelper类定义了四个重要的常量:
DATABASE_NAME
它保存了数据库的文件名称,如这个例子中是simple_video.db。它对实际的SQLite数据库文件进行命名。我们之前提到过,该文件保存在路径/data/data/com.oreilly.demo.pa.finchvideo/databases/simple_video.db中,Android会创建该数据库文件。
DATABASE_VERSION
它定义了数据库版本,当修改数据库模式时,可以任意选择和增加该版本号。如果机器的数据库版本号小于DATABASE_VERSION,系统会运行onUpgrade方法,把数据库升级到当前版本。
VIDEO_TABLE_NAME
这是在这个简单的数据库中唯一的表名。
*_NAME
这些是数据库的列名。正如前面提到的,为通过游标访问的任何表定义一个名为_id的字段并把它作为主键是必须的。
构造函数
该provider的数据库构造函数SimpleVideoDbHelper使用super函数调用其父类的构造函数。父类的构造函数执行了创建数据库对象的大部分操作。
onCreate
当Android应用尝试从数据库中读取数据或写入数据,而该数据库不存在时,Android框架会执行onCreate方法。在YouTubeDbHelper类中的onCreate方法显示了创建数据库的一种方式。如果初始化数据库需要大量的SQL代码,最好把这些代码保存在resource文件strings.xml中。这会增强Java代码的可读性。但是它也使得开发人员在修改代码时需要查看两个不同的文件。当然,如果程序使用的数据库很简单,如在SimpleVideoDbHelper中所执行的,则直接在Java代码中编写SQL可能更简单,或者如果使用查询生成器,则可能都不需要SQL。
注意:如果想从String resource中加载SQL,必须注意在Android文档中所描述的string的变化:在resource字符串中通过反斜杠escape所有单引号和双引号(把/"改成/",/'改成/'),使用属性formatted=/"false/"。例如:
<string name=/"sql_query/" formatted=/"false/"> SELECT * FROM videos WHERE name LIKE /"%cycle%/" </string>
onCreate方法实际上并不需要创建数据库。传递给该方法一个新的、空数据库,必须完全初始化它。在SimpleVideoDbHelper中,这是个简单的任务,是通过调用createVideosTable完成的。
onUpdate
SimpleVideoContentProvider的onUpdate方法非常简单:它删除数据库。当provider在后期要使用数据库时,Android会调用onCreate方法,因为数据库不存在。虽然在这个非常简单的例子中,这种方式可能是可以接受的,provider只是作为网络数据的缓存,它肯定不适合作为联系方式数据库。如果你的客户在升级软件版本时,需要每次重新设置信息,他们肯定很不乐意。因此,在现实中,该onUpdate方法没有什么用。通常而言,onUpdate方法需要识别应用使用的所有之前版本的数据库,并通过数据安全的方式把这些数据库转化成最新的形式。更大的应用可能会有一些升级脚本,每个版本包含一个脚本。应用可以依次执行每个升级脚本,直到数据库完全更新。
createTable
我们创建这个函数,对创建表的SQL代码进行封装。