Android第一行代码--详解持久化技术

Posted 钢铁-程序猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android第一行代码--详解持久化技术相关的知识,希望对你有一定的参考价值。

android第一行代码–详解持久化技术

1、持久化技术简介

Android系统中主要提供了三种方式用于简单的实现数据持久化功能,即文件存储、SharePrefrence存储以及数据库存储。除了这三种方式,你还可以将数据保存在自己的SD卡中。

2、文件存储

文件存储不对存储的内容进行任何格式化处理,所有数据都是原封不动的保存在文件当中的。

将数据存储到文件中(openFileOutput(),返回的是FileOutputStream对象,通过流将数据写入文件)

Context类提供openFileOutput()方法用于将数据存储到指定的文件中。

  • 1、文件名,不可包含路径,因此,所有的文件默认存储在/data/data/< packagename>/files/目录下的。
  • 2、文件的操作模式,两种:MODE_PRIVATE和MODE_APPEND。MODE_PRIVATE是默认的操作模式,当指定同样文件名的时候,MODE_PRIVATE模式下会覆盖原文件,MODE_APPEND模式下会追加内容。
public void save(){
	String data = "Data to save";
	FileOutputStream out = null;
	BufferedWriter writer = null;
	try{
		out = openFileOutput("data",Context.MODE_PRIVATE);
		writer = new BufferedWriter(new OutputStreamWriter(out));
		writer.write(data);
	}catch(IOException e){
		e.printStackTrace();
	}finally{
		try{
			if(writer != null){
				writer.close();
			}
		}catch(IOException e){
			e.printStackTrace();
		}
	}
}

如何证明数据确实已经保存成功了呢?可以使用Android Device Monitor工具(点击Android Studio导航栏中的Tools–》android,现在导航栏已经找不到了,可以到\\sdk\\tools\\lib\\monitor-x86_64中去找monitor打开)来查看。

从文件中读取数据(openFileInput()方法)

openFileInput()

  • 接收文件名,系统会自动到/data/data/< pcakage name>/files/目录下去加载这个文件,并返回FileInputStream对象。
public String load(){
	FileInputStream in = null;
	BufferedReader reader = null;
	StringBuilder content = new StringBuilder();
	try{
		in = openFileInput("data");
		reader = new BufferedReader(new InputStreamReader(in));
		String line = "";
		while((line = read.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();
}

SharePreferences存储(使用键值对的方式存储数据)

SharePreferences支持多种不同的数据类型存储,如果存储的数据类型是整型,那么读取出来的也是整型。

1、将数据存储到SharedPreferences中

Android中主要提供了三种方法用于得到SharedPreferences对象,用于存储数据。

  • 1、Context类中的getSharedPreferences()方法
  • 2、Activity类中的getPreferences()方法
  • 3、PreferenceManager类中的getDefaultSharedPreference()方法。

Context类中的getSharedPreferences()方法

两个参数:

  • 指定SharedPreferences文件的名称,不存在则创建,文件都存放在/data/data/< package name>/shared_prefs/目录下的
  • 指定操作模式,只有MODE_PRIVATE这一种模式可选,是默认的操作模式,和直接传0效果相同,表示只有当前应用程序才可以对这个SharedPreferences文件进行读写。其他模式均废除。

Activity类中的getPreferences()方法(会自动将当前活动的类名作为文件名)

这个方法和Context中的getSharedPreferences()方法很相似,不过它只接受一个操作模式参数,因此使用这个方法时会自动将当前活动的类名作为SharedPreferences的文件名。

PreferenceManager类中的getDefaultSharedPreference()方法(使用当前程序的包名作为前缀命名文件)

这是一个静态方法,接受一个Context参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件。

四步骤:

  • 1、获取SharedPreferences对象
  • 2、调用该对象的edit方法来获取一个SharedPreferences.Editor对象
  • 3、向SharedPreferences.Editor对象中添加数据。比如一个布尔数据就使用putBoolean()方法,添加字符串就使用putString()
  • 4、调用apply方法将添加的数据提交。实现数据存储操作。
package com.example.sharedpreferencestest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button saveData = (Button)findViewById(R.id.save_data);
        saveData.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit();
                editor.putString("name","Tom");
                editor.putInt("age",28);
                editor.putBoolean("married",false);
                editor.apply();
            }
        });
    }
}

从SharedPreferences中读取数据

从SharedPreferences文件中读取数据很简单,SharedPreferences提供了一系列的get方法,用于对存储的数据进行读取。

提供了很多get方法,这些get方法都有两个参数。

  • 第一个是键值
  • 第二个是默认值,当传入的键找不到对应的值会以什么样的默认值进行返回。

SQLite数据库存储

