Android学习羁绊之数据存储

数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑关机的情况下,这些数据仍然不会丢失。保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据是处于持久状态的,持久化技术则提供了一种机制可以让数据在瞬时状态和持久状态之间进行转换。

Android系统中主要提供了3种方式用于简单地实现数据持久化功能,即文件存储、SharedPreferences存储以及数据库存储。除了这3种方式之外,还可以将数据保存在手机的SD卡中,不过使用文件、SharedPreferences或数据库来保存数据会相对更简单一些,而且比起将数据保存在SD卡中会更加地安全。

文件存储

文件存储是Android中最基本的一种数据存储方式,它不对存储的内容进行任何的格式化处理,所有数据都是原封不动地保存到文件当中的,因而它比较适合用于存储一些简单的文本数据或二进制数据。

存储数据到文件中

Context 类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中。
openFileOutput()方法有两个参数:

  • 第一个参数是文件名,在文件创建的时候使用的是这个名称,指定的文件名不可以包含路径,所有的文件都是默认存储到/data/data/<package name>/files/目录下的。
  • 第二个参数是文件的操作模式,主要有以下几种模式:
  • MODE_PRIVATE:默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容
  • MODE_APPEND:表示如果该文件已存在,就往文件里面追加内容,不存在就创建新文件
  • MODE_WORLD_READABLE:允许其他程序对当前程序的文件进行读操作,易引起应用的安全性漏洞,已在Android 4.2版本中被废弃
  • MODE_WORLD_WRITEABLE:允许其他程序对当前程序的文件进行写操作,易引起应用的安全性漏洞,已在Android 4.2版本中被废弃

openFileOutput () 方法返回的是一个FileOutputStream 对象,得到了这个对象之后就可以使用Java流的方式将数据写入到文件中了。
代码如下所示:

    private void save(String inputText) {
        FileOutputStream out = null;
        BufferedWriter writer = null;
        try {
            out = openFileOutput("data", Context.MODE_PRIVATE);
            writer = new BufferedWriter(new OutputStreamWriter(out));
            writer.write(inputText);
        } catch (IOException e) {
            Log.e("文件存储失败", e.getMessage());
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                Log.e("文件流关闭失败", e.getMessage());
            }
        }
    }

从文件中读取数据

Context 类中提供了一个openFileInput() 方法,用于从文件中读取数据。
openFileInput()方法只接收一个参数,即要读取的文件名,系统会自动到/data/data/<package name>/files/目录下去加载这个文件,并返回一个FileInputStream 对象,得到了这个对象之后再通过Java流的方式就可以将数据读取出来了。
代码如下所示:

	private 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 = reader.readLine()) != null) {
                content.append(line);
            }
        } catch (IOException e) {
            Log.e("文件读取失败", "load: " + e.getMessage());
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                Log.e("文件流关闭失败", e.getMessage());
            }
        }
        return content.toString();
    }

SharePreferences

SharedPreferences是使用键值对的方式来存储数据,当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把相应的值取出来。SharedPreferences还支持多种不同的数据类型存储,如果存储的数据类型是整型,那么读取出来的数据也是整型的;如果存储的数据是一个字符串,那么读取出来的数据仍然是字符串。

存储数据到SharePreferences中

使用SharedPreferences来存储数据,首先需要获取到SharedPreferences 对象。Android中主要提供了3种方法用于得到SharedPreferences 对象。

  1. Context 类中的getSharedPreferences()方法
    此方法接收两个参数,第一个参数用于指定SharedPreferences文件的名称,如果指定的文件不存在则会创建一个,SharedPreferences文件都是存放在/data/data/<package name>/shared_prefs/目录下的。第二个参数用于指定操作模式,目前只有MODE_PRIVATE这一种模式可选,它是默认的操作模式,和直接传入0效果是相同的,表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。其他几种操作模式均已被废弃。

  2. Activity 类中的getPreferences()方法
    和Context中的getSharedPreferences() 方法很相似,只接收一个操作模式参数,使用这个方法时会自动将当前活动的类名作为SharedPreferences的文件名。

  3. PreferenceManager类中的getDefaultSharedPreferences()方法(该类已被废弃)
    静态方法,接收一个Context 参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件。

获得SharedPreferences 对象之后,就可以开始向SharedPreferences文件中存储数据了:

  1. 调用SharedPreferences 对象的edit() 方法来获取一个SharedPreferences.Editor 对象。

  2. 向SharedPreferences.Editor 对象中添加数据。(添加一个布尔型数据就使用putBoolean() 方法,添加一个字符串则使用putString() 方法)

  3. 调用apply() 方法将添加的数据提交,从而完成数据存储操作。

从SharedPreferences中读取数据

