【Android】开局一行码,数据自己填(浅析安卓数据存储)

【Android】开局一行码,数据自己填(浅析安卓数据存储)

写在最前面

"当世界再次在黎明的曙光中苏醒,我却发现自己躺在了十年前的床上。阳光透过窗帘的缝隙,洒在我脸上,温暖而真实。我揉了揉眼睛,心中涌起一股难以置信的情绪。这不是梦,我真的回到了过去,那个我曾经无数次在梦中渴望回到的时刻。我记得这一天,记得接下来会发生什么——那是我人生的转折点,是我所有悲剧的开始。

上一世,我忘却了Android中数据本地持久化的方式,遭人嘲笑,背叛,最后郁郁而终,而这一世,我带着上一世的代码重新归来。

我有了重新开始的机会,有了改变命运的力量。这一次,我不会再让任何人伤害他,不会再让任何遗憾留在心中。我要让那些曾经嘲笑我、背叛我的人,都见识到我的崛起。重生,是我的新起点,也是我复仇的开始。"

对于Android开发者而言,掌握数据存储技术是构建强大应用的必备技能。Android提供了多种数据存储方案来满足不同的需求。本篇博客,我们将探讨Android中两种核心的数据存储技术:SharedPreferences和SQLite数据库。

SharedPreferences

在Android开发中,对于轻量级数据的存储,SharedPreferences无疑是一个简单而强大的工具。它允许开发者以键值对的形式存储和检索数据,非常适合用于存储应用配置、用户偏好设置等。本文将深入探讨SharedPreferences的工作原理、使用方法以及一些实际应用示例。

SharedPreferences简介

SharedPreferences是Android提供的一个轻量级存储解决方案,用于存储少量的键值对数据。它基于XML文件存储数据,通常位于应用的私有目录下。SharedPreferences的数据存储是跨进程的,但要注意读写操作不是原子性的。

SharedPreferences的创建与使用

创建SharedPreferences实例

创建当前应用的SharedPreferences实例: 使用ContextgetSharedPreferences(String name, int mode)方法。其中name是SharedPreferences文件的名称,mode是访问模式。

SharedPreferences sharedPreferences = getSharedPreferences("my_prefs", MODE_PRIVATE);

编辑SharedPreferences数据

编辑SharedPreferences数据需要通过SharedPreferences.Editor对象:

获取Editor对象

SharedPreferences.Editor editor = sharedPreferences.edit();

使用Editor进行数据操作

  • putString, putInt, putBoolean等方法用于添加或更新数据。
  • remove方法用于删除指定的键。
  • clear方法用于清空所有数据。
editor.putString("username", "CrazyMO");
editor.putInt("score", 100);

提交更改

  • commit方法会同步提交更改,返回一个布尔值表示操作是否成功。
  • apply方法会异步提交更改,不返回操作结果。
boolean isCommitted = editor.commit();
editor.apply();

SharedPreferences.Editor putBoolean(String key, boolean value)

SharedPreferences.Editor putFloat(String key, float value)

SharedPreferences.Editor putInt(String key, int value)

SharedPreferences.Editor putLong(String key, long value)

SharedPreferences.Editor putString(String key, String value)

SharedPreferences.Editor putStringSet(String key, Set values)

SharedPreferences.Editor remove(String key)

SharedPreferences.Editor clear()

SharedPreferences的监听机制

SharedPreferences提供了监听机制,允许开发者在数据发生变化时得到通知:

SharedPreferences.OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        // 处理数据变化
    }
};

sharedPreferences.registerOnSharedPreferenceChangeListener(listener);

// 取消监听
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);

SharedPreferences的访问

Map<String, ?> getAll()

boolean getBoolean(String key, boolean defValue)

float getFloat(String key, float defValue)

int getInt(String key, int defValue)

long getLong(String key, long defValue)

String getString(String key, String defValue)

Set getStringSet(String key, Set defValues)

