把经常重复使用的或者结构化的数据保存到一个数据库中是比较合适的,例如联系人信息(通讯录)。这一课,我们假定你熟悉一般的SQL数据库,并帮助你开始在Android系统中使用SQLite数据库。你在Android上使用数据库可能用到的API,都包含在android.database.sqlite包里。
Define a Schema and Contract
一个SQL数据库的主要原则是图式:正式宣布如何组织数据库。图式是反映在SQL语句用来创建你的数据库。你会发现它有助于创造一个伴侣类,称为一个合同,明确指定模式的布局在系统和自我记录方式。
合同类包含了那些用于定义URI,表和列的名字的常量。合同类允许你在同一个包中其他类里使用相同的常量。这让你可以在一个地方更改列的名称,就在你的代码中的传播。
组织合同类的一个好办法是把定义是全局的整个数据库在类的根级别。然后为每一个表创建一个内部类,列举这个表中的列。
注意:通过实现BaseColumns接口,你的内部类可以继承一个名为_ID的主键字段,一些Android类,如光标适配器会期望它的存在。它不是必需的,但这可以帮助你的数据库与Android框架协同工作。
例如,这段代码为一个表定义表名和列名:
public static abstract class FeedEntry implements BaseColumns { public static final String TABLE_NAME = "entry"; public static final String COLUMN_NAME_ENTRY_ID = "entryid"; public static final String COLUMN_NAME_TITLE = "title"; public static final String COLUMN_NAME_SUBTITLE = "subtitle"; ... }
为了防止有人不小心实例化这个合同类,给它一个空的构造函数。
// Prevents the FeedReaderContract class from being instantiated. private FeedReaderContract() {}
Create a Database Using a SQL Helper
一旦你定义了你的数据库看起来,你应该实现创建和维护数据库和表的方法。这里有一些典型的语句,用于创建和删除表:
private static final String TEXT_TYPE = " TEXT"; private static final String COMMA_SEP = ","; private static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + FeedReaderContract.FeedEntry.TABLE_NAME + " (" + FeedReaderContract.FeedEntry._ID + " INTEGER PRIMARY KEY," + FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP + FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP + ... // Any other options for the CREATE command " )"; private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + TABLE_NAME_ENTRIES;
就像那些保存在内部存储上的文件一样,Android将你的数据库保存在为应用程序分配的私有磁盘空间里。你的数据是安全的,因为默认情况下,这个区域不被其他应用程序访问。
SQLiteOpenHelper类中有一套有用的API。当你使用这个类获取引用你的数据库时,系统只在需要的时候和不在应用程序启动时,执行可能需要长时间运行的创建和更新数据库操作。你所需要做的就只是调用getWritableDatabase()或getReadableDatabase()。
注:因为他们可能会长时间运行,所以要确保在一个后台线程(例如:AsyncTask或者IntentService)中调用getWritableDatabase()或getReadableDatabase()。
要使用SQLiteOpenHelper,就创建一个子类,重写onCreate(),onUpgrade()和onOpen()回调方法。你可能还要实现onDowngrade(),但这不是必须的。
例如,下面是一个SQLiteOpenHelper的实现,使用到了上面显示的命令:
public class FeedReaderDbHelper extends SQLiteOpenHelper { // If you change the database schema, you must increment the database version. public static final int DATABASE_VERSION = 1; public static final String DATABASE_NAME = "FeedReader.db"; public FeedReaderDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); } public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // This database is only a cache for online data, so its upgrade policy is // to simply to discard the data and start over db.execSQL(SQL_DELETE_ENTRIES); onCreate(db); } public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { onUpgrade(db, oldVersion, newVersion); } }
要对数据库进行访问,就实例化你的SQLiteOpenHelper子类:
FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());
Put Information into a Database
通过传递一个ContentValues对象给insert()方法可以往数据库插入数据:
// Gets the data repository in write mode SQLiteDatabase db = mDbHelper.getWritableDatabase(); // Create a new map of values, where column names are the keys ContentValues values = new ContentValues(); values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID, id); values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title); values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_CONTENT, content); // Insert the new row, returning the primary key value of the new row long newRowId; newRowId = db.insert( FeedReaderContract.FeedEntry.TABLE_NAME, FeedReaderContract.FeedEntry.COLUMN_NAME_NULLABLE, values);
insert()的第一个参数就是表名。第二个参数提供一个列名,当ContentValues为空时,框架可以插入NULL值(你可以传递一个“NULL”,这样当没有值时框架不会插入一行)。
为了从数据库中读取数据,使用query()方法,传递你的选择规则和希望的列。该方法结合insert()和update()元素,除列列表定义你想要的数据获取,而不是数据插入。查询的结果是一个Cursor对象返回给你。
SQLiteDatabase db = mDbHelper.getReadableDatabase(); // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { FeedReaderContract.FeedEntry._ID, FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED, ... }; // How you want the results sorted in the resulting Cursor String sortOrder = FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED + " DESC"; Cursor c = db.query( FeedReaderContract.FeedEntry.TABLE_NAME, // The table to query projection, // The columns to return selection, // The columns for the WHERE clause selectionArgs, // The values for the WHERE clause null, // don't group the rows null, // don't filter by row groups sortOrder // The sort order );
看在光标行,用一个光标移动的方法,你必须在你开始读值调用。一般来说,你应该首先调用moveToFirst(),它将读取指针放在结果集的第一条记录上。对于每一条记录,你可以通过调用Cursor get方法,如getString()或getLong(),来读取其列值。对于每个获得方法,你必须传递渴望读取列的索引位置(可以通过调用getColumnIndex()或getColumnIndexOrThrow()来获取)。比如说:
cursor.moveToFirst(); long itemId = cursor.getLong( cursor.getColumnIndexOrThrow(FeedReaderContract.FeedEntry._ID) );
要从表中删除行,你需要提供的选择标准,确定行。数据库API提供了用于创建的选择标准,防止SQL注入机制。该机制将选择规范为选择条款和选择参数。本条款定义看列,并允许你将柱试验。参数值测试被绑定到条款。因为成绩不处理相同作为一个普通的SQL语句,它是免疫的SQL注入。
// Define 'where' part of query. String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; // Specify arguments in placeholder order. String[] selelectionArgs = { String.valueOf(rowId) }; // Issue SQL statement. db.delete(table_name, mySelection, selectionArgs);
当你需要修改你的数据库的值的子集,使用update()方法。
更新表结合delete()位置的语法的语法insert()含量值。
SQLiteDatabase db = mDbHelper.getReadableDatabase(); // New value for one column ContentValues values = new ContentValues(); values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title); // Which row to update, based on the ID String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; String[] selelectionArgs = { String.valueOf(rowId) }; int count = db.update( FeedReaderDbHelper.FeedEntry.TABLE_NAME, values, selection, selectionArgs);