这是我系列中有关在Android应用程序中保存数据的第四篇文章。 这是其他帖子:
简介:如何在Android应用程序中保存数据
将数据保存到Android应用程序中的文件
在您的Android应用程序中保存首选项
先前的帖子介绍了如何将文件保存到文件系统和首选项文件。 如果对于一个简单的应用程序,这就足够了,但是如果您的数据具有复杂的结构,或者如果您要保存很多数据,那么使用数据库是一个更好的选择。 管理数据库需要更多的知识和设置,但是它附带许多验证和性能优化。 Android SDK包括开源SQLite数据库引擎以及访问它所需的类。
SQLite是一个独立的关系数据库,不需要服务器即可工作。 数据库本身会保存到应用程序内部存储中的文件中,因此每个应用程序都有自己的专用数据库,其他应用程序无法访问该数据库。 您可以在http://www.sqlite.org上了解有关SQLite项目本身及其对SQL查询语言的实现的更多信息。
数据库新手? 关系数据库将数据保存到表中。 每个表都是由列组成的,并且对于每个列,您必须选择一个名称和可以保存在其中的数据类型。 每个表还应具有被设置为表键的一列或多列,以便可以唯一地标识每一行数据。 也可以在表之间定义关系。
数据库的基础知识和大多数数据库使用的SQL查询语言可能需要花很多篇文章来解释。 如果您不知道如何使用数据库,那么这是一个值得学习的主题,因为几乎所有应用程序都使用数据库来存储数据。
为了演示如何创建数据库并与之交互,我创建了一个小示例应用程序,该应用程序可从http://github.com/CindyPotvin/RowCounter获得 。 该应用程序是用于编织项目的行计数器:用户可以创建一个编织项目,其中包含一个或多个计数器,这些计数器用于跟踪当前完成的行数并显示要达到的总行数。 数据库的结构如下,其中一个项目表与一个row_counter表相关: 首先,为了能够创建数据库,我们需要为每个表指定一个合同类,以描述该表元素的名称。 每当需要数据库中元素的名称时,都应使用此类。 为了描述每列的名称,协定类还包含一个子类,该子类具有android.provider.BaseColumn的实现,该实现会自动添加_ID和_COUNT列的名称。 我还喜欢将CREATE TABLE SQL查询放在合同类中,以便SQL查询中使用的所有字符串都在同一位置。 这是示例中row_counter表的合同类:
/**
* This class represents a contract for a row_counter table containing row
* counters for projects. The project must exist before creating row counters
* since the counter have a foreign key to the project.
*/
public final class RowCounterContract {
/**
* Contains the name of the table to create that contains the row counters.
*/
public static final String TABLE_NAME = "row_counter";
/**
* Contains the SQL query to use to create the table containing the row counters.
*/
public static final String SQL_CREATE_TABLE = "CREATE TABLE "
+ RowCounterContract.TABLE_NAME + " ("
+ RowCounterContract.RowCounterEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ RowCounterContract.RowCounterEntry.COLUMN_NAME_PROJECT_ID + " INTEGER,"
+ RowCounterContract.RowCounterEntry.COLUMN_NAME_CURRENT_AMOUNT + " INTEGER DEFAULT 0,"
+ RowCounterContract.RowCounterEntry.COLUMN_NAME_FINAL_AMOUNT + " INTEGER,"
+ "FOREIGN KEY (" + RowCounterContract.RowCounterEntry.COLUMN_NAME_PROJECT_ID + ") "
+ "REFERENCES projects(" + ProjectContract.ProjectEntry._ID + "));";
/**
* This class represents the rows for an entry in the row_counter table. The
* primary key is the _id column from the BaseColumn class.
*/
public static abstract class RowCounterEntry implements BaseColumns {
// Identifier of the project to which the row counter belongs
public static final String COLUMN_NAME_PROJECT_ID = "project_id";
// Final amount of rows to reach
public static final String COLUMN_NAME_FINAL_AMOUNT = "final_amount";
// Current amount of rows done
public static final String COLUMN_NAME_CURRENT_AMOUNT = "current_amount";
}
}
要创建用于存储合同描述的数据的表,您必须实现android.database.sqllite.SQLLiteOpenHelper类,该类管理对数据库的访问。 应根据需要实施以下方法:
- onCreate:第一次由您的应用程序打开数据库时调用此方法。 您应该通过创建表并初始化所需的任何数据来设置数据库以用于该方法。
- onUpdate:升级应用程序且版本号已更改时,将调用此方法。 您不需要为第一个版本执行任何操作,但是在以下版本中,您必须提供查询以根据需要将数据库从旧版本修改为新结构,以便您的用户在升级期间不会丢失其数据。
- onDowngrade(可选):如果要处理将应用程序降级到需要较旧版本的情况,则可以实现此方法。 默认实现将引发SQLiteException且不会修改数据库。
- onOpen(可选):在创建数据库,升级到新版本或降级到旧版本之后,将调用此方法。
这是android.database.sqllite.SQLLiteOpenHelper的基本实现,该示例对onCreate方法中的数据库的每个表执行SQL CREATE TABLE查询。 android.database.sqlite.SQLiteDatabase类中没有可用的方法来创建表格,因此您必须使用execSQL方法来执行查询。
/**
* This class helps open, create, and upgrade the database file containing the
* projects and their row counters.
*/
public class ProjectsDatabaseHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version.
public static final int DATABASE_VERSION = 1;
// The name of the database file on the file system
public static final String DATABASE_NAME = "Projects.db";
public ProjectsDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/**
* Creates the underlying database with the SQL_CREATE_TABLE queries from
* the contract classes to create the tables and initialize the data.
* The onCreate is triggered the first time someone tries to access
* the database with the getReadableDatabase or
* getWritableDatabase methods.
*
* @param db the database being accessed and that should be created.
*/
@Override
public void onCreate(SQLiteDatabase db) {
// Create the database to contain the data for the projects
db.execSQL(ProjectContract.SQL_CREATE_TABLE);
db.execSQL(RowCounterContract.SQL_CREATE_TABLE);
initializeExampleData(db);
}
/**
* This method must be implemented if your application is upgraded and must
* include the SQL query to upgrade the database from your old to your new
* schema.
*
* @param db the database being upgraded.
* @param oldVersion the current version of the database before the upgrade.
* @param newVersion the version of the database after the upgrade.
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Logs that the database is being upgraded
Log.i(ProjectsDatabaseHelper.class.getSimpleName(),
"Upgrading database from version " + oldVersion + " to " + newVersion);
}
}
一旦android.database.sqllite.SQLLiteOpenHelper实现,你可以得到数据库对象android.database.sqlite.SQLiteDatabas的一个实例,使用助手的getReadableDatabase方法E,如果你只需要如果你需要读取数据或getWritableDatabase方法读取和写入数据。 数据可以执行四种基本操作,并且不能像所有数据库一样撤消修改。
- 插入新行: android.database.sqlite.SQLiteDatabase对象的insert方法在表中插入新数据行。 可以使用execSQL方法通过SQL INSERT查询插入数据,但建议使用insert以避免SQL注入: insert方法只能创建一个数据库行,而无论输入内容如何。 在以下示例中,在创建表之后,将通过数据库助手的onCreate方法在应用程序的数据库中初始化一些测试项目:
/** * Initialize example data to show when the application is first installed. * * @param db the database being initialized. */ private void initializeExampleData(SQLiteDatabase db) { // A lot of code is repeated here that could be factorized in methods, // but this is clearer for the example // Insert the database row for an example project in the project table in the // database long projectId; ContentValues firstProjectValues = new ContentValues(); firstProjectValues.put(ProjectContract.ProjectEntry.COLUMN_NAME_TITLE, "Flashy Scarf"); projectId = db.insert(ProjectContract.TABLE_NAME, null, firstProjectValues); // Insert the database rows for a row counter linked to the project row // just created in the database (the insert method returns the // identifier of the row) ContentValues firstProjectCounterValues = new ContentValues(); firstProjectCounterValues.put(RowCounterContract .RowCounterEntry.COLUMN_NAME_PROJECT_ID, projectId); firstProjectCounterValues.put(RowCounterContract .RowCounterEntry.COLUMN_NAME_FINAL_AMOUNT, 120); db.insert(RowCounterContract.TABLE_NAME, null, firstProjectCounterValues); // Insert the database row for a second example project in the project // table in the database. ContentValues secondProjectValues = new ContentValues(); secondProjectValues.put(ProjectContract.ProjectEntry.COLUMN_NAME_TITLE, "Simple Socks"); projectId = db.insert(ProjectContract.TABLE_NAME, null, secondProjectValues); // Insert the database rows for two identical row counters for the // project in the database ContentValues secondProjectCounterValues = new ContentValues(); secondProjectCounterValues.put(RowCounterContract .RowCounterEntry.COLUMN_NAME_PROJECT_ID, projectId); secondProjectCounterValues.put(RowCounterContract .RowCounterEntry.COLUMN_NAME_FINAL_AMOUNT, 80); db.insert(RowCounterContract.TABLE_NAME, null, secondProjectCounterValues); db.insert(RowCounterContract.TABLE_NAME, null, secondProjectCounterValues); }
- 读取现有行: android.database.sqlite.SQLiteDatabase类中的查询方法检索以前插入数据库中的数据。 此方法将返回一个游标,该游标指向您的请求返回的行的集合(如果有)。 然后,您可以将从数据库表中获取的数据转换为可以在您的应用程序中使用的对象:在示例中, 项目表中的行将转换为Project对象。
/** * Gets the list of projects from the database. * * @return the current projects from the database. */ public ArrayList getProjects() { ArrayList projects = new ArrayList(); // Gets the database in the current database helper in read-only mode SQLiteDatabase db = getReadableDatabase(); // After the query, the cursor points to the first database row // returned by the request. Cursor projCursor = db.query(ProjectContract.TABLE_NAME, null, null, null, null, null, null); while (projCursor.moveToNext()) { // Get the value for each column for the database row pointed by // the cursor using the getColumnIndex method of the cursor and // use it to initialize a Project object by database row Project project = new Project(); int idColIndex = projCursor.getColumnIndex(ProjectContract.ProjectEntry._ID); long projectId = projCursor.getLong(idColIndex); project.setId(projCursor.getLong(projectId); int nameColIndex = projCursor.getColumnIndex(ProjectContract .ProjectEntry.COLUMN_NAME_TITLE); project.setName(projCursor.getString(nameColIndex)); // Get all the row counters for the current project from the // database and add them all to the Project object project.setRowCounters(getRowCounters(projectId)); projects.add(project); } return (projects); }
- 更新现有行: android.database.sqlite.SQLiteDatabase类的实例的update方法更新数据库表中一行或多行中的数据。 像插入方法一样,您可以使用execSQL查询来运行SQL UPDATE查询,但是使用更新方法更安全。 在以下示例中,使用新值更新row_counter表中行计数器的当前行计数器值。 根据指定的条件,仅更新带有作为参数传递的标识符的行计数器,但是使用其他条件,则可以更新许多行,因此,应始终确保该条件仅选择所需的行。
/** * Updates the current amount of the row counter in the database to the value * in the object passed as a parameter. * * @param rowCounter the object containing the current amount to set. */ public void updateRowCounterCurrentAmount(RowCounter rowCounter) { SQLiteDatabase db = getWritableDatabase(); ContentValues currentAmountValue = new ContentValues(); currentAmountValue.put(RowCounterContract.RowCounterEntry.COLUMN_NAME_CURRENT_AMOUNT, rowCounter.getCurrentAmount()); db.update(RowCounterContract.TABLE_NAME, currentAmountValue, RowCounterContract.RowCounterEntry._ID +"=?", new String[] { String.valueOf(rowCounter.getId()) }); }
- 删除现有行: android.database.sqlite.SQLiteDatabase类的实例的delete方法删除数据库表的一行或多行。 与插入方法一样,您可以使用execSQL查询运行SQL UPDATE查询,但是使用delete方法更安全。 在以下示例中,将删除row_counter表中的行计数器。 根据指定的条件,只会删除带有作为参数传递的标识符的行计数器,但在另一个条件下,您可以删除许多行,因此应始终确保该条件仅选择所需的行,因此也不要删除很多数据。
/** * Deletes the specified row counter from the database. * * @param rowCounter the row counter to remove. */ public void deleteRowCounter(RowCounter rowCounter) { SQLiteDatabase db = getWritableDatabase(); db.delete(RowCounterContract.TABLE_NAME, RowCounterContract.RowCounterEntry._ID +"=?", new String[] { String.valueOf(rowCounter.getId()) }); }
最后,如果您希望封装对数据库中数据的访问权限以避免在活动中直接调用数据库帮助程序,则还可以从Android SDK中实现android.content.ContentProvider类。 仅当您的应用程序必须与其他应用程序共享数据时才需要这样做:您不需要一个就可以入门,但是随着数据变得越来越复杂,您应该考虑使用它。
翻译自: https://www.javacodegeeks.com/2014/08/saving-to-a-sqlite-database-in-your-android-application.html