6.2 数据库

本节介绍Android的数据库存储方式--SQLite的使用方法,包括:SQLite用到了哪些SQL语法,如何使用数据库管理操纵SQLitem,如何使用数据库帮助器简化数据库操作,以及如何利用SQLite改进登录页面的记住密码功能。

6.2.1  SQL的基本语法

SQL本质上是一种编程语言,它的学名叫做“结构化查询语言”(全称为Structured Query Language,简称SQL)。不过SQL语言并非通用的编程语言,它专用于数据库的访问和处理,更像是一种操作命令,所以常说SQL语句而不说SQL代码。标准的SQL语句分为3类:数据定义,数据操纵和数据控制。但不同的数据库往往有自己的实现。

SQLite是一种小巧的嵌入式数据库,使用方便,开发简单。如同MYSQL,Oracle那样,SQLite也采用SQL语句管理数据,由于它属于轻型数据库,不涉及复杂的数据控制操作,因此App开发只用到数据定义和数据操纵两类SQL语句。此外,SQLite的SQL语法与通用的SQL语法略有不同,接下来介绍的两类SQL语法全部基于SQLite。

    1.  数据定义语言

    数据定义语言(全称Data Definition Language,简称DDL)描述了怎样变更数据实体的框架结构。就SQLite而言,DDL语言主要包括3种操作:创建表格,删除表格,修改表结构,分别说明如下。

    (1)创建表格

    表格的创建动作由create命令完成,格式为“CREATE TABLE IF NOT EXISTS 表格名称(以逗号分隔的名字段定义);”。以用户信息表为例,它的建表语句如下:

CREATE TABLE IF NOT EXISTS user_info(
    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    name VARCHAR NOT NULL, age INTEGER NOT NULL,
    height LONG NOT NULL, weight FLOAT NOT NULL,
    married INTEGER NOT NULL, update_time VARCHAR NOT NULL);

上面的SQL语法与其他数据库的SQL语法有所出入,相关的注意点说明如下:

①  SQL语句不区分大小写,无论是create与table这类关键词,还是表格名称,字段名称都不区分大小写。唯一区分大小写的是被单引号括起来的字符串值。

②  为避免重复建表,应加上IF NOT EXISTS关键词,例如CREATE TABLE IF NOT EXISTS 表格名称 ▪▪▪▪

③  SQLite支持整型INTEGER,长整型LONG,字符串VARCHAR,浮点型FLOAT,但不支持布尔类型。布尔类型的数据要使用整型保存,如果直接保存布尔数据,在入库时SQLite会自动将它转为0或1,其中0表示false,1表示true。

④  建表时需要唯一标识字段,它的字段名为_id。创建新表都要加上该字段定义,例如_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL。

    (2)删除表格

    表格的删除动作由drop命令完成,格式为“DROP TABLE IF EXISTS 表格名称;”。下面是删除用户信息的SQL语句例子:

DROP TABLE IF EXISTS user_info;

    (3)修改表结构

    表格的修改动作由alter命令完成,格式为“ALTER TABLE 表格名称 修改操作;”。不过SQLite只支持增加字段,不支持修改字段,也不支持删除字段。对于字段增加操作,需要在alter之后补充add命令,具体格式如“ALTER TABLE 表格名称 ADD COLUMN 字段名称 字段类型;”。下面是给用户信息表增加手机号字段的SQL语句例子:

ALTER_TABLE user_info ADD COLUMN phone VARCHAR;
2.  数据操纵语言

数据操纵语言(全称Data Manipulation Language,简称DML)描述了怎样处理数据实体的内部记录。表格记录的操作类型包括添加,删除,修改,查询4类,分别说明如下:

(1)添加记录

    记录的添加动作由insert命令完成,格式为“INSERT INTO 表格名称(以逗号分隔的字段名列表)VALUES(以逗号分隔的字段值列表);”。下面是往用户信息表插入一条记录的SQL语句例子:

INSERT INTO user_info (name,age,height,weight,married,update_time)
VALUES ('张三',20,170,50,0,'20200504');

(2)删除记录

    记录的删除动作由delete命令完成,格式为“DELETE FROM 表格名称 WHERE 查询条件;”,其中查询条件的表达式形如“字段名=字段值”,多个字段的条件交集通过“AND”连接,条件并集通过“OR”连接。下面是从用户信息表删除指定记录的SQL语句例子:

DELETE FROM user_info WHERE name='张三';

(3)修改记录

    记录的修改动作由update命令完成,格式为“UPDATE 表格名称 SET 字段名=字段值 WHERE 查询条件;”。下面是对用户信息表更新指定记录的SQL语句例子:

UPDATE user_info SET married=1 WHERE name='张三';

(4)查询记录

    记录的查询动作由select命令完成,格式为“SELECT 以逗号分隔的字段名列表 FROM 表格名称 WHERE 查询条件;”。如果字段名列表填星号(*),则表示查询该表的所有字段。下面是从用户信息表查询指定记录的SQL语句例子:

SELECT*FROM user_info ORDER BY age ASC;

6.2.2  数据库管理器  SQLiteDatabase

    SQL语句毕竟只是SQL命令,若要在Java代码中操纵SQLite,还需专门的工具类。SQLiteDatabase便是Android提供的SQLite数据库管理器,开发者可以在活动页面代码中调用openOrCreateDatabase方法获得数据库实例,参考代码如下:

            SQLiteDatabase db = openOrCreateDatabase(mDatabaseName, Context.MODE_PRIVATE,null);
            String desc = String.format("数据库%s创建%s", db.getPath(), (db!=null)?"成功":"失败");
            tv_database.setText(desc);

 完整代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".DatabaseActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/btn_database_create"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="创建数据库"
            android:textColor="@color/black"
            android:textSize="17sp" />

        <Button
            android:id="@+id/btn_database_delete"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="删除数据库"
            android:textColor="@color/black"
            android:textSize="17sp" />
    </LinearLayout>
    <TextView
        android:id="@+id/tv_database"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="5dp"
        android:textColor="@color/black"
        android:textSize="17sp" />

</LinearLayout>
package com.example.datastorage;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class DatabaseActivity extends AppCompatActivity implements View.OnClickListener {
    private TextView tv_database; // 声明一个文本视图对象
    private String mDatabaseName;// 包含完整路径的数据库名称
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_database);

        tv_database=findViewById(R.id.tv_database);
        findViewById(R.id.btn_database_create).setOnClickListener(this);
        findViewById(R.id.btn_database_delete).setOnClickListener(this);
        // 生成一个测试数据库的完整路径
        mDatabaseName=getFilesDir()+"/test.db";
    }

    @Override
    public void onClick(View view) {
        if (view.getId()==R.id.btn_database_create){
            // 创建或打开数据库。数据库如果不存在就创建它,如果存在就打开它
            SQLiteDatabase db = openOrCreateDatabase(mDatabaseName, Context.MODE_PRIVATE,null);
            String desc = String.format("数据库%s创建%s", db.getPath(), (db!=null)?"成功":"失败");
            tv_database.setText(desc);
        } else if (view.getId()==R.id.btn_database_delete) {
            boolean result = deleteDatabase(mDatabaseName);// 删除数据库
            String desc = String.format("数据库%s删除%s", mDatabaseName, result?"成功":"失败");
            tv_database.setText(desc);
        }
    }
}

    首次运行测试App,调用openOrCreateDatabase方法会自动创建数据库,并返回该数据库的管理器实例,创建结果如图所示。

 

    获得数据库实例之后,就能对该数据库开展各项操作了。数据库管理器SQLiteDatabase提供了若干操作数据表的API,常用的方法有3类,列举如下:

    1.  管理类,用于数据库层面的操作

    ●  openDatabase:打开指定路径的数据库。

    ●  isOpen:判断数据库是否已打开。

    ●  close:关闭数据库。

    ●  getVersion:获取数据库的版本号。

    ●  setVersion:设置数据库的版本号。

    2.  事务类,用于事务层面的操作

    ●  beginTransaction:开始事务。

    ●  setTransactionSuccessful:设置事务的成功标志。

    ●  endTransaction:结束事务。执行本方法时,系统会判断之前是否调用了                                        setTransactionSuccessful方法,如果之前已调用该方法就提交事务,如果没有调用该方法就          回滚事务。

    3.  数据处理类,用于数据表层面的操作

    ●  execSQL:执行拼接好的SQL控制语句。一般用于建表,删表,变更表结构。

    ●  delete:删除符合条件的记录。

    ●  update:更新符合条件的记录信息。

    ●  insert:插入一条记录。

    ●  query:执行查询操作,并返回结果集的游标。

    ●  rawQuery:执行拼接好的SQL查询语句,并返回结果集的游标。

    在实际开发中,经常用到的是查询语句,建议写好查询操作的select语句,再调用rawQuery方法执行查询语句。

6.2.3  数据库帮助器  SQLiteOpenHelper

    由于SQLiteDatabase存在局限性,一不小心就会重复打开数据库,处理数据库的升级也不方便,因此Android提供了数据库帮助器SQLiteOpenHelper,帮助开发者合理使用SQLite。

    SQLiteOpenHelper的具体使用步骤如下:

01  新建一个继承SQLiteOpenHelper的数据库操作类,按提示重写onCreate和onUpgrade两个方          法。其中,onCreate方法只在第一次打开数据库时执行,在此可以创建表结构;而onUpgrade        方法在数据库版本升高时执行,在此可以根据新旧版本号变更表结构。

02  为保证数据库的安全使用,需要封装几个必要方法,包括获取单例对象,打开数据库连接,关        闭数据库连接,说明如下:

      ●  获取单例对象,确保在App运行过程中数据库只会打开一次,避免重复打开引起错误。

      ●  打开数据库连接:SQLite有锁机制,即读锁和写锁的处理,故而数据库连接也分两种,读              连接可调用getReadableDatabase方法获得,写连接可调用getWritableDatabase方法获得。

      ●  关闭数据库连接,数据库操作完毕,调用数据库实例的close方法关闭连接。

03  提供对表记录增加,删除,修改,查询的操作方法。

    能被SQLite直接使用的数据结构是ContentValues类,它类似于映射Map,也提供了put和get方法存取键值对。区别之处在于:ContentValues的键只能是字符串,不能是其他类型。ContentValues主要用于增加记录和更新记录,对应数据库的insert和update方法。

    记录的查询操作用到了游标类Cursor,调用query和rawQuery方法返回的都是Cursor对象,若要获取全部的查询结果,则需要根据游标的指示一条一条遍历结果集合。Cursor的常用方法可分为3类,说明如下:

    1.  游标控制类方法,用于指定游标的状态

    ●  close:关闭游标。

    ●  isClosed:判断游标是否关闭。

    ●  isFirst:判断游标是否在开头。

    ●  isLast:判断游标是否在末尾。

    2.  游标移动类方法,把游标移动到指定位置

    ●  moveToFirst:移动游标到开头。

    ●  moveToLast:移动游标到末尾。

    ●  moveToNext:移动游标到下一条记录。

    ●  moveToPrevious:移动游标到上一条记录。

    ●  move:往后移动游标若干条记录。

    ●  moveToPosition:移动游标到指定位置的记录。

    3.  获取记录类方法,可获取记录的数量,类型以及取值

    ●  getCount:获取结果记录的数量。

    ●  getInt:获取指定字段的整型值。

    ●  getLong:获取指定字段的长整数型值。

    ●  getFloat:获取指定字段的浮点数值。

    ●  getString:获取指定字段的字符串值。

    ●  getType:获取指定字段的字段类型。

    鉴于数据库操作的特殊性,不方便单独演示某个功能,接下来从创建数据库开始介绍,完整演示一下数据库的读写操作。用户注册信息的演示页面包括两个,分别是记录保存页面和记录读取页面,其中记录保存页面通过insert方法向数据库添加用户信息,完整代码如下:

package com.example.datastorage;

public class UserInfo {
    public long rowid; // 行号
    public int xuhao; // 序号
    public String name; // 姓名
    public int age; // 年龄
    public long height; // 身高
    public float weight; // 体重
    public boolean married; // 婚否
    public String update_time; // 更新时间
    public String phone; // 手机号
    public String password; // 密码

    public UserInfo() {
        rowid = 0L;
        xuhao = 0;
        name = "";
        age = 0;
        height = 0L;
        weight = 0.0f;
        married = false;
        update_time = "";
        phone = "";
        password = "";
    }
}
package com.example.datastorage;

