概述

在 Android 代码中创建和修改 SQLite 数据库,我们可以参考 Android 文档 Save data using SQLite,我们在 Android 中需要采取两个基本步骤来设置 SQLite 就可以和数据库互动了。如下所示:

  1. Define a schema and contract-创建架构和契约类
  2. Create a database using an SQLOpenHelper-使用 SQLiteOpenHelper 创建数据库

完成上面两步后就可以执行创建、读取、更新和删除我们的数据的操作了。

创建架构和契约类

创建数据库架构其实就是需要规划数据库结构。所以我们需要问两个问题:

  1. 表格的名称是什么?
  2. 表格里列的名称和数据类型是什么?

还记得下面的语句吗?

CREATE TABLE  ( ,  , ...);

以宠物收容所场景为例:

Attribute Storage Class
Name(宠物名) TEXT(文本)
Breed(品种) TEXT(文本)
Gender(公母) INTEGER(0-未知,1-公、2-母)
Weight(体重) INTEGER(为了简单)

绘制表格,规划数据库结构
在 Android 应用中使用数据库_第1张图片

创建 Contract (契约)类

为什么要使用 Contract 类?

看个创建表格的两个例子:

String makeTableStatement = "CREATE TABLE entry (_id INTEGER PRIMARY KEY, entryid TEXT, title TEXT, subtitle TEXT)";---String SQL_CREATE_ENTRIES = "CREATE TABLE" + FeedEntry.TABLE_NAME + "(" + FeedEntry._ID + "INTEGER PRIMARY KEY," + FeedEntry.COLUMN_NAME_ENTRY_ID + "TEXT," + FeedEntry.COLUMN_NAME_TITLE+"TEXT);"

观察上述代码,我们发现第二个创建表格的语句中 表格名称和列名称存储在了常量里。 在生成 SQL 指令时,消除了出现拼写错误的可能性,或者不小心将不应该大写的字母大写了。如果想更改实际的列名称,也只需在一个地方更改下就行了。

总结使用 Contract 类的三个理由:

  • 帮助我们定义架构,规定了去哪里查找数据库常量;
  • 在生成 SQL 指令时,可以帮助我们避免拼写错误;
  • 使我们更容易更新数据库架构

创建 Contract 类

根据之前规划的数据库结构即我们定义的架构,我们在项目的主包下面创建一个包名为 data,然后在改包下创建一个新的 java 类名为 PetContract,将该类用 final 修饰,因为它只是用来提供常量,我们不需要扩展或为此外部类实现任何内容。使用我们定义的架构,为每个表格创建一个内部类,并为每个列标题创建常量,代码如下所示:

public final class PetContract {    private PetContract() {}    public static final class PetEntry implements BaseColumns {        public final static String TABLE_NAME = "pets";        public final static String _ID = BaseColumn._ID;        public final static String COLUMN_PET_NAME = "name";        public final static String COLUMN_PET_BREED = "breed";        public final static String COLUMN_PET_GENDER = "gender";        public final static String COLUMN_PET_WEIGHT = "weight";        public final static int GENDER_UNKONWN = 0;        public final static int GENDER_MALE = 1;        public final static int GENDER_FORMALE = 2;    }}

使用 SQLiteOpenHelper 创建数据库

dada 包中创建一个新的 PetDbHelper 类,该类继承自 SQLiteOpenHelper类。我们需要重写 onCreate()onUpGrade() 方法,并且为数据库名称和版本创建常量,同时别忘了创建该类的构造函数,还要为用来创建表格的 SQLite 指令创建一个字符串常量。