SharedPreferences的实际应用

下面是一个简单的SharedPreferences使用示例,展示如何在Activity中读写SharedPreferences。

public class SettingsActivity extends AppCompatActivity {
    private SharedPreferences settings;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);
        settings = getSharedPreferences("settings_prefs", MODE_PRIVATE);
    }

    public void saveSettings(View view) {
        SharedPreferences.Editor editor = settings.edit();
        editor.putString("theme", "dark");
        editor.putBoolean("notifications", true);
        editor.apply();
    }

    public void loadSettings(View view) {
        String theme = settings.getString("theme", "light");
        boolean notifications = settings.getBoolean("notifications", false);
        Toast.makeText(this, "Theme: " + theme + ", Notifications: " + notifications, Toast.LENGTH_SHORT).show();
    }
}

SQLite

SQLite 是一种轻量级的关系型数据库管理系统,广泛用于 Android 开发中,用于管理应用程序的数据。以下是对 SQLite 数据库在 Android 中应用的详细介绍。

SQLite 简介

SQLite 是一个自给自足的、无服务器的数据库,它将数据以文件形式存储,不需要复杂的配置过程,即插即用。它的设计非常注重简便性和高效性,特别适合资源有限的环境,如移动设备。

它的结构类似于一个表:

image-20240725145442527

SQLite 的特点

  1. 轻量级:SQLite 的库体积小,完全配置时小于400KiB。
  2. 自给自足:不需要任何外部依赖,可以独立运行。
  3. 无服务器:与其他数据库系统不同,SQLite 不需要部署服务支持,可以简化应用的架构和部署流程。
  4. 零配置:无需安装或管理,降低了开发和维护的复杂度。
  5. 事务性:支持事务操作,完全兼容ACID(原子性、一致性、隔离性、持久性)的特性,确保数据的完整性和可靠性。
  6. 跨平台:SQLite 可以在 Windows、Linux、Mac OS X、Android 等多个平台上运行,保证了应用的跨平台兼容性。
  7. 高性能:相对于其他嵌入式数据库,SQLite 具有更快的查询速度和更小的内存占用,使得应用运行更加流畅。
  8. 安全性:支持数据加密和访问控制,可以保障数据安全,减少数据泄露的风险。

SQLite 在 Android 中的应用

水词儿说完来放点干货吧,作为Android数据存储的重点部分,笔者将带着大家制作一个Student信息增删改查的库,用APP来实现~,希望大家喜欢。

本部分内容参考自:

27.1-Android中数据库SQLite的使用(上)_哔哩哔哩_bilibili

27.1-Android中数据库SQLite的使用(上)_哔哩哔哩_bilibili

对up主的内容做了部分删改。

前期准备

先来看看基本的项目结构吧

image-20240725113028877

主界面是一个有四个按钮的Activity,分别转向数据库的增删改查四个功能,此外还有一个Helper类留到后面再讲。

能学到这里,想必已经对安卓有一定了解,笔者这里就不放上控件的具体写法和布局思路啦,把代码直接贴上!

MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
    }

    public void insertData(View view) {
        Intent intent = new Intent(this, InsertActivity.class);
        startActivity(intent);
    }

    public void deleteData(View view) {
        Intent intent = new Intent(this, DeleteActivity.class);
        startActivity(intent);
    }

    public void updateData(View view) {
        Intent intent = new Intent(this, UpdateActivity.class);
        startActivity(intent);
    }

    public void searchData(View view) {
        Intent intent = new Intent(this, QueryActivity.class);
        startActivity(intent);
    }
}

activity_main.xml:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <Button
        style="@style/MyBtnStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="insertData"
        android:text=""/>

    <Button
        style="@style/MyBtnStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="deleteData"
        android:text=""/>

    <Button
        style="@style/MyBtnStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="updateData"
        android:text=""/>

    <Button
        style="@style/MyBtnStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="searchData"
        android:text=""/>