import static android.provider.SyncStateContract.Helpers.update;
import static androidx.core.content.ContentResolverCompat.query;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class UserDBHelper extends SQLiteOpenHelper {
    private static final String TAG = "UserDBHelper";
    private static final String DB_NAME = "user.db"; // 数据库的名称
    private static final int DB_VERSION = 1; // 数据库的版本号
    private static UserDBHelper mHelper = null; // 数据库帮助器的实例
    private SQLiteDatabase mDB = null; // 数据库的实例
    public static final String TABLE_NAME = "user_info"; // 表的名称

    private UserDBHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    private UserDBHelper(Context context, int version) {
        super(context, DB_NAME, null, version);
    }

    // 利用单例模式获取数据库帮助器的唯一实例
    public static UserDBHelper getInstance(Context context, int version) {
        if (version > 0 && mHelper == null) {
            mHelper = new UserDBHelper(context, version);
        } else if (mHelper == null) {
            mHelper = new UserDBHelper(context);
        }
        return mHelper;
    }

    // 打开数据库的读连接
    public SQLiteDatabase openReadLink() {
        if (mDB == null || !mDB.isOpen()) {
            mDB = mHelper.getReadableDatabase();
        }
        return mDB;
    }

    // 打开数据库的写连接
    public SQLiteDatabase openWriteLink() {
        if (mDB == null || !mDB.isOpen()) {
            mDB = mHelper.getWritableDatabase();
        }
        return mDB;
    }

    // 关闭数据库连接
    public void closeLink() {
        if (mDB != null && mDB.isOpen()) {
            mDB.close();
            mDB = null;
        }
    }
    // 创建数据库,执行建表语句
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        Log.d(TAG, "onCreate");
        String drop_sql = "DROP TABLE IF EXISTS " + TABLE_NAME + ";";
        Log.d(TAG, "drop_sql:" + drop_sql);
        sqLiteDatabase.execSQL(drop_sql);
        String create_sql = "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " ("
                + "_id INTEGER PRIMARY KEY  AUTOINCREMENT NOT NULL,"
                + "name VARCHAR NOT NULL," + "age INTEGER NOT NULL,"
                + "height INTEGER NOT NULL," + "weight FLOAT NOT NULL,"
                + "married INTEGER NOT NULL," + "update_time VARCHAR NOT NULL"
                //演示数据库升级时要先把下面这行注释
                + ",phone VARCHAR" + ",password VARCHAR"
                + ");";
        Log.d(TAG, "create_sql:" + create_sql);
        sqLiteDatabase.execSQL(create_sql); // 执行完整的SQL语句
    }

    // 升级数据库,执行表结构变更语句
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        Log.d(TAG, "onUpgrade oldVersion=" + i + ", newVersion=" + i1);
        if (i1 > 1) {
            //Android的ALTER命令不支持一次添加多列,只能分多次添加
            String alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "phone VARCHAR;";
            Log.d(TAG, "alter_sql:" + alter_sql);
            sqLiteDatabase.execSQL(alter_sql);
            alter_sql = "ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + "password VARCHAR;";
            Log.d(TAG, "alter_sql:" + alter_sql);
            sqLiteDatabase.execSQL(alter_sql); // 执行完整的SQL语句
        }
    }

    // 根据指定条件删除表记录
    public int delete(String condition) {
        // 执行删除记录动作,该语句返回删除记录的数目
        return mDB.delete(TABLE_NAME, condition, null);
    }

    // 删除该表的所有记录
    public int deleteAll() {
        // 执行删除记录动作,该语句返回删除记录的数目
        return mDB.delete(TABLE_NAME, "1=1", null);
    }

    // 往该表添加一条记录
    public long insert(UserInfo info) {
        List<UserInfo> infoList = new ArrayList<UserInfo>();
        infoList.add(info);
        return insert(infoList);
    }

    // 往该表添加多条记录
    public long insert(List<UserInfo> infoList) {
        long result = -1;
        for (int i = 0; i < infoList.size(); i++) {
            UserInfo info = infoList.get(i);
            List<UserInfo> tempList = new ArrayList<UserInfo>();
            // 如果存在同名记录,则更新记录
            // 注意条件语句的等号后面要用单引号括起来
            if (info.name != null && info.name.length() > 0) {
                String condition = String.format("name='%s'", info.name);
                tempList = query(condition);
                if (tempList.size() > 0) {
                    update(info, condition);
                    result = tempList.get(0).rowid;
                    continue;
                }
            }
            // 如果存在同样的手机号码,则更新记录
            if (info.phone != null && info.phone.length() > 0) {
                String condition = String.format("phone='%s'", info.phone);
                tempList = query(condition);
                if (tempList.size() > 0) {
                    update(info, condition);
                    result = tempList.get(0).rowid;
                    continue;
                }
            }
            // 不存在唯一性重复的记录,则插入新记录
            ContentValues cv = new ContentValues();
            cv.put("name", info.name);
            cv.put("age", info.age);
            cv.put("height", info.height);
            cv.put("weight", info.weight);
            cv.put("married", info.married);
            cv.put("update_time", info.update_time);
            cv.put("phone", info.phone);
            cv.put("password", info.password);
            // 执行插入记录动作,该语句返回插入记录的行号
            result = mDB.insert(TABLE_NAME, "", cv);
            if (result == -1) { // 添加成功则返回行号,添加失败则返回-1
                return result;
            }
        }
        return result;
    }

    // 根据条件更新指定的表记录
    public int update(UserInfo info, String condition) {
        ContentValues cv = new ContentValues();
        cv.put("name", info.name);
        cv.put("age", info.age);
        cv.put("height", info.height);
        cv.put("weight", info.weight);
        cv.put("married", info.married);
        cv.put("update_time", info.update_time);
        cv.put("phone", info.phone);
        cv.put("password", info.password);
        // 执行更新记录动作,该语句返回更新的记录数量
        return mDB.update(TABLE_NAME, cv, condition, null);
    }

    public int update(UserInfo info) {
        // 执行更新记录动作,该语句返回更新的记录数量
        return update(info, "rowid=" + info.rowid);
    }

    // 根据指定条件查询记录,并返回结果数据列表
    public List<UserInfo> query(String condition) {
        String sql = String.format("select rowid,_id,name,age,height," +
                "weight,married,update_time,phone,password " +
                "from %s where %s;", TABLE_NAME, condition);
        Log.d(TAG, "query sql: " + sql);
        List<UserInfo> infoList = new ArrayList<UserInfo>();
        // 执行记录查询动作,该语句返回结果集的游标
        Cursor cursor = mDB.rawQuery(sql, null);
        // 循环取出游标指向的每条记录
        while (cursor.moveToNext()) {
            UserInfo info = new UserInfo();
            info.rowid = cursor.getLong(0); // 取出长整型数
            info.xuhao = cursor.getInt(1); // 取出整型数
            info.name = cursor.getString(2); // 取出字符串
            info.age = cursor.getInt(3); // 取出整型数
            info.height = cursor.getLong(4); // 取出长整型数
            info.weight = cursor.getFloat(5); // 取出浮点数
            //SQLite没有布尔型,用0表示false,用1表示true
            info.married = (cursor.getInt(6) == 0) ? false : true;
            info.update_time = cursor.getString(7); // 取出字符串
            info.phone = cursor.getString(8); // 取出字符串
            info.password = cursor.getString(9); // 取出字符串
            infoList.add(info);
        }
        cursor.close(); // 查询完毕,关闭数据库游标
        return infoList;
    }

    // 根据手机号码查询指定记录
    public UserInfo queryByPhone(String phone) {
        UserInfo info = null;
        List<UserInfo> infoList = query(String.format("phone='%s'", phone));
        if (infoList.size() > 0) { // 存在该号码的登录信息
            info = infoList.get(0);
        }
        return info;
    }

}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp"
    tools:context=".SQLiteWriteActivity">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="56dp">
        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="姓名:"
            android:gravity="center"
            android:textSize="17sp"/>
        <EditText
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="3dp"
            android:layout_marginTop="3dp"
            android:layout_toRightOf="@+id/tv_name"
            android:background="@drawable/editext_selector"
            android:gravity="left|center"
            android:hint="请输入姓名"
            android:inputType="text"
            android:maxLength="12"
            android:textColor="@color/black"
            android:textSize="17sp" />
    </RelativeLayout>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="40dp" >

        <TextView
            android:id="@+id/tv_age"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="年龄:"
            android:textColor="@color/black"
            android:textSize="17sp" />

        <EditText
            android:id="@+id/et_age"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="3dp"
            android:layout_marginTop="3dp"
            android:layout_toRightOf="@+id/tv_age"
            android:background="@drawable/editext_selector"
            android:gravity="left|center"
            android:hint="请输入年龄"
            android:inputType="number"
            android:maxLength="2"
            android:textColor="@color/black"
            android:textSize="17sp" />
    </RelativeLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="40dp" >

        <TextView
            android:id="@+id/tv_height"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="身高:"
            android:textColor="@color/black"
            android:textSize="17sp" />

        <EditText
            android:id="@+id/et_height"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="3dp"
            android:layout_marginTop="3dp"
            android:layout_toRightOf="@+id/tv_height"
            android:background="@drawable/editext_selector"
            android:gravity="left|center"
            android:hint="请输入身高"
            android:inputType="number"
            android:maxLength="3"
            android:textColor="@color/black"
            android:textSize="17sp" />
    </RelativeLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="40dp" >

        <TextView
            android:id="@+id/tv_weight"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="体重:"
            android:textColor="@color/black"
            android:textSize="17sp" />

        <EditText
            android:id="@+id/et_weight"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="3dp"
            android:layout_marginTop="3dp"
            android:layout_toRightOf="@+id/tv_weight"
            android:background="@drawable/editext_selector"
            android:gravity="left|center"
            android:hint="请输入体重"
            android:inputType="numberDecimal"
            android:maxLength="5"
            android:textColor="@color/black"
            android:textSize="17sp" />
    </RelativeLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="40dp" >

        <CheckBox
            android:id="@+id/ck_married"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:checked="false"
            android:text="已婚"
            android:textColor="@color/black"
            android:textSize="17sp" />
    </RelativeLayout>

    <Button
        android:id="@+id/btn_save"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存到数据库"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <Button
        android:id="@+id/btn_jump"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳转到数据库"
        android:textColor="@color/black"
        android:textSize="17sp" />
