文章目录

  • 数据持久化
  • 文件存储
    • 将数据存储进文件
      • 实例
    • 从文件中读取数据
      • 实例
  • SharedPreferences存储
    • 将数据存储进文件
      • 实例
    • 从文件中读取数据
      • 实例
    • 实现记住密码的功能
  • SQLite数据库存储
    • 创建自己的帮助类
    • 调用自己的帮助类
    • 补全 onUpgrade() 方法
    • 增删查改
      • 增:SQLiteDatabase.insert()
      • 改:SQLiteDatabase.update()
      • 删:SQLiteDatabase.delete()
      • 查:SQLiteDatabase.query()
    • 通过 SQL语句 实现增删查改

数据持久化

保存在内存中的数据是属于瞬时状态的,而保存在存储设备中的数据上处于持久状态的,持久化技术提供了一种可以让数据在瞬时状态和持久状态之间转换的机制。

Android系统中主要提供了3种常用方式用于简单地实现数据持久化功能,即文件存储SharedPreference存储以及数据库存储


文件存储

将数据存储进文件

Context类 中提供了一个 openFileOutput 方法,用于将数据存储到指定的文件中。这个方法接收两个参数:

  • 第一个参数是文件名:在文件创建的时候使用的就是这个名称,文件名不可以包含路径,因为所有的文件都是默认存储到 /data/data/<packagename>/files/ 目录下的。
  • 第二个参数是文件的操作模式:主要有两种模式可以选,MODE_PRIVATE 默认的操作模式,写入的内容会覆盖原文件的内容; MODE_APPEND 则表示如果该文件已经存在,就往文件里面追加内容,不存在创建新文件

该方法返回一个 FileOutputStream 对象,得到了这个对象之后就可以使用 Java流 的方式将数据写入到文件中了。

实例

在布局文件中添加输入框 EditText 控件:

在活动文件中,定义不同生命周期的不同行为:

  • onCreate 方法:获取 EditText 实例;
  • onDestroy 方法:获取 EditText 中的内容,并通过自定义方法 save 保存到名为 data 的文件中。
public class SecondActivity extends AppCompatActivity {private EditText editText;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.second_layout);editText = findViewById(R.id.edit);}@Overrideprotected void onDestroy() {super.onDestroy();String inputText = editText.getText().toString();save(inputText);}public void save(String inputText){FileOutputStream out = null; // 文件字节输出流,继承OutputStream类BufferedWriter writer = null; // 将文本写入字符输出流try {// 获得一个 字节输出流对象,规定数据存储到名为data的文件中,文件的操作模式为MODE_PRIVATEout = openFileOutput("data", Context.MODE_PRIVATE);// 借助out构建OutputStreamWriter临时对象,作为从字符流到字节流的桥接// 再通过临时对象构建 字符输出流对象 以便将文本内容写入到字节流中writer = new BufferedWriter(new OutputStreamWriter(out));// 将文本内容写入到字符流中writer.write(inputText);} catch (IOException e){e.printStackTrace();} finally {try {if(writer != null){writer.close();}} catch (IOException e){e.printStackTrace();}}}
}

PS:对于上述将文本存入文件的流程,一开始我理解错了,顺着代码顺寻看以为是字节流转成字符流再写入文件,把 inputText 当保存文本的文件了。。。

其实正确逻辑是:

运行结果:

在文本框内输入内容:

退出程序后,在AS中通过如下操作打开文件页面:

在下图路径中找到 data 文件,查看其内容:


从文件中读取数据

Context 类中还提供了一个 openFileInput 方法,用于从文件中读取数据。这个方法只接受一个参数:

  • 要读取的文件名:然后系统会自动到 /data/data/<packagename>/files 目录下去加载这个文件。

该方法返回一个 FileInputStream 对象,得到了这个对象之后再通过 Java流 的方式就可以将数据读取出来了。

实例

EditText 为空则将文件中的文本读入到 EditText 中:

public class SecondActivity extends AppCompatActivity {private EditText editText;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.second_layout);editText = findViewById(R.id.edit);String inputText = load();if(!TextUtils.isEmpty(inputText)){editText.setText(inputText);editText.setSelection(inputText.length());Toast.makeText(this, "loading successed", Toast.LENGTH_LONG).show();}}public String load(){FileInputStream in = null;BufferedReader reader = null;StringBuilder content = new StringBuilder();try {in = openFileInput("data"); // 文本字节输入流// InputStreamReader作为字符流到字节流的桥接reader = new BufferedReader(new InputStreamReader(in));String line = "";// 从字符流中读取数据,每次读取文件的一行while((line = reader.readLine()) != null){content.append(line);}} catch (IOException e){e.printStackTrace();} finally {if(reader != null){try {// 处理完文本后关闭流reader.close();} catch (IOException e){e.printStackTrace();}}}return content.toString();}
}

PS:在判空时使用了 TextUtils.isEmpty() 而非 String.isEmpty(),这是因为:

  • String 类下的 isEmpty() 返回的只是 字符串的长度是否为0,如果 字符串为null 就会直接报 空指针。源码如下:
public boolean isEmpty() { return count == 0; }
  • TextUtils.isEmpty() 会对 null长度 进行判断,所以 不会报空指针。源码如下:
public static boolean isEmpty(CharSequence str) { if (str == null || str.length() == 0) return true; else return false;
}

此时,一打开界面即可显示:


SharedPreferences存储

将数据存储进文件

大致分为两步,第一步,获取对象:

SharedPreferences 通过 键值对 的方式来存储数据的。要想存储数据,需要先获取 SharedPreferences对象,Android 中主要提供了三种方法用于得到 SharedPreferences 对象:

  • Context类 中的 getSharedPreferences 方法:此方法接受两个参数,

    1. 第一个参数用于指定 SharedPreferences 文件的名称,如果文件不存在则创建一个。文件都存放在 /data/data/<packagename>/shared_prefs/ 目录下。
    2. 第二个参数用于指定 操作模式,目前只有 MODE_PRIVATE 这种默认的操作模式可选,和直接传入 0 效果是相同的,表示只有当前的应用程序才可以对这个 SharedPreferences 文件进行读写
  • Acitvity类 中的 getPreferences 方法:只有一个参数——操作模式自动使用当前活动的类名来作为 SharedPreferences 的文件名。
  • PreferenceManager类 中的 getDefaultSharedPreferences 方法:静态方法,接收一个 Context 参数,并自动使用当前应用程序的包名作为前缀来命名 SharedPreferences 文件。

第二步,存储数据:

  1. 调用 SharedPreferences对象 的 edit方法 来获取一个 SharedPreferences.Editor对象
  2. 向 SharedPreferences.Editor对象 中添加数据,比如添加一个布尔型数据就使用 putBoolean 方法,添加一个字符串则使用 putString 方法。
  3. 调用 apply 方法将添加的数据提交,从而完成数据存储操作。

实例

实现点击按钮保存数据的功能:

        Button button_share = findViewById(R.id.button_share);button_share.setOnClickListener((View v)->{SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();editor.putString("name", "cmy");editor.putInt("weight", 120);editor.putBoolean("married", false);editor.apply();Toast.makeText(this, "share over", Toast.LENGTH_LONG).show();});

点击红框所示按钮:

即可将数据存在如下图所示的路径中:


从文件中读取数据

第一步仍是获取对象,上文已经讲过,这里不再赘述。

第二步,通过对应的 get**()方法 获取对应类型数据,如字符串使用 getString() 方法,这些 get 方法都接受两个参数:

  • 第一个参数是:也就是 KV模型 中的 Key
  • 第二个参数是默认值:当传入的找不到对应值时,以默认值返回。

实例

点击 get sharePreferences 按钮从 SharedPreferences文件 中读取数据:

再通过 Toast 显示出来:

        Button button_getShare = findViewById(R.id.button_getShare);button_getShare.setOnClickListener((View v)->{SharedPreferences preferences = getSharedPreferences("data", MODE_PRIVATE);String name = preferences.getString("name", "");int weight = preferences.getInt("weight", 0);boolean married = preferences.getBoolean("married", false);String res = name+" "+String.valueOf(weight)+" "+String.valueOf(married);Toast.makeText(this, res, Toast.LENGTH_LONG).show();});

实现记住密码的功能

之前在本博客里实现过登陆界面,这里为登陆界面新加入一个记住密码的功能。

修改布局文件,添加以下代码,实现右侧所示布局:

这里使用到了一个新控件 复选框:CheckBox ,用户可以通过点击来进行选中/取消,以表是否需要记住密码。

修改 LoginActivity.java 代码,结合 SharedPreferences 实现记住密码的功能:


增添的内容主要是:

  • 三个相关对象 CheckBox、SharedPreferences、SharedPreferences.Editor 的创建和实例化;
  • 初始化布尔型对象 isRemember 作为 判断记住密码功能是否生效 的辅助变量;
    1. 一开当然不存在 remember_password 这个键对应的值,isRemember 为默认值 false
    2. 成功登陆一次后,remember_password 这个键对应的值就是 true 了。

以及登陆成功后的操作:

  • 调用 CheckBoxisChecked() 方法检查复选框是否被选中,被选中则返回 true
  • true 时表示用户希望记住密码,此时:
    1. remember_password 对应的值设为 true
    2. accountpassword 对应的值都存入到 SharedPreferences 文件中并提交。
  • false 表示用户并不想记住密码,此时要调用 clean() 方法清楚掉 SharedPreferences 文件中的所有数据。

运行结果:

  • 输入正确的账户和密码,并选中记住密码,点击登录:
  • 通过强制下线跳转回登陆界面,此时发现账号密码已经自动填充了。
  • 如果此时取消选中复选框,再点击登录:
  • 再次返回登陆界面就不会被填充了:

    PS:这里只做示例,实际项目中不能将密码以明文形式存储到 SharedPreferences 文件中,因为会被轻易盗取,必须结合加密算法对密码进行加密。

SQLite数据库存储

为了管理数据库,安卓专门提供了一个 SQLiteOpenHelper 帮助类,这是个抽象类,如果要使用它,需要创建一个 自己的帮助类 去继承它。下面列举几个该类中常用的方法:

  • 两个抽象方法onCreateonUpgrade,我们必须在 自己的帮助类 里重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
  • 两个实例方法getReadableDatabasegetWritableDatabase。这两个方法都可以创建或者打开一个现有的数据库,数据库文件存放在 /data/data/<packagename>/databases/ 目录下,并返回一个可对数据库进行读写的对象。不同的是,当数据库不可写入的时候,getReadableDatabase 方法返回的对象会用只读的方式打开数据库,而 getWritableDatabase 会出现异常
  • 两个构造函数:常用的一个构造方法接收4个参数:第一个是Context;第二个是数据库名;第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null;第四个参数表示当前数据库的版本号,可以用于升级数据库。

创建自己的帮助类

public class MyDatabaseHelper extends SQLiteOpenHelper {public static final String CREATE_STUDENT = "create table Student ("+ "id integer primary key autoincrement,"+ "gender text,"+ "weight real,"+ "age integer,"+ "name text)";private Context context;public MyDatabaseHelper( Context context,  String name,SQLiteDatabase.CursorFactory factory, int version) {super(context, name, factory, version);this.context = context;}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL(CREATE_STUDENT);Toast.makeText(context, "create succeeded", Toast.LENGTH_LONG).show();}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
  • 把建表语句定义成一个字符串常量,integer 表示整型,real 表示浮点型,text 表示文本类型,blob 表示二进制类型。此外,使用了 primary key 将 id 设置为主键,并且用 autocrement 关键字表示 id 列是自增长的。
  • onCreate 方法中有调用了 SQLiteDatabaseexecSQL 方法去执行这条建表语句。

调用自己的帮助类

创建一个活动 SQLiteActivity,其布局内有一个按钮,点击即可创建 Student.db 数据库:

public class SQLiteActivity extends AppCompatActivity {private MyDatabaseHelper myDatabaseHelper;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.sqlite_layout);myDatabaseHelper = new MyDatabaseHelper(this, "Student.db", null, 1);Button button_create = findViewById(R.id.button_create);button_create.setOnClickListener((View v)->{myDatabaseHelper.getWritableDatabase();Toast.makeText(this, "创建数据库成功", Toast.LENGTH_LONG).show();});}
}
  • 第一次点击按钮时,会检测到当前程序并没有 Student.db 这个数据库,于是会创建该数据库并调用 MyDatabaseHelper 中的 onCreate 方法,得以创建 Student 表。
  • 之后点击按钮就不会再调用 MyDatabaseHelper 中的 onCreate 方法了,因为 Student.db 已经存在了。