SharedPreferences 对象中提供了一系列的get 方法,用于对存储的数据进行读取,每种get 方法都对应了SharedPreferences.Editor 中的一种put 方法。这些get 方法都接收两个参数,第一个参数是键,传入存储数据时使用的键就可以得到相应的值了;第二个参数是默认值,即表示当传入的键找不到对应的值时会以什么样的默认值进行返回。

getString("name", "");

SQLite数据库存储

SQLite是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百KB的内存,因而特别适合在移动设备上使用。SQLite不仅支持标准的SQL语法,还遵循了数据库的ACID事务,掌握了其他关系型数据库的用法,上手SQLite就很快。SQLite又比一般的数据库要简单得多,它甚至不用设置用户名和密码就可以使用。Android正是把这个功能极为强大的数据库嵌入到了系统当中,使得本地持久化的功能有了一次质的飞跃。

创建数据库

Android提供了一个SQLiteOpenHelper帮助类来帮助我们使用SQLite,SQLiteOpenHelper是一个抽象类,我们需要创建一个自己的帮助类去继承它,才能使用它。SQLiteOpenHelper中有两个抽象方法,分别是onCreate() 和onUpgrade() ,在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。

SQLiteOpenHelper中还有两个非常重要的实例方法:getReadableDatabase()和getWritableDatabase():

  • getReadableDatabase():创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。当数据库不可写入的时候(如磁盘空间已满),返回的对象将以只读的方式去打开数据库。
  • getWritableDatabase():创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。当数据库不可写入的时候(如磁盘空间已满),则将出现异常。

SQLiteOpenHelper提供了几个构造方法:

    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name,  @Nullable CursorFactory factory, int version) {
            this(context, name, factory, version, null);
    }

    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable CursorFactory factory, int version,   @Nullable DatabaseErrorHandler errorHandler) {
            this(context, name, factory, version, 0, errorHandler);
    }

    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version,  @NonNull SQLiteDatabase.OpenParams openParams) {
        this(context, name, version, 0, openParams.toBuilder());
    }

    public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable CursorFactory factory, int version, int minimumSupportedVersion, @Nullable DatabaseErrorHandler errorHandler) {
        this(context, name, version, minimumSupportedVersion, new SQLiteDatabase.OpenParams.Builder());
        mOpenParamsBuilder.setCursorFactory(factory);
        mOpenParamsBuilder.setErrorHandler(errorHandler);
    }

    private SQLiteOpenHelper(@Nullable Context context, @Nullable String name, int version, int minimumSupportedVersion, @NonNull SQLiteDatabase.OpenParams.Builder openParamsBuilder) {
        Preconditions.checkNotNull(openParamsBuilder);
        if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
        mContext = context;
        mName = name;
        mNewVersion = version;
        mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
        setOpenParamsBuilder(openParamsBuilder);
    }

一般重写第一个构造方法即可,这个方法有四个参数:

  1. Context:当前的Activity。
  2. name:数据库名
  3. factory:允许在查询数据的时候返回一个自定义的Cursor
  4. version:当前数据库的版本号,可用于对数据库进行升级操作

构建出SQLiteOpenHelper的实例之后,再调用它的getReadableDatabase() 或getWritableDatabase() 方法就能够创建数据库了,数据库文件会存放在/data/data/<package name>/databases/目录下。

自定义SQLiteOpenHelper代码如下:

public class DatabaseHelper extends SQLiteOpenHelper {

    public DatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        //自定义构造逻辑
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //自定义数据库创建逻辑
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //自定义数据库更新逻辑
    }
}

使用SQL语句创建数据库,SQLiteDatabase提供了execSQL()方法,将要执行的SQL语句字符串传入即可。

查看数据库的工具

Android SDK提供了一个调试工具——adb,使用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。
它存放在sdk的platform-tools目录下,如果想要在命令行中使用这个工具,就需要先把它的路径配置到环境变量里。
打开命令行界面,输入adb shell ,就会进入到设备的控制台,来使用cd 命令进入到/data/data/<package name>/databases/目录下,使用sqlite 命令来打开数据库,键入sqlite3,后面加上数据库名,就可以查看对应的数据库。

升级数据库

要升级数据库,需要在onUpgrade()方法中添加相应的升级逻辑,同时更改SQLiteOpenHelper的构造方法中第四个参数version,传入一个比当前版本号更大的数,即可完成对数据库升级。

//将第一条创建自定义SQLiteOpenHelper语句更改成第二条即可。
databaseHelper = new DatabaseHelper(this, "BookStore.db", null, 1);

databaseHelper = new DatabaseHelper(this, "BookStore.db", null, 2);

添加数据