/** * Database helper for Pets app. Manages database creation and version management. */public class PetDbHelper extends SQLiteOpenHelper {    public static final String LOG_TAG = PetDbHelper.class.getSimpleName();    /** Name of the database file */    private static final String DATABASE_NAME = "shelter.db";    /**     * Database version. If you change the database schema, you must increment the database version.     */    private static final int DATABASE_VERSION = 1;    /**     * Constructs a new instance of {@link PetDbHelper}.     *     * @param context of the app     */    public PetDbHelper(Context context) {        super(context, DATABASE_NAME, null, DATABASE_VERSION);    }    /**     * This is called when the database is created for the first time.     */    @Override    public void onCreate(SQLiteDatabase db) {        // Create a String that contains the SQL statement to create the pets table        String SQL_CREATE_PETS_TABLE =  "CREATE TABLE " + PetEntry.TABLE_NAME + " ("                + PetEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "                + PetEntry.COLUMN_PET_NAME + " TEXT NOT NULL, "                + PetEntry.COLUMN_PET_BREED + " TEXT, "                + PetEntry.COLUMN_PET_GENDER + " INTEGER NOT NULL, "                + PetEntry.COLUMN_PET_WEIGHT + " INTEGER NOT NULL DEFAULT 0);";        // Execute the SQL statement        db.execSQL(SQL_CREATE_PETS_TABLE);    }    /**     * This is called when the database needs to be upgraded.     */    @Override    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {        // The database is still at version 1, so there's nothing to do be done here.    }}

写了这么多我们也不知道数据库是否能正常运行,我们可以通过下面的方法进行检查。

查看数据库是否正常运转:

  • 创建 displayDatabaseInfo() 方法,将其放置到我们代码中,只有 SQL 能正常运转时,该方法才能运转。
  • 将数据库下载到本地,通过在终端使用 SQL 语句查看。
 @Override    protected void onCreate(Bundle savedInstanceState) {    ...    displayDatabaseInfo()    }/**     * Temporary helper method to display information in the onscreen TextView about the state of     * the pets database.     */    private void displayDatabaseInfo() {        // To access our database, we instantiate our subclass of SQLiteOpenHelper        // and pass the context, which is the current activity.        PetDbHelper mDbHelper = new PetDbHelper(this);        // Create and/or open a database to read from it        SQLiteDatabase db = mDbHelper.getReadableDatabase();        // Perform this raw SQL query "SELECT * FROM pets"        // to get a Cursor that contains all rows from the pets table.        Cursor cursor = db.rawQuery("SELECT * FROM " + PetEntry.TABLE_NAME, null);        try {            // Display the number of rows in the Cursor (which reflects the number of rows in the            // pets table in the database).            TextView displayView = (TextView) findViewById(R.id.text_view_pet);            displayView.setText("Number of rows in pets database table: " + cursor.getCount());        } finally {            // Always close the cursor when you're done reading from it. This releases all its            // resources and makes it invalid.            cursor.close();        }    }

连接数据库流程

在 Android 应用中使用数据库_第2张图片

PetDbHelper mDbHelper = new PetDbHelper(this);SQLiteDatabase db = mDbHelper.getReadableDatabase();

请求数据库,mDbHelper 将检查是否已经存在一个数据库?

如果不存在,则 PetDbHelper 的实例将使用 onCreate() 方法创建一个数据库,接着创建 SQLiteDatabase 的实例对象返回给 Activity。

如果数据库已经存在,PetDbHelper 的实例将不会调用 onCreate() 方法,相反,将创建一个 SQLiteDatabase 的实例对象并关联现有的数据库,然后将该对象返回给请求数据库的 Activity。

最终时帮助我们创建 SQLiteDatabase 对象与 shelter 数据库关联的 SQLiteDatabase 对象,之后我们就可以通过该对象向 shelter 数据库传达 SQLite 指令了。

从设备中提取数据库

了解了数据库的连接流程,我们就可以检测数据库是否正常运转。当然我们还可以通过另一种方式,通过查看设备的文件系统,使我们能够实际地看到 petDbHelper 类在创建数据库,然后当数据库实际创建好后看到 .db 文件出现。

在 Android 应用中使用数据库_第3张图片

在这之前我们可以打开手机应用信息,将 pets 应用中清楚缓存和存储数据。同时注释掉代码中的 displayDatabaseInfo() 方法。在 Android Studio 中点击 Android Device Monitor 按钮,选择我们当前使用的模拟器,转到 File Explorer,该功能可以让我们浏览设备的文件系统,点开 data 文件夹,再点开里面的 data 文件夹,在里面找到我们应用的包,再打开包,查找文件,我们会发现没有 .db 文件,这时我们的 getReadableDatabase() 方法尚未被调用。

在 Android 应用中使用数据库_第4张图片

当我们把刚才注释掉的 displayDatabaseInfo() 方法重新恢复,运行代码后,我们再查看文件系统中该包下有了 shelter.db 数据库文件了。再运行一次就不会再出现新的文件了。如果想看 shelter.db 文件中的内容,可以通过 DDMS 下载文件,保存到电脑本地上,通过 SQL 语言在终端查看。

在 Android 应用中使用数据库_第5张图片

数据库对象及插入数据

在 Android 文档 Put information into a database 中,为我们举出了例子。

// Gets the data repository in write modeSQLiteDatabase db = mDbHelper.getWritableDatabase();// Create a new map of values, where column names are the keysContentValues values = new ContentValues();values.put(FeedEntry.COLUMN_NAME_TITLE, title);values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle);// Insert the new row, returning the primary key value of the new rowlong newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);

ContentValues 中存储了大量的键值对,键是数据库中的列名称,值就是插入的值。举例:

在 Android 应用中使用数据库_第6张图片

ContentValues values = new ContentValues();values.put(PetEntry.COLUMN_PET_NAME, "Garfield");values.put(PetEntry.COLUMN_PET_BREED, "Tabby");values.put(PetEntry.COLUMN_PET_GENDER, PetEntry.GENDER_MALE);values.put(PetEntry.COLUMN_PET_WEIGHT, 7);db.insert(PetEntry.TABLE_NAME, null, values);

注意 insert() 方法会返回新插入的 ID,如果出现错误它会返回 -1。

数据库查询方法

我们可以通过 db.rawquery() 方法来读取数据库,但不建议使用。就像存在 SQLiteDatabase.insert() 方法一样,我们可以使用 SQLiteDatabase.query() 方法来读取数据库,它会帮助我们构建查询,从而避免语法错误。我们看下 Android 文档来了解下:

SQLiteDatabase db = mDbHelper.getReadableDatabase();// Define a projection that specifies which columns from the database// you will actually use after this query.String[] projection = {    BaseColumns._ID,    FeedEntry.COLUMN_NAME_TITLE,    FeedEntry.COLUMN_NAME_SUBTITLE    };// Filter results WHERE "title" = 'My Title'String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";String[] selectionArgs = { "My Title" };// How you want the results sorted in the resulting CursorString sortOrder =    FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";Cursor cursor = db.query(    FeedEntry.TABLE_NAME,   // The table to query    projection,             // The array of columns to return (pass null to get all)    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    );

观察上述代码,我们发现首先定义了一个字符串数组 projection,它其实是指我们想要获取的列名称,就像这样的语句:

SELECT name, breed FROM pets;

定义 projection 使我们能够指定想要获取的具体列,默认则获取所有列,类似于:

SELECT * FROM pets;

projection 大小会影响到性能

调用 query() 方法,该方法具有大量输入参数,代表了 SELECT 语句的不同部分。第一个参数 projection 我们已经在上面介绍了,接下来 selectionselectionArgs 处理的是可选 WHERE 条件。例如根据 ID 选择单个宠物:

SELECT * FROM pets WHERE _id = 1;

使用 selectionselectionArgs 属性就是这样的:

// Define 'where' part of queryString selection = PetEntry._ID + "?";// Specify arguments in placeholder orderString[] selectionArgs = {"1"}; 

selection 参数是 WHERE 关键字之后的类型为 String 的,这里使用 ? 作为占位符,然后填充为 selectionArgs 参数中的值,它是一个字符串数组,负责替换这里 selection 中的问号,这里将其设为 1。

为什么使用 ? 号和 selectionArgs ,而不是直接写成 1 ?

这里没有区别,我们可以将 selectionArgs 设为 null,将 ? 号改为 1 。但是在某些情况下,选择内容可能来自表格,使用占位符是一种安全措施,可以防止出现 SQL 注入攻击,也就是用户不按套路出牌,编辑一些代码类内容输入,使我们的查询语句出现歧义错误。

调用 query() 方法后会返回一个 Cursor 对象,它是一种可以捕获数据库中所有子集的对象。

什么是 Cursor

简而言之,Cursor 是指 代表数据库中多行内容的对象。假设如下是我们整个数据库的架构:
在 Android 应用中使用数据库_第7张图片

如果我们针对这个数据库中这个表格调用 query() 方法,我们可以指定希望返回的值,然后这些信息就会以 Cursor 对象的形式返回给我们。

如果我们的选择参数是

SELECT * FROM pets

我们获得的 Cursor 对象,其中包含数据库中的各行内容。

如果我们希望进一步指定仅选择 pets 表格中宠物名为 Toto 的行,我们获得的 CUrsor 对象就是其中所有行里的宠物名是 Toto 的行,结果如下:
这里写图片描述

我们再来看另一个示例,假设从 pets 表格中选择 name 和 breed,也就是将白鸽缩小为仅返回这两列:
在 Android 应用中使用数据库_第8张图片

也就是将表格缩小为仅返回这两列,返回的 Cursor 对象包含所有行,但是仅包含 name 和 breed 列的详情。

Cursor 包含了是我们能够访问和浏览 Cursor 对象的方法,如果 Cursor 包含多行的话就可以浏览各行。

Cursor.getCount()

Cursor 对象代表了数据库中的行和列,此外,他还提供了这些行中的当前位置,为了能获得特定的数据,我们需要将 Cursor 移动到我们所需要的精确行

在 Android 应用中使用数据库_第9张图片

当我们第一次获取 Cursor 时,位置从 -1 开始,这是无效的位置,首个可用位置为0,然后逐步递增。

Cursor.moveToFirst

该方法既将 Cursor 中的位置移动到结果中的第一行,这使我们能够访问第一条记录里的数据。

在 Android 应用中使用数据库_第10张图片

Cursor.moveToLast

该方法会使我们跳到这里的最后一行。

在 Android 应用中使用数据库_第11张图片

Cursor.moveToPosition(int position)

该方法会将 Cursor 位置移动到指定位置。

在 Android 应用中使用数据库_第12张图片

上面方法返回的都是 Boolean 类型的值,这样可以帮助我们判断实际是否移动到了该位置。比如我们当前 Cursor 已经移动到了最后一行,再调用向下移动的方法就会返回 false。

我们可以使用不同的 get 方法从数据库中获取特定的值。
在 Android 应用中使用数据库_第13张图片

使用 getColumnIndex(String columnName)方法,来根据名称来获取列的索引。举例:

在 Android 应用中使用数据库_第14张图片

对于 Cursor 要注意的一个事项是,使用完毕后,一定要记得调用 cursor.close(),这样会完全清空 Cursor 使其无效。仅在完全操作完毕后调用该方法。不关闭 Cursor 的话,会因内存泄漏而降低性能。

文章内容总结自优达学城课程 Android 基础:数据存储

文章只是作为个人记录学习使用,如有不妥之处请指正,谢谢。

更多相关文章

  1. Android 中文设置成粗体的方法
  2. Bitmap和Drawable相互转换方法
  3. android onSaveInstanceState方法 横坚屏切换
  4. Android Studio 编译提示 aapt.exe finished with non-zero exit
  5. Android Sqlite数据库跨版本升级 保存之前数据
  6. android中处理json最佳方法

随机推荐

  1. Android(安卓)驱动之旅 第四章:在Android(
  2. 做自己的Android(安卓)ROM,屏蔽对framewor
  3. 第二部分:开发简要指南-第五章 支持不同的
  4. Android(安卓)应用的版本兼容 了解一下(理
  5. 《Android移动应用基础教程》(Android(安
  6. apk开发者!含泪狂刷Android基础面试118题,
  7. 【Android的从零单排开发日记】之入门篇(
  8. 年后陆续面试Android(安卓)15 家大厂,这个
  9. 《Android/OPhone 开发完全讲义》样章和
  10. 北京android待遇 月薪过万秘籍分享