SQLite是Android内置的数据库,是轻量级的关系型数据库,运算速度非常快,占用资源很少,通常几百kb内存就足够了。不仅支持标准的SQL语法,还遵循ACID事务。

创建数据库(SQLiteOpenHelper)

通过这个类就可以非常简单的对数据库进行创建和升级。

两个抽象方法:

  • onCreate()方法
  • onUpdategrade()方法

SQLiteOpenHelper是一个抽象类,若要用,我们必须在自己的帮助类里面重写这两个方法,然后在这两个方法中实现创建、升级数据库的逻辑

两个重要的实例化方法:

  • getReadableDatabase()
  • getWritableDatabase()

这两个方法都可以创建或打开一个现有的数据库(如果数据库已经存在则直接打开,否则创建一个新的数据库),并返回一个对数据库进行读写操作的对象。

不同的是,当数据库不可写入的时候(如磁盘已满),getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritaleDatabase()方法则出现异常。

SQLiteOpenHelper中有两个构造函数可供重写,参数少的构造函数有4个参数,分别是:

  • 1、Context:必须要有它才能对数据库进行操作
  • 2、数据库名
  • 3、允许我们在查询数据时返回一个自定义的Cursor(光标),一般传入null。
  • 4、当前数据库版本号,用于对数据库进行升级操作。

数据库文件存放在/data/data/< package name>/databases/目录下

SQLite不像其他的数据库拥有复杂的数据类型,它的类型很简单:

  • integer 整型
  • real 浮点型
  • text 文本类型
  • blob 二进制类型

例子

创建MyDatabaseHelper继承SQLiteOpenHelper:

package com.example.databasetest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;

public class MyDatabaseHelper extends SQLiteOpenHelper {
    public static final String CREATE_BOOK = "create table book("
            +"id integer primary key autoincrement,"
            +"author text,"
            +"price real,"
            +"pages integer,"
            +"name text)";


    private Context mContext;

    public MyDatabaseHelper(Context context,String name,SQLiteDatabase.CursorFactory factory,int version){
        super(context,name,factory,version);
        mContext = context;
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        Toast.makeText(mContext,"Create succeed",Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

在activity_main.xml中创建一个按钮,在MainActivity.java中设置点击事件:

package com.example.databasetest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,1);
        Button createDatabase = (Button)findViewById(R.id.create_database);
        createDatabase.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //在这里,想要获取SQLiteDatabase对象
                //但是第一次点击的时候并没有BookStore.db这个数据库
                //于是会调用MyDatabaseHelper中的onCreate方法。这样book表就得到了创建。
                dbHelper.getWritableDatabase();
            }
        });
    }
}

怎么证明数据库创建成功?

  • 使用adb shell对数据库和表的创建情况进行检查。

adb是Android SDK中自带的一个调试工具,使用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。它存放在sdk的platform-tools目录下。

cd命令进入到/data/data/com.example.databasetest/databases/目录下,并用ls命令查看该目录里的文件。

在这里插入图片描述

书上说是有两个文件.db和.db-journal,其中.db-journal是为了让数据库能够支持事务而产生的临时日志文件。自己实现产生三个文件.db、.db-shm以及.db-wal

升级数据库

如何让自己创建的继承自SQLiteOpenHelper类中的onUpgrade执行?

  • SQLiteOpenHelper的构造方法里接收的第四个参数为当前数据库的版本号,之前传入的是1,现在只需要传入比1打的数,就可以让onUpgrade()方法得到执行了。

比如:
修改MainActivity代码

package com.example.databasetest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

		//之前传入的是数据库的版本号为1,
		//现在传入的是数据库的版本号为2。
		//所以会执行onUpgrade
        dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,2);
        
        Button createDatabase = (Button)findViewById(R.id.create_database);
        createDatabase.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //在这里,想要获取SQLiteDatabase对象
                //但是第一次点击的时候并没有BookStore.db这个数据库
                //于是会调用MyDatabaseHelper中的onCreate方法。这样book表就得到了创建。
                dbHelper.getWritableDatabase();
            }
        });
    }

}

修改MyDatabaseHelper代码

package com.example.databasetest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;

public class MyDatabaseHelper extends SQLiteOpenHelper {
    public static final String CREATE_BOOK = "create table book("
            +"id integer primary key autoincrement,"
            +"author text,"
            +"price real,"
            +"pages integer,"
            +"name text)";

    public static final String CREATE_CATEGORY = "create table category("
            +"id integer primary key autoincrement,"
            +"category_name text,"
            +"category_code integer)";

    private Context mContext;