对数据进行的操作有4种,即CRUD。其中C代表添加(Create),R代表查询(Retrieve),U代表更新(Update),D代表删除(Delete)。每一种操作又各自对应了一种SQL命令。

调用SQLiteOpenHelper的getReadableDatabase()或getWritableDatabase() 方法是可以用于创建和升级数据库的,这两个方法还都会返回一个SQLiteDatabase 对象,借助这个对象就可以对数据进行CRUD操作。

insert(String table, String nullColumnHack, ContentValues values);

SQLiteDatabase 中提供了一个insert() 方法,这个方法就是专门用于添加数据的。它接收3个参数:

  1. table:要插入数据的表名
  2. nullColumnHack:用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL
  3. values:ContentValues 对象,它提供了一系列的put() 方法重载,用于向ContentValues 中添加数据,将表中的每个列名以及相应的待添加数据传入。

相关代码如下所示:

SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", "The Da Vinci Code");
values.put("author", "Dan Brown");
database.insert("Book", null, values);

更新数据

update(String table, ContentValues values, String whereClause, String[] whereArgs);

SQLiteDatabase 中也提供update() 方法,用于对数据进行更新,这个方法接收4个参数:

  1. table:要更新数据的表名
  2. values:ContentValues 对象,它提供了一系列的put() 方法重载,用于向ContentValues 中添加数据,将表中的每个列名以及相应的待添加数据传入。
  3. whereClause:更新某一行或某几行中的数据。
  4. whereArgs:更新某一行或某几行中的数据。
    对于第三第四个参数,用于约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。

相关代码如下:

SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 12);
database.update("Book", values, "name = ?", new String[]{"The Da Vinci Code"});

删除数据

delete(String table, String whereClause, String[] whereArgs);

SQLiteDatabase中提供delete() 方法,专门用于删除数据,这个方法接收3个参数:

  1. table:要删除数据的表名
  2. whereClause:删除某一行或某几行中的数据。
  3. whereArgs:删除某一行或某几行中的数据。
    对于第二第三个参数,用于约束删除某一行或某几行中的数据,不指定的话默认就是删除所有行。

相关代码如下:

SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete("Book", "pages > ?", new String[]{"500"});

查询数据

SQLiteDatabase中提供query() 方法用于对数据进行查询,该方法参数较多:

调用query()方法会返回一个Cursor对象,通过循环遍历的方式从这个对象中抽取数据。

Cursor对象常用的方法如下:

  • getString(int columnIndex):获取字符串数据,同理有getInt(),getDouble()等等相关方法,传入数据对应的索引值作为参数,和getColumnIndex()搭配使用
  • getColumnIndex(String columnName):获取到某一列在表中对应的位置索引,然后将这个索引传入到相应的取值方法中,就可以得到从数据库中读取到的数据
  • close():关闭Cursor
  • moveToFirst():判断是否存在数据,若存在,则将数据的指针移动到第一行的位置,并返回true,若不存在数据,则直接返回false
  • moveToNext():判断当前数据指针是否指向最后一行数据,若没有,则将数据指针移动到下一行的位置,并返回true,若是,则直接返回false

使用SQL操作数据库

查询数据的时候调用的是SQLiteDatabase的rawQuery() 方法,其他的操作都是调用的execSQL() 方法,将SQL语句作为参数传入即可实现SQL语句操作数据库

使用LitePal操作数据库

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

配置LitePal

在app/build.gradle文件中添加以下内容就可以引入LitePal:

dependencies {
    ...
    implementation 'org.litepal.android:core:1.4.1'
}

然后在项目的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>标签用于指定映射类型

最后还要在AndroidManifest.xml文件中添加内容:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="cn.chenjianlink.android.litepal">

    <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/AppTheme">
        ...

</manifest>

将项目的application 配置为org.litepal.LitePalApplication ,这样才能让LitePal的所有功能都可以正常工作。

这样项目就可以使用LitePal了

用LitePal创建数据库

使用的编程语言是面向对象语言,使用的数据库则是关系型数据库,那么将面向对象的语言和面向关系的数据库之间建立一种映射关系,这就是对象关系映射。

创建Java Bean类,定义需要的字段,并生成相应的get和set方法,然后将该类添加到映射模型列表当中,修改litepal.xml文件:

<?xml version="1.0" encoding="utf-8"?>
<litepal>
    <dbname value="BookStore"></dbname>
    <version value="2"></version>
    <list>
        <mapping class="cn.chenjianlink.android.litepal.Book"></mapping>
    </list>
</litepal>

在<list>标签中添加 <mapping>标签来声明要配置的映射模型类(Java Bean),使用完整的类名表示。

最后在Java代码中引入以下内容:

LitePal.getDatabase();

这样就可以创建好数据库了。

用LitePal升级数据库