</LinearLayout>

一个简单的线性布局+四个按钮。

防止读者嫌麻烦,把按钮的样式代码也贴上:

<style name="MyBtnStyle">
        <item name="android:textColor">@color/white</item>
        <item name="android:textSize">20sp</item>
        <item name="android:layout_marginTop">20dp</item>
        <item name="android:layout_marginRight">20dp</item>
        <item name="android:layout_marginLeft">20dp</item>
</style>

呈现出来的效果类似于这样~

image-20240725113427104

为了方便,建议大家把增删改查四个Activity提前创建出来,写好intent跳转的语句~

如果不想做这些事,想直接学习数据库相关内容,也可以直接跳转到下面内容。

数据库的建立

俗话说得好,要致富,先建数据库,要建数据库,先创Helper类

我们创建一个MySQLiteHelper类,继承自SQLiteOpenHelper。

image-20240725114901468

SQLiteOpenHelper 类是 Android SDK 提供的一个抽象类,用于帮助开发者管理数据库的创建和版本管理。当你需要在应用程序中使用 SQLite 数据库时,你需要继承这个类,并实现它的几个关键方法来创建和操作数据库。

如图所示,我们需要实现一个构造方法以及重写两个抽象方法。

我们来看看这三个函数是干啥用的,首先来看两个重写的方法。

  1. onCreate(SQLiteDatabase db):
    • 当数据库第一次被创建时调用。在这个回调方法中,你可以执行 CREATE TABLE 语句来初始化数据库结构。
  2. onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion):
    • 当数据库版本发生变化时调用。在这个回调方法中,你可以处理数据库的升级逻辑,比如添加新表、修改现有表结构等。

简单来说,我们会在onCreate(SQLiteDatabase db)这个方法中初始化数据库,然后在onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)这个方法中升级数据库,那什么叫数据库版本发生变化呢?

我们来看看构造函数的组成。

构造函数 (SQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version)):

  • context: 上下文对象,用于获取应用程序的数据库路径。
  • name: 数据库名称。如果为空,则使用默认数据库。
  • factory: 用于创建 Cursor 对象的工厂。通常设置为 null,使用默认工厂。
  • version: 数据库版本号。用于管理数据库的版本和升级。

由于笔者学艺不精,学到这里还不太清楚上下文对象的含义,故在此处贴上上下文对象相关知识:

在 Android 开发中,“上下文对象”(Context)是一个核心概念,它提供了一系列关于应用程序环境的信息。上下文对象是 Android 四大组件(Activity、Service、BroadcastReceiver、ContentProvider)之一,并且是这些组件的基类。

上下文对象的含义:

  1. 全局信息的载体:上下文对象包含了应用程序的全局信息,比如应用程序的包名、当前活动的列表、系统服务等。
  2. 系统服务的访问:通过上下文对象,应用程序可以访问系统服务,如通知管理器(NotificationManager)、窗口管理器(WindowManager)等。
  3. 资源管理:上下文对象允许应用程序访问其资源,包括字符串、颜色、尺寸、布局等。
  4. 启动组件:使用上下文对象,可以启动其他组件,如启动一个新的 Activity 或者发送一个 Intent 来启动 Service 或 BroadcastReceiver。
  5. 权限检查:上下文对象用于检查运行时权限,确保应用程序的组件在执行特定操作前具有相应的权限。

一个构造函数要传四个参数,未免有些过于繁琐了,而且其中三个参数都与外部环境无关(即数据库名称、版本号、和工厂对象)那我们不妨在Helper类里面封装好构造函数,如图所示:

image-20240725144257677

把底部复杂的传参操作放上去,仅从外部传入一个context上下文对象。

注意:为了使得修改方便,我们将数据库的名字设置为一个常量,这里就叫:

private static final String DB_NAME = "mySQLite.db";

至此,我们的构造函数就写完啦!

接下来我们来看看怎么填充onCreate函数,也就是怎么创建数据库。