</LinearLayout>
package com.example.datastorage;

import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Toast;

public class SQLiteWriteActivity extends AppCompatActivity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
    private UserDBHelper mHelper; // 声明一个用户数据库帮助器的对象
    private EditText et_name; // 声明一个编辑框对象
    private EditText et_age; // 声明一个编辑框对象
    private EditText et_height; // 声明一个编辑框对象
    private EditText et_weight; // 声明一个编辑框对象
    private boolean isMarried = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sqlite_write);

        et_name = findViewById(R.id.et_name);
        et_age = findViewById(R.id.et_age);
        et_height = findViewById(R.id.et_height);
        et_weight = findViewById(R.id.et_weight);
        CheckBox ck_married = findViewById(R.id.ck_married);
        ck_married.setOnCheckedChangeListener(this);
        findViewById(R.id.btn_save).setOnClickListener(this);
        findViewById(R.id.btn_jump).setOnClickListener(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 获得数据库帮助器的实例
        mHelper = UserDBHelper.getInstance(this,1);
        mHelper.openWriteLink();// 打开数据库帮助器的写连接
    }

    @Override
    protected void onStop() {
        super.onStop();
        mHelper.closeLink();// 关闭数据库连接
    }

    @Override
    public void onClick(View view) {
        if (view.getId()==R.id.btn_save){
            String name = et_name.getText().toString();
            String age = et_age.getText().toString();
            String height = et_height.getText().toString();
            String weight = et_weight.getText().toString();
            if (TextUtils.isEmpty(name)) {
                Toast.makeText(this, "请先填写姓名",Toast.LENGTH_LONG).show();
                return;
            } else if (TextUtils.isEmpty(age)) {
                Toast.makeText(this, "请先填写年龄",Toast.LENGTH_LONG).show();
                return;
            } else if (TextUtils.isEmpty(height)) {
                Toast.makeText(this, "请先填写身高",Toast.LENGTH_LONG).show();
                return;
            } else if (TextUtils.isEmpty(weight)) {
                Toast.makeText(this, "请先填写体重",Toast.LENGTH_LONG).show();
                return;
            }
            // 以下声明一个用户信息对象,并填写它的各字段值
            UserInfo info = new UserInfo();
            info.name = name;
            info.age = Integer.parseInt(age);
            info.weight = Float.parseFloat(weight);
            info.married = isMarried;
            info.update_time = DateUtil.getNowDateTime("yyyy-MM-dd HH:mm:ss");
            mHelper.insert(info);// 执行数据库帮助器的插入操作
            Toast.makeText(this, "数据已写入SQLite数据库",Toast.LENGTH_LONG).show();
        } else if (view.getId()==R.id.btn_jump) {
            Intent intent = new Intent(this, SQLiteReadActivity.class);
            startActivity(intent);
        }
    }

    @Override
    public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
        isMarried = b;
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".SQLiteReadActivity">
    <Button
        android:id="@+id/btn_delete"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="删除所有记录"
        android:textSize="17sp"/>
    <TextView
        android:id="@+id/tv_sqlite"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="5dp"
        android:textColor="@color/black"
        android:textSize="17sp" />