使用SQLiteOpenHelper来升级数据库的方式,虽说功能是实现了,但有有一个问题:升级数据库的时候需要先把之前的表drop掉,然后再重新创建才行,这样会造成数据丢失,每当升级一次数据库,之前表中的数据就全没了。可以通过复杂的逻辑控制来避免这种情况,但是维护成本很高,但使用LitePal,可以有效避免这个问题,而且也更简单。

若要在原有表中添加字段,则直接在对应的Java Bean中添加字段即可,若要添加新的表,则在<list>标签中引入要创建的表对应的Java Bean,最后将版本号加1即可。

用LitePal添加数据

进行CRUD操作时,必须让Java Bean继承DataSupport 类:

public class Book extends DataSupport {
    ...
}

要添加新数据,则创建出模型类的实例,再将所有要存储的数据设置好,最后调用一下save() 方法就好了。

Book book = new Book();
book.setName("The Da Vinci Code");
book.setPrice(16.96);
book.save();

用LitePal更新数据

最简单的一种更新方式就是对已存储的对象重新设值,然后重新调用save() 方法。这种更新方式只能对已存储的对象进行操作,限制性比较大。

Book book = new Book();
book.setName("The Lost Symbol");
book.setPrice(19.95);
book.save();
book.setPrice(10.99);
book.save();

对于LitePal来说,对象是否已存储就是根据调用model.isSaved() 方法的结果来判断的,返回true 就表示已存储,返回false 就表示未存储。
只有在两种情况下model.isSaved() 方法才会返回true ,一种情况是已经调用过model.save() 方法去添加数据了,此时model 会被认为是已存储的对象。另一种情况是model 对象是通过LitePal提供的查询API查出来的,由于是从数据库中查到的对象,因此也会被认为是已存储的对象。

另外一种方法是调用updateAll() 方法去执行更新操作,updateAll()方法中可以指定一个条件约束,和SQLiteDatabase中update() 方法的where参数部分有点类似,但更加简洁,如果不指定条件语句的话,就表示更新所有数据。

Book book = new Book();
book.setPrice(14.00);
book.updateAll("name = ? and author = ?", "The Lost Symbol", "Dan Brown");

当把一个字段的值更新成默认值时,是能使用set方法来设置数据,使用setToDefault()来设置。

Book book = new Book();
book.setToDefault("price");
book.updateAll();

用LitePal删除数据

使用LitePal删除数据的方式主要有两种:

  • 第一种方法:直接调用已存储对象的delete()方法
  • 第二种方法:调用DataSupport.deleteAll()方法来删除数据,其中deleteAll()方法的第一个参数用于指定删除哪张表中的数据(Java Bean的class对象),第二个参数用于指定约束条件
DataSupport.deleteAll(Book.class, "price < ?", "15");

用LitePal查询数据

LitePal提供了多种方法来进行数据库查询,极大化简了查询的操作,常用的查询方法如下:

  • findAll():查询所有数据,传入要查询数据模型的Java Bean的class对象,返回对应Java Bean的集合对象
List<Book> books = DataSupport.findAll(Book.class);
  • findFirst():查询表中的第一条数据,传入要查询数据模型的Java Bean的class对象
Book firstBook = DataSupport.findFirst(Book.class);
  • findLast():查询表中的最后一条数据,传入要查询数据模型的Java Bean的class对象
Book lastBook = DataSupport.findLast(Book.class);

除此之外,可以使用通过连缀查询来定制更多的查询功能:

  • select()方法用于指定查询哪几列的数据,对应了SQL当中的select 关键字
List<Book> books = DataSupport.select("name", "author").find(Book.class);
  • where() 方法用于指定查询的约束条件,对应了SQL当中的where 关键字
List<Book> books = DataSupport.where("pages > ?", "400").find(Book.class);
  • order() 方法用于指定结果的排序方式,对应了SQL当中的order by 关键字
List<Book> books = DataSupport.order("price desc").find(Book.class);
  • limit() 方法用于指定查询结果的数量
List<Book> books = DataSupport.limit(3).find(Book.class);
  • offset() 方法用于指定查询结果的偏移量
List<Book> books = DataSupport.limit(3).offset(1).find(Book.class);

用以上5个方法进行任意的连缀组合,来完成一个比较复杂的查询操作

  • find() 方法用于执行查询,构建好查询条件后调用这个方法执行查询,传入对应的Java Bean的class对象

LitePal还支持原生SQL语句进行查询:调用DataSupport.findBySQL() 方法来进行原生查询,第一个参数用于指定SQL语句,第二个参数用于指定占位符的值

Cursor c = DataSupport.findBySQL("select * from Book where pages > ? and price < ?", "400",  "20");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姓chen的大键哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值