布局文件 sqlite_layout.xml

<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/button_create"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="create database"/>
</LinearLayout>

运行结果:

create succeeded 只会在数据库首次创建时出现:

创建数据库成功 会在每次点击按钮后出现:

查看数据库和表的创建情况

在环境变量中添加好 platform-tools 后:


打开 cmd,输入 adb shell 进入设备控制台:

通过 su 获取管理员权限,否则无法进入 /data/data/<packagename>/databases/ 路径:

通过 cd 命令进入数据库文件所在目录:

该目录下有两个数据库文件,一个是我们创建的 Student.db ;一个是为了让支持事务的临时日志文件 Student.db-journal

打开数据库:

查看数据库中有哪些表:

PS:android_metadata 是每个数据库自动生成的。

查看建表语句:

通过 .exit.quit 退出数据库:


补全 onUpgrade() 方法

该方法用于升级数据库,目前项目中已经有了一张 Student 表用于存放学生的各种详细数据,如果想再添加一张 Class 表用于记录学生的班级,怎么做呢?

将建表语句添加到自己的帮助类 MyDatabaseHelper 中:

该如上图所示在 onCreate 阶段执行一次 Class 的建表语句吗?

不是的,正如上一个实例中,数据库创建完成后,我们再点击按钮,只会弹出 创建数据库成功 而不会弹出 create succeeded 一样,两者的根本原因都是 onCreate 方法只会在创建数据库时执行一次,创建成功后不会再次执行。

因此无法在 Student.db 存在的情况下通过 onCreate 方法添加新表,而应通过 onUpgrade 方法添加新表。 具体做法如下:

@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {//  如果数据库中存在 Student 表或 Class表,就将他们删除。db.execSQL("drop table if exists Student");db.execSQL("drop table if exists Class");// 然后调用 onCreate 方法重新创建onCreate(db);}

PS:之所以 不跳过删除已有表直接调用 onCreate ,是因为 创建表时如果该表已存在会报错

接下来重新调用 SQLiteOpenHelper 的构造方法,使第四个参数——数据库版本号大于之前传入的 1 即可让 onUpgrade 执行:

运行结果:

PS:使用 AS 时也可以通过一下流程查看数据库及建表情况:


增删查改

CRUD 操作当然可以通过 SQL 语言实现,但 Android 也提供了一系列辅助方法,前面提到 getReadableDatabasegetWriteableDatabase 方法是会返回一个 SQLiteDatabase 对象,借助这个对象就可以对数据进行操作了。

增:SQLiteDatabase.insert()

该方法有三个参数:

  1. 表名
  2. 用于在未指定添加数据的情况下给某些可为空的列自动赋值为 null
  3. ContentValues 对象,它提供了一系列的 put 方法重载,用于向 ContentValues 中添加数据,只需要将表中的每个列名待添加数据传入即可。

在布局中添加了一个按钮用于增加数据:

SQLiteActivityonCreate 方法中添加以下代码:

点击两次按钮的运行结果:



两张表各添加了两行数据。


改:SQLiteDatabase.update()

该方法有四个参数:

  1. 表名;
  2. ContentValues 对象;
  3. 第三个、第四个参数用于约束更新某一行或者某几行的数据,不指定的话默认更新所有行

在布局中添加了一个按钮用于更新数据:

SQLiteActivityonCreate 方法中添加以下代码:

        Button button_update = findViewById(R.id.button_update);button_update.setOnClickListener((View v)->{SQLiteDatabase db = myDatabaseHelper.getReadableDatabase();ContentValues values = new ContentValues();// 第一条数据values.put("weight", 90);values.put("name", "zj");db.update("Student", values, "id = ?", new String[] {"2"});// 第二条数据values.put("class_name", "电子");values.put("class_num", 183);db.update("Class", values, "id = ?", new String[] {"1"});Toast.makeText(this, "更新完成", Toast.LENGTH_LONG).show();});

以第一条数据为例:

  • values 用以更新 weightname 两项属性的值;
  • 第三、第四个参数指定更新 id=2 的行。

点击按钮后的运行结果:



删:SQLiteDatabase.delete()

该方法接受三个参数:

  1. 表名;
  2. 第二、三个用于约束删除某几行的数据,不指定则删除所有行

SQLiteActivityonCreate 方法中添加以下代码:

        Button button_delete = findViewById(R.id.button_delete);button_delete.setOnClickListener((View v)->{SQLiteDatabase db = myDatabaseHelper.getReadableDatabase();db.delete("Student", "weight < ?", new String[] {"100"});Toast.makeText(this, "删除完成", Toast.LENGTH_LONG).show();});
  • 删除 weight < 100 的行。

点击按钮后的运行结果:


查:SQLiteDatabase.query()

该方法比前三个复杂一些,最短的一个重载方法也需要传入七个参数:

  1. 表名;
  2. 用于指定查询哪几列;
  3. 三四个参数用于约束查询某几行的数据,不指定则默认查询所有行的数据
  4. 第五个参数用于指定需要去 group by 的列,不指定则不对查询结果进行分组
  5. 第六个参数用于对 group by 之后的数据进一步过滤;
  6. 第七个参数用于指定查询结果的排序方式。


调用该方法后会返回一个 Cursor 对象,查询到的所有数据都将从这个对象中取出。

        Button button_query = findViewById(R.id.button_query);button_query.setOnClickListener((View v)->{SQLiteDatabase db = myDatabaseHelper.getReadableDatabase();// 查询 Class 表中所有数据Cursor cursor = db.query("Class", null, null, null,null, null, null);if(cursor.moveToFirst()){do{String res = "";res += cursor.getString(cursor.getColumnIndex("class_name")) + " ";res += cursor.getString(cursor.getColumnIndex("class_num"));Toast.makeText(this, res, Toast.LENGTH_LONG).show();}while(cursor.moveToNext());}cursor.close();});
  • query方法 首参数设置为 Class,其余参数设置为 null,表示查询 Class表 所有数据。
  • 调用 moveToFirst方法 将数据指针移动到第一行的位置;
  • 通过 getColumnIndex方法 获取位置索引以遍历每一行数据,并将之通过 Toast 打印到屏幕上;
  • 通过 moveToNext方法 移动数据指针遍历下一行数据,如果指针已经到达了数据指针集的尾后位置,此方法将返回 false

通过 SQL语句 实现增删查改

除了查询语句通过 db.rawQuery() 执行之外,其他三种操作都可以通过 db.execSQL() 执行。

Android入门(12)| 数据持久化相关推荐

  1. Android数据持久化

    Android数据持久化(存储) 1.SharedPreferences SharedPreferences是Android提供的数据持久化的一种手段,适合单进程.小批量的数据存储与访问.Shared ...

  2. android libbfaac.so,Android中Json数据读取与创建

    一:  Json的特性和在数据交互中的地位就不用说了,直接看案例. 首先在android studio中创建assets文件目录,用于存放Json数据文件,android studio 1.3 默认项 ...

  3. $《第一行代码:Android》读书笔记——第6章 数据持久化

    主要讲述了Android数据持久化的三种方式:文件存储.SharedPreference存储.SQLite数据库存储. (一)文件存储 其实Android中文件存储方式和Java的文件操作类似,就是用 ...

  4. Android之数据持久化技术

    一.数据持久化技术概述 1.数据持久化:指将那些内存中的瞬时数据保存到存储设备当中,保证即使在手机或电脑关机的情况下,这些数据仍然不会丢失. 2.持久化技术提供了一种机制可以让数据在瞬时状态和持久状态 ...

  5. Android数据持久化:SharePreference

    SharePreference:作为Android数据持久化的一种,具有一定的便捷性,适合存储一些体积小的数据. 存储数据方式:键值对的方式,类似于Map: 利用SharePreference.Edi ...

  6. Android数据持久化:文件存储

    数据持久化: 数据可分为瞬时数据和关键数据.保存在内存之中的数据是瞬时数据,而对于一些关键性数据,后期需要持续使用的,应当保存在存储设备中: 持久化保存方式: 文件存储.SharePreference ...

  7. Android教程 -09 数据的持久化存储

    视频为本篇播客知识点讲解,建议采用超清模式观看, 欢迎点击订阅我的优酷 任何一个程序其实说白了就是在不停地和数据打交道,数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证及时手机关机的情况下 ...

  8. Android入门:通过JSON数据与服务器进行通信

    我们完成"Android入门:通过XML数据与服务器进行通信"同样的功能,只是数据传输使用JSON而不是XML: 注意点: (1)当返回JSON时,content-type为tex ...

  9. android 清空数组缓存,Android数据持久化之读写SD卡中内容的方法详解

    本文实例讲述了Android数据持久化之读写SD卡中内容的方法.分享给大家供大家参考,具体如下: 前面文章里讲的那三个方法:openFileOutput.openFileInput虽然都能通过流对象O ...

最新文章

  1. Airbnb改进部署管道安全性,规范部署顺序
  2. vmware horizon view 添加vcenter server
  3. 判断一个显示对象是否移除
  4. Graph Embedding方案之DeepWalk
  5. 数据加密:RSA 密钥
  6. html5 怎么实现展开文字,html5实现滚动文字
  7. Navicat for SQLite 10.1.3 注册码
  8. java自己写的网络爬虫
  9. java adt包_adt下载(Android开发工具)adt离线安装包大集合下载 - 3322软件站
  10. c语言汽车租赁平台报告,汽车租赁系统项目报告.doc
  11. 华为云薛浩:走进视频“新时代”
  12. html问卷调查实验报告,网上问卷调查实验报告..doc
  13. oracle 联合查询去重,oracle两张表关联查询
  14. 快捷键,总结一些实用高效的快捷键
  15. 数据结构的小知识点(初学者使用)“朝闻道”知识分享大赛
  16. miui12是Android版本,miui12基于安卓几版本开发的?miui12是安卓11吗[多图]
  17. LDO + 稳压管的扩压电路试用笔记
  18. okhttp3发送https请求
  19. matlab计算macd_[原创]基于MATLAB的一个简单的交易策略(基于MACD)的Matlab代码-经管之家官网!...
  20. 多柱汉罗塔(python解法,带注释,注释为个人理解)

热门文章

  1. html全局浮窗,Html 实现浮动窗口
  2. java的requestmapping_SpringMVC RequestMapping 详解
  3. Windows CE创建桌面快捷方式
  4. asp.net php 哪个好,ASP.NET、JSP和PHP究竟哪个好
  5. python开发中遇到的难题_初学者在Python开发中常见的问题(上)
  6. hadoop namenode启动不了_集群版hadoop安装,写给大忙人看的
  7. LSGO软件技术团队2015~2016学年第六周(1005~1011)总结
  8. 【转】WPF调用图片路径,或资源图片
  9. 【转】ABP源码分析十五:ABP中的实用扩展方法
  10. 【转】【MySQL】运行原理(四):重做日志(redo log),回滚日志(undo log),二进制日志(binlog)