image-20240725145244808

其实也很简单,我们只需要设置一个创建数据库的SQL语句,再用execSQL将其运行即可。

图中的SQL语句意为:

  • "create table ": 这是 SQL 语句的一部分,用于指定接下来的操作是创建一个新表。

  • TABLE_NAME_STUDENT: 这是一个在代码其他地方定义的常量,代表表的名称。

  • " (id integer primary key autoincrement, " + ...
    

    : 这部分定义了表的列和它们的属性:

    • id: 列名,用于存储唯一标识符。
    • integer: 数据类型,指定 id 列应该存储整数值。
    • primary key: 指定 id 列是表的主键,意味着它的值必须是唯一的,并且可以被用来唯一确定表中的每条记录。
    • autoincrement: 指定 id 列的值会自动递增。每当插入新记录时,如果未指定 id 的值,它会自动分配下一个整数。
    • name, number, gender, score: 这些都是表的列名,后面跟着它们的数据类型 text,表示这些列将存储文本数据。

除了Integer,text,数据库还有很多种数据类型,例如:

整数类型

  • TINYINT: 非常小的整数
  • SMALLINT: 小的整数
  • MEDIUMINT: 中等大小的整数
  • INTINTEGER: 标准整数
  • BIGINT: 大整数

定点数和浮点数类型

  • DECIMALNUMERIC: 定点数,可指定小数位数。
  • FLOAT: 单精度浮点数。
  • DOUBLE: 双精度浮点数。

字符串类型

  • CHAR: 固定长度的字符串,长度可在 0 到 255 之间。
  • VARCHAR: 可变长度的字符串,长度可在 0 到 65535 字符之间。
  • TINYTEXT: 很小的文本数据,最大长度 255 字节。
  • TEXT: 较长的文本数据。
  • MEDIUMTEXT: 中等长度的文本数据,最大长度 16MB。
  • LONGTEXT: 很长的文本数据,最大长度 4GB。

这里仅放一部分,其他的数据类型还请读者自行去搜寻资料啦。

至此,我们的MySQLiteHelper类算是建立完成,onUpgrade方法用于更新数据库,我们暂且不管。

数据的增加

接下来就是激动人心的增删改查环节,在接下来四个模块中,我会先放上对应的xml文件代码,而后再放上主要程序思路的解析。

首先是InsertActivity:

image-20240725150150365

我们的增加数据如上,一共有四个数据:姓名学号性别与分数。以下是xml代码~

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".InsertActivity"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="20dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="姓名"
            android:textSize="30sp"/>
        <EditText
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="20dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="学号"
            android:textSize="30sp"/>
        <EditText
            android:id="@+id/et_number"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="20dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="性别"
            android:textSize="30sp"/>
        <RadioGroup
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:gravity="center">
            <RadioButton
                android:id="@+id/rb_man"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text=""/>
            <RadioButton
                android:id="@+id/rb_woman"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text=""
                android:layout_marginLeft="20dp"/>

        </RadioGroup>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="20dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="分数"
            android:textSize="30sp"/>
        <EditText
            android:id="@+id/et_score"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </LinearLayout>

    <Button
        style="@style/MyBtnStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存"
        android:onClick="insert"/>

</LinearLayout>

在Activity文件中,我们需要做两件事:

首先,在InsertActivity中new一个MySQLiteOpenHelper类,用于数据库的操作。