    public MyDatabaseHelper(Context context,String name,SQLiteDatabase.CursorFactory factory,int version){
        super(context,name,factory,version);
        mContext = context;
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
        Toast.makeText(mContext,"Create succeed",Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("drop table if exists Book");
        db.execSQL("drop table if exists Category");
        onCreate(db);
    }
}

添加数据

对数据的操作,CRUD(Create、Retrieve、Update、Delete)。

可以借助getReadableDatabase()或getWriteableDatabase()方法返回的SQLiteDatabase对象来对数据进行CRUD操作。

insert()方法

三个参数:

  • 1、表名
  • 2、用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般我们用不到这个功能,直接传入null。
  • 3、第三个参数是一个ContentValues对象。提供了一系列的put方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
//开始第一组数据组装
values.put("name","The Da Vinci Code");
values.put("author","Dan Brown"):
values.put("pages",454);
values.put("price",16.96);
db.insert("Book",null,values);

更新数据(update)

参数:

  • 第一个参数:表名
  • 第二个参数:ContentValues对象
  • 第三、四个参数:约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price",10.99);
db.update("Books",values,"name=?",new String[]{"The Da Vinci Code"});

第三个第四个参数用于指定具体更新的行,第三个参数对应的是SQL语句的where部分,表示更新所有name等于?的行,而?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。因此上述代码的意图是将名字为The Da Vinci Code的这本书的价格改成10.99

删除数据

delete方法,参数:

  • 1、第一个参数表名
  • 2、第二个参数又是用于约束删除某一行或某几行的数据,不指定则默认删除所有行。
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book","pages>?",new String[]{"500"});

删除页码超过500页的书。

查询数据(query())

这个方法的参数非常复杂,最短的一个方法重载需要传入7个参数。这7个参数的含义:

  • 1、第一个参数:表名
  • 2、指定查询哪几列,如果不指定,则默认查询所有列。
  • 3、第三、第四个参数用于约束查询某一个行或某几行的数据,不指定,默认查所有行
  • 4、第五个参数用于指定需要去group by的列,不指定则表示不对查询结果进行group by。
  • 第六个参数对用group by之后的数据进行进一步过滤。
  • 第七个参数用于制定查询结果的排序方式,不指定则使用默认排序方式。
query()方法参数对应SQL部分描述
tablefrom table_name指定查询的表名
columnsselect column1,column2指定查询的列名
selectionwhere column = value指定where的约束条件
selectionArgs为where中的占位符提供具体的值
groupBygroup by column指定需要group by的列
havinghaving column = value对group by的结果进一步约束
order byorder by column1,column2指定查询结果的排序方式

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

SQLiteDatabase db = dbHelper.getWritableDatabase();
//查询Book表中所有的数据
Cursor cursor = db.query("Book",null,null,null,null,null,null);
if(cursor.moveToFirst()){
	do{
		//遍历Cursor对象,取出数据并打印
		String name = cursor.getString(cursor.getColumnIndex("name"));
		int pages = cursor.getInt(cursor.getColumnIndex("pages"));
		double price = cursor.getDouble(cursor.getColumnIndex("price"));
	}while(cursor.moveToNext())
}
cursor.close();

使用LitePal(开源库)操作数据库(需要导包,创建配置文件,在AndroidManifest中配置LitePalApplication)

LitePal简介

LitePal是一款开源的Android数据库框架,它采用了对象关系映射(ORM)的模式,并将我们平时开发最常用到的一些数据库功能进行封装,使得不用编写一行SQL语句就可以完成各种建表和增删改查的操作。

配置LitePal

编辑app/build.gradle文件,在dependencies闭包中添加如下内容

compile ‘org.litepal.android:core:1.3.2’

在app/src/main/下创建目录assets目录,然后在assets目录下新建一个litepal.xml文件。

<?xml version="1.0" encoding="UTF-8" ?>
<litepal>
    <dbname value="BookStore"></dbname>
    <version value="1"></version>
    <list>

    </list>
</litepal>

< dbname>标签用于指定数据库名,< version>标签用于指定数据库版本号,< list>标签用于指定所有的映射模型。

还需要再配置一下LitePalApplication,修改AndroidManifest中的代码,如下所示:

android:name=“org.litepal.LitePalApplication”

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.litepaltest">

    <application
        android:name="org.litepal.LitePalApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.LitePalTest">
        <activity android:name=".MainActivity"以上是关于Android第一行代码--详解持久化技术的主要内容,如果未能解决你的问题,请参考以下文章

Android第一行代码--详解持久化技术

第一行代码 Android 第二版到货啦

《第一行代码Android(第3版)》— Android 书籍

第一行代码 学习

阅读郭林《第一行代码》的笔记——第6章 数据存储全方案,详解持久化技术

第一行代码 6.4 数据存储全方案-详解持久化数据- 数据库