</LinearLayout>
package com.example.datastorage;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;

public class SQLiteReadActivity extends AppCompatActivity implements View.OnClickListener {
    private UserDBHelper mHelper; // 声明一个用户数据库帮助器的对象
    private TextView tv_sqlite; // 声明一个文本视图对象
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sqlite_read);

        tv_sqlite = findViewById(R.id.tv_sqlite);
        findViewById(R.id.btn_delete).setOnClickListener(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 获得数据库帮助器的实例
        mHelper = UserDBHelper.getInstance(this, 1);
        mHelper.openReadLink(); // 打开数据库帮助器的读连接
        readSQLite(); // 读取数据库中保存的所有用户记录
    }

    @Override
    protected void onStop() {
        super.onStop();
        mHelper.closeLink(); // 关闭数据库连接
    }

    // 读取数据库中保存的所有用户记录
    private void readSQLite() {
        if (mHelper == null) {
            Toast.makeText(this, "数据库连接为空", Toast.LENGTH_SHORT).show();
            return;
        }
        // 执行数据库帮助器的查询操作
        List<UserInfo> userList = mHelper.query("1=1");
        String desc = String.format("数据库查询到%d条记录,详情如下:", userList.size());
        for (int i = 0; i < userList.size(); i++) {
            UserInfo info = userList.get(i);
            desc = String.format("%s\n第%d条记录信息如下:", desc, i + 1);
            desc = String.format("%s\n 姓名为%s", desc, info.name);
            desc = String.format("%s\n 年龄为%d", desc, info.age);
            desc = String.format("%s\n 身高为%d", desc, info.height);
            desc = String.format("%s\n 体重为%f", desc, info.weight);
            desc = String.format("%s\n 婚否为%b", desc, info.married);
            desc = String.format("%s\n 更新时间为%s", desc, info.update_time);
        }
        if (userList.size() <= 0) {
            desc = "数据库查询到的记录为空";
        }
        tv_sqlite.setText(desc);
    }

    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.btn_delete) {
            mHelper.closeLink(); // 关闭数据库连接
            mHelper.openWriteLink(); // 打开数据库帮助器的写连接
            mHelper.deleteAll(); // 删除所有记录
            mHelper.closeLink(); // 关闭数据库连接
            mHelper.openReadLink(); // 打开数据库帮助器的读连接
            readSQLite(); // 读取数据库中保存的所有用户记录
            Toast.makeText(this, "已删除所有记录", Toast.LENGTH_SHORT).show();
        }
    }
}