而后写一个insert方法,作为按钮的点击事件。

	public void insert(View view) {
        String name = binding.etName.getText().toString().trim();
        String number = binding.etNumber.getText().toString().trim();
        String score = binding.etScore.getText().toString().trim();
        String gender = "";

        if (binding.rbMan.isChecked()) {
            gender = "男";
        }

        if (binding.rbWoman.isChecked()) {
            gender = "女";
        }

        StudentBean studentBean = new StudentBean();
        studentBean.setName(name);
        studentBean.setNumber(number);
        studentBean.setScore(score);
        studentBean.setGender(gender);

        //插入数据库中
        long row = mySQLiteOpenHelper.insertData(studentBean);
        if(row != -1){
            Toast.makeText(this, "添加成功", Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(this, "添加失败", Toast.LENGTH_SHORT).show();
        }
    }

写到这里突然想起来没放StudentBean的代码,其实也不难写,但是为了方便读者还是贴上去吧。(特别声明:与水字数无关)

一个简单的javabean:

public class StudentBean {
    private String name;
    private String number;
    private String score;
    private String gender;

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    public String getScore() {
        return score;
    }

    public void setScore(String score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "StudentBean{" +
                "name='" + name + '\'' +
                ", number='" + number + '\'' +
                ", score='" + score + '\'' +
                ", gender='" + gender + '\'' +
                '}';
    }
}

我们继续看上面的代码

String name = binding.etName.getText().toString().trim();
  • 从界面上的 EditText 控件(假设其 ID 是 etName)获取文本内容,调用 getText() 获取 Editable 对象,然后调用 toString() 将其转换为字符串,并使用 trim() 去除字符串两端的空白字符。
    long row = mySQLiteOpenHelper.insertDate(studentBean);
  • 调用 mySQLiteOpenHelper 对象的 insertData 方法,并将 studentBean 对象传递给它。这个方法负责将学生数据插入到数据库中。返回值 row 是新插入行的 ID,如果是 -1 表示插入失败。
    if(row != -1){
        Toast.makeText(this, "添加成功", Toast.LENGTH_SHORT).show();
    }else{
        Toast.makeText(this, "添加失败", Toast.LENGTH_SHORT).show();
    }
  • 检查插入操作是否成功。如果 row 不等于 -1,则显示一个 Toast 消息提示用户添加成功;否则,显示添加失败的消息。

我们回到Help类中编写insert相关的逻辑:

新建一个函数:

	public long insertData(StudentBean studentBean) {
        SQLiteDatabase db = getWritableDatabase();
        ContentValues values = new ContentValues();

        values.put("name",studentBean.getName());
        values.put("number",studentBean.getNumber());
        values.put("gender",studentBean.getGender());
        values.put("score",studentBean.getScore());

        return db.insert(TABLE_NAME_STUDENT,null,values);
    }
SQLiteDatabase db = getWritableDatabase();
  • 调用 SQLiteOpenHelper 类的 getWritableDatabase() 方法获取一个可写数据库实例。如果数据库文件不存在,此方法会创建它。返回的 SQLiteDatabase 对象用于执行数据库操作。
	ContentValues values = new ContentValues();
  • 创建一个 ContentValues 对象。ContentValues 类用于存储键值对,这些键值对将被用作 SQL INSERT 语句的值。
		values.put("name",studentBean.getName());
        values.put("number",studentBean.getNumber());
        values.put("gender",studentBean.getGender());
        values.put("score",studentBean.getScore());
  • 使用 ContentValues 对象的 put() 方法添加数据。这里将 StudentBean 对象的 name 属性值存储到 ContentValues 对象中,键名为 "name"

  • StudentBean 对象的 number 属性值存储到 ContentValues 对象中,键名为 "number"

  • StudentBean 对象的 gender 属性值存储到 ContentValues 对象中,键名为 "gender"

  • StudentBean 对象的 score 属性值存储到 ContentValues 对象中,键名为 "score"

  • 使用 SQLiteDatabase对象的 insert()方法将数据插入到数据库中。参数如下:

    • TABLE_NAME_STUDENT: 表名,这是一个常量,代表数据库中存储学生信息的表。
    • null: 列名数组,传入 null 表示插入 ContentValues 中的所有键值对。如果不传 null,则需要指定一个列名数组,仅插入数组中列出的列
    • values: ContentValues 对象,包含要插入的键值对数据。
  • insert() 方法返回一个长整型值,表示新插入行的行 ID。如果插入失败,返回 -1

数据添加的逻辑我们就算写完了,接下来我们编写删除的逻辑

数据的删除

惯例先放上界面的代码:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DeleteActivity"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="20dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="姓名"
            android:textSize="30sp"/>
        <EditText
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </LinearLayout>


    <Button
        style="@style/MyBtnStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="删除"
        android:onClick="delete"/>

</LinearLayout>

image-20240725152010250

这次我们需要编写一个以姓名作为基准的删除逻辑。

在活动中的代码逻辑不怎么变化,依然是先创建一个数据库帮助类,再调用其中的函数。

public class DeleteActivity extends AppCompatActivity {

    ActivityDeleteBinding binding;
    private MySQLiteOpenHelper mySQLiteOpenHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        binding = ActivityDeleteBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        
        mySQLiteOpenHelper = new MySQLiteOpenHelper(this);
    }


    public void delete(View view) {
        String name = binding.etName.getText().toString().trim();
        
        int row = mySQLiteOpenHelper.deleteByName(name);
        if(row > 0){
            Toast.makeText(this, "删除成功,已删除关于" + name + "的" + row + "条所有数据", Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(this, "删除失败,没有找到符合条件的数据", Toast.LENGTH_SHORT).show();
        }
    }
}

放上完整代码,我们马不停蹄,直接前往MySQLiteOpenHelper类!

在帮助类中的代码仍然不复杂:

public int deleteByName(String name){
        SQLiteDatabase db = getWritableDatabase();
        return db.delete(TABLE_NAME_STUDENT,"name like ?",new String[] {name});
    }

火速打开可写数据库,而后执行delete方法,来看看delete方法的参数:

  • SQLiteDatabase db = getWritableDatabase();: 这行代码获取一个可写的数据库实例,用于执行删除操作。
  • return db.delete(TABLE_NAME_STUDENT, "name like ?", new String[] {name});: 这行代码执行实际的删除操作。
    • 第一个参数 TABLE_NAME_STUDENT 是要操作的数据库表的名称。
    • 第二个参数 "name like ?" 是一个 SQL 删除条件,使用 LIKE 操作符来进行模糊匹配。? 是一个参数占位符,用于在 SQL 语句中防止 SQL 注入攻击。
    • 第三个参数 new String[] {name} 是一个字符串数组,包含了用于替换 SQL 语句中占位符的实际参数值。在这个例子中,name 变量的值将被用于匹配数据库中 name 列的值。

db.delete() 方法会返回一个整数,表示被删除的行数。如果删除操作没有影响到任何行(即没有找到匹配的记录),则返回 0

数据的更新与修改

update部分,我们复用此前插入的布局,修改按钮的文字即可,仍然是放上布局文件代码:

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".UpdateActivity"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="20dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="姓名"
            android:textSize="30sp"/>
        <EditText
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="20dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="学号"
            android:textSize="30sp"/>
        <EditText
            android:id="@+id/et_number"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="20dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="性别"
            android:textSize="30sp"/>
        <RadioGroup
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            android:gravity="center">
            <RadioButton
                android:id="@+id/rb_man"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text=""/>
            <RadioButton
                android:id="@+id/rb_woman"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text=""
                android:layout_marginLeft="20dp"/>

        </RadioGroup>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="20dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="分数"
            android:textSize="30sp"/>
        <EditText
            android:id="@+id/et_score"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </LinearLayout>

    <Button
        style="@style/MyBtnStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="更新"
        android:onClick="update"/>

</LinearLayout>

image-20240725152532587

经过前两次尝试,想必已经对流程不陌生了,我们直接进入update方法的编写:

在Activtiy中:

    public void update(View view) {
        String name = binding.etName.getText().toString().trim();
        String number = binding.etNumber.getText().toString().trim();
        String score = binding.etScore.getText().toString().trim();
        String gender = "";

        if (binding.rbMan.isChecked()) {
            gender = "男";
        }

        if (binding.rbWoman.isChecked()) {
            gender = "女";
        }

        StudentBean studentBean = new StudentBean();
        studentBean.setName(name);
        studentBean.setNumber(number);
        studentBean.setScore(score);
        studentBean.setGender(gender);

        //插入数据库中
        long row = mySQLiteOpenHelper.updateDate(studentBean);
        if(row > 0){
            Toast.makeText(this, "更新成功", Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(this, "更新失败!", Toast.LENGTH_SHORT).show();
        }
    }

获取到一个完整的StudentBean之后,我们将其传入数据库帮助类中进行更新。

    public long updateDate(StudentBean studentBean) {
        SQLiteDatabase db = getWritableDatabase();
        ContentValues values = new ContentValues();

        values.put("name",studentBean.getName());
        values.put("number",studentBean.getNumber());
        values.put("gender",studentBean.getGender());
        values.put("score",studentBean.getScore());

        return db.update(TABLE_NAME_STUDENT,values,"name like ?",new String[] {studentBean.getName()});
    }

既然是更新,总有一个不变的值吧,我们如何在数据更新时确定那个不变的值,然后对一整行进行更新呢?我们来关注一下update的参数~:

  • return db.update(TABLE_NAME_STUDENT, values, "name like ?", new String[] {studentBean.getName()});:调用 SQLiteDatabase 对象的 update 方法执行更新。
  • 参数解释:
    • TABLE_NAME_STUDENT:指定要更新的表名。
    • values:包含要更新数据的 ContentValues 对象。
    • "name like ?":SQL 更新条件,使用 LIKE 操作符进行模糊匹配,? 是一个参数占位符。
    • new String[] {studentBean.getName()}:字符串数组,包含用于替换 SQL 语句中占位符的实际参数值,这里使用的是 StudentBean 对象中的 name 属性值。

我们依旧通过name like ? 这一句来寻找SQL的更新条件,如果想要依据别的数据进行更新,修改参数即可。

数据的查询

最后,数据的查询,仍然是先放上xml文件,这次依然以姓名作为基准,但略有所不同,我们在按钮底下加了一个空的textview,用来显示查询到的数据。

<?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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".QueryActivity"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:layout_marginTop="20dp">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="姓名"
            android:textSize="30sp"/>
        <EditText
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </LinearLayout>


    <Button
        style="@style/MyBtnStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="删除"
        android:onClick="query"/>

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="left"
        android:textSize="30dp"/>

</LinearLayout>

来写一下Activity中的query方法:

public void query(View view) {
        String name = binding.etName.getText().toString().trim();

        List<StudentBean> list = mySQLiteOpenHelper.queryByName(name);

        if(list != null){
            String res = "";
            for(StudentBean s : list){
                res += "姓名:" + s.getName() + " 学号:" + s.getNumber()
                        + " 性别:" + s.getGender() + " 分数" + s.getScore() + "\n";
            }
            binding.tvResult.setText(res);
        }
    }

首先在数据库Helper类中搜索到所有与姓名有关的,而后添加到list中。

再将其格式化,放入准备好的textview里面。

进入Helper类中看看:

    public List<StudentBean> queryByName(String name){
        SQLiteDatabase db = getWritableDatabase();
        List<StudentBean> list = new ArrayList<>();
        Cursor query = db.query(TABLE_NAME_STUDENT, null, "name like ?", new String[]{name}, null, null, null);
        if(query != null){
            while (query.moveToNext()) {
                String name1 = query.getString(query.getColumnIndex("name"));
                String number = query.getString(query.getColumnIndex("number"));
                String gender = query.getString(query.getColumnIndex("gender"));
                String score = query.getString(query.getColumnIndex("score"));

                StudentBean studentBean = new StudentBean();
                studentBean.setName(name1);
                studentBean.setNumber(number);
                studentBean.setGender(gender);
                studentBean.setScore(score);

                list.add(studentBean);
            }
            query.close();
        }

        return list;
    }

哦唷,怎么这个搜索的方法有这么多参数,还用上while了,赶紧让我来瞧上一瞧。

获取数据库实例

SQLiteDatabase db = getWritableDatabase();:获取一个可写的数据库实例。尽管这里我们只需要查询操作,但 getWritableDatabase() 也可以用于查询,因为它确保了数据库文件存在。

初始化结果列表

List<StudentBean> list = new ArrayList<>();:创建一个 ArrayList 来存储查询结果,每个结果都是一个 StudentBean 对象。

执行查询操作

Cursor query = db.query(TABLE_NAME_STUDENT, null, "name like ?", new String[]{name}, null, null, null);

使用 SQLiteDatabase对象的 query方法执行查询:

  • db: 这是一个 SQLiteDatabase 对象的实例,代表与数据库的连接。通常通过 SQLiteOpenHelper 类的 getReadableDatabase()getWritableDatabase() 方法获得。
  • query() 方法: SQLiteDatabase 类的 query() 方法用于执行 SQL 查询并返回一个 Cursor 对象。这个方法接受多个参数来定义查询的行为。
  • TABLE_NAME_STUDENT: 这是一个字符串常量,表示要查询的数据库表的名称。在这个例子中,假设它被定义为 "students"
  • null: 这是第一个参数,表示查询返回的列名。传入 null 意味着查询将返回表中的所有列。
  • “name like ?”: 这是 SQL 查询条件。使用 LIKE 操作符进行模糊匹配,? 是一个参数占位符,稍后会被实际的参数值替换。
  • new String[]{name}: 这是一个字符串数组,包含用于替换 SQL 语句中占位符的实际参数值。在这个例子中,它包含一个元素,即变量 name 的值。这个值将被用于匹配表中 name 列的值。
  • null, null, null: 这些是 query() 方法的最后三个参数,分别用于指定分组、排序和限制查询结果的行数:
    • 第一个 null: 分组语句(例如 GROUP BY),这里未使用分组,所以传入 null
    • 第二个 null: 排序语句(例如 ORDER BY),这里未使用排序,所以传入 null
    • 第三个 null: 限制语句(例如 LIMIT),这里未使用限制,所以传入 null

处理查询结果

  • if(query != null){ ... }:检查 Cursor 对象是否非空。
  • while (query.moveToNext()) { ... }:使用 Cursor 对象的 moveToNext() 方法遍历查询结果的每一行。

从 Cursor 中获取数据

  • query.getString(query.getColumnIndex("列名")):使用 Cursor 对象的 getString 方法和列名对应的索引来获取列的值。

关闭 Cursor

  • query.close();:查询完毕后关闭 Cursor 对象,释放资源。

这里怎么有点眼熟?Cursor是什么?

Cursor 是 Android SDK 中的一个类,它提供了对 SQLite 数据库查询结果的访问。以下是 Cursor 的主要用途和如何使用它来实现搜索:

Cursor 的用途:

  1. 数据检索Cursor 用于检索 SQLite 数据库的查询结果。
  2. 逐行访问:它允许开发者逐行遍历查询结果,一次处理一行数据。
  3. 列值获取:通过 Cursor 的方法,可以访问当前行的每一列的值。
  4. 资源管理Cursor 封装了对数据库结果集的管理,确保在数据使用完毕后正确关闭并释放系统资源。

它有点类似于C语言中的指针,可以一行一行搜索我们需要的数值。

结语

本篇博客参考

Android基础-SharedPreferences详解_android sharedpreferences-CSDN博客

https://liaoxuefeng.com/books/sql/query/

介绍了安卓中数据本地存储的两种解决方案,其中SharedPreferences偏向于处理轻量化的数据,而SQLite偏向于处理复杂一些的数据,两种方法各有优势劣势。希望可以对读者有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值