运行测试App,先打开记录保存页面,依次录入信息并将两个用户的注册信息保存至数据库,如图所示。

 

6.2.4  优化记住密码功能 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/398103.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习——概念引入

深度学习 深度学习简介深度学习分类根据网络结构划分&#xff1a;循环神经网络卷积神经网络 根据学习方式划分&#xff1a;监督学习无监督学习半监督学习 根据应用领域划分&#xff1a;计算机视觉自然语言处理语音识别生物信息学 深度学习简介 深度学习&#xff08;Deep Learni…

将Windows电脑右下角的“中”字或“英”字输入法状态隐藏的方法

本文介绍在Windows 11操作系统中&#xff0c;将任务栏右下角的语言栏的“中”、“英”标识加以隐藏、消除的一种或许可行的方法。 最近换了新电脑&#xff0c;终于用上了Windows 11操作系统。但是&#xff0c;默认状态下&#xff0c;在任务栏最右侧&#xff0c;也就是屏幕右下角…

2024最新版Redis安装使用指南

2024最新版Redis安装使用指南 Installation and Usage Guide to the Latest Redis in 2024 By JacksonML 1. 什么是Redis? The open-source, in-memory data store used by millions of developers as a cache, vector database, document database, streaming engine, an…

MSS与cwnd的关系,rwnd又是什么?

慢启动算法是指数递增的 这种指数增长的方式是慢启动算法的一个核心特点&#xff0c;它确保了TCP连接在开始传输数据时能够快速地探测网络的带宽容量&#xff0c;而又不至于过于激进导致网络拥塞。具体来说&#xff1a; 初始阶段&#xff1a;当TCP连接刚建立时&#xff0c;拥…

Prometheus 教程

目录 一、简介二、下载安装1、安装 prometheus2、安装 alertmanager3、安装 grafana4、安装 node_exporter5、安装 mysqld_exporter 一、简介 Prometheus 是一个开源的系统监控和警报工具。它最初由 SoundCloud 开发&#xff0c;并于 2012 年发布为开源项目。Prometheus 专注于…

【leetcode刷题】 93.复原IP地址单层逻辑特殊点总结

这个跟131分割回文串比较类似&#xff0c;但是这里的回溯过程需要注意两个事项&#xff0c;一个是横向深入时要考虑到原字符串中加入“.”所以计数的idx从i2开始。纵向回退时要把用来控制结束时机的pointnum减掉1&#xff0c;再把这时已经加入了“.”的字符串去掉“.”。 判断合…

关于node与node-sass那些事

昨晚找了之前的一个项目想要复习下&#xff0c;结果npm i报错&#xff0c;大致意思就是noda-sass的版本和node的对不上&#xff0c;那怎么办呢&#xff1a; 1.换node版本&#xff0c;那好吧&#xff0c;首先要明白&#xff0c;对应的版本关系 2.然后我开始用nvm换node版本&am…

金晨颜值逆袭。每年一个样,美丽爆表。

♥ 为方便您进行讨论和分享&#xff0c;同时也为能带给您不一样的参与感。请您在阅读本文之前&#xff0c;点击一下“关注”&#xff0c;非常感谢您的支持&#xff01; 文 |猴哥聊娱乐 编 辑|徐 婷 校 对|侯欢庭 微博热议金晨颜值蜕变&#xff01;从20岁清纯到31岁明艳&…

RHEL8提示需要注册才可以yum解决办法

关闭注册以及修改更新远&#xff08;已注册的RHEL8忽略本步骤&#xff09; 原因&#xff1a;因为没注册的红帽子是无法连接到官方的Yum源的 箭头所指的改成0 vi /etc/yum/pluginconf.d/subscription-manager.conf 箭头所指的改成0 cd /etc/yum.repos.d/ wget https://mirro…

docker之安装mongo创建运行环境

目录 一、docker pull 最新资源 二、启动mongo镜像 启动命令查看日志拉取低版本镜像成功启动 三、进入mongo容器 进入容器进入mongo环境查询当前所在库切换库至admin随意切换库 并 创建用户登录用户新增文档数据等 五、总结 版本兼容可备份操作 一、docker pull 最新资源…

CentOS已安装宝塔的情况下手动安装phpMyAdmin

CentOS 7.9.2009&#xff0c;宝塔7.9.4。 服务器中已有MySQL&#xff0c;可能不是通过宝塔安装的&#xff0c;而是手动安装的。用命令行可以正常进入MySQL查看和管理数据&#xff0c;说明已有的MySQL是正常的。在宝塔中点击数据库提示“未安装本地数据库&#xff0c;已隐藏无法…

基于函数计算AIGC生成图应用

目录 基于函数计算部署AIGC应用的主要步骤 创建Stable Diffusion模型的应用 访问应用实现文字生图 函数的查看与管理 基于函数计算部署AIGC应用的主要步骤 用函数计算实现AIGC只要简单的三步&#xff0c;分别是创建应用、运行应用及查看管理。 创建Stable Diffusion模型的应…

【大厂AI课学习笔记】【2.2机器学习开发任务实例】(7)特征构造

特征分析之后&#xff0c;就是特征构造。 特征构造第一步 特征构造往往要进行数据的归一化。 在本案例中&#xff0c;我们将所有的数据&#xff0c;将所有特征区间调整为0~1之间。 如上图。 那么&#xff0c;为什么要进行归一化&#xff0c;又如何将数据&#xff0c;调整为…

《隐私计算简易速速上手小册》第2章:关键技术介绍(2024 最新版)

文章目录 2.1 同态加密2.1.1 基础知识2.1.2 主要案例:云计算数据分析2.1.3 拓展案例 1:医疗数据分析2.1.4 拓展案例 2:金融风险评估2.2 安全多方计算(SMC)2.2.1 基础知识2.2.2 主要案例:跨机构金融数据共享2.2.3 拓展案例 1:医疗研究合作2.2.4 拓展案例 2:跨国界数据交…

拼夕夕 拼多多关键词恢复供应,欢迎骚扰

API接口&#xff08;Application Programming Interface&#xff09;是一种定义了软件组件之间交互的规范。它允许不同的软件系统之间进行通信和数据交换&#xff0c;使得开发者可以利用已有的功能和服务来构建自己的应用程序。 API接口可以分为不同的类型&#xff0c;包括Web…

加固手持平板电脑在铁轨维修上的应用|亿道三防onerugged

随着铁路交通的发展&#xff0c;铁轨维修成为保障行车安全和运营效率的重要环节。在这个关键的维修过程中&#xff0c;专业的设备和工具至关重要。亿道三防onerugged系列产品中的加固手持平板电脑是一款在铁轨维修上应用的理想选择。 首先&#xff0c;这款加固手持平板电脑满足…

全面总结!加速大模型推理的超全指南来了!

2023 年&#xff0c;大型语言模型&#xff08;LLM&#xff09;以其强大的生成、理解、推理等能力而持续受到高度关注。然而&#xff0c;训练和部署 LLM 非常昂贵&#xff0c;需要大量的计算资源和内存&#xff0c;因此研究人员开发了许多用于加速 LLM 预训练、微调和推理的方法…

汽车控制器软件正向开发

需求常见问题: 1.系统需求没有分层,没有结构化,依赖关系不明确 2.需求中没有验证准则 3.对客户需求的追溯缺失,不完整,颗粒度不够 4.系统需求没有相应的系统架构,需求没有分解到硬件和软件 5.需求变更管控不严格,变更频繁,变更纪录描述不准确,有遗漏,客户需求多…

使用 DevComponents DotNetBar DateTimeInput 控件实现高级日期时间选择功能

使用 DevComponents DotNetBar DateTimeInput 控件实现高级日期时间选择功能 在.NET WinForms 应用程序开发中&#xff0c;提供直观、易用的日期时间选择功能对于创建用户友好的界面至关重要。DevComponents DotNetBar 提供了一个功能丰富的 DateTimeInput 控件&#xff0c;它不…

MySQL篇之主从同步原理

一、原理 MySQL主从复制的核心就是二进制日志。 二进制日志&#xff08;BINLOG&#xff09;记录了所有的 DDL&#xff08;数据定义语言&#xff09;语句和 DML&#xff08;数据操纵语言&#xff09;语句&#xff0c;但不包括数据查询&#xff08;SELECT、SHOW&#xff09;语句。…