Android系统中主要提供了3种方式用于简单地实现数据持久化功能,即文件存储、SharePrefererence存储以及数据库存储。当然,除了这三种方式之外,你还可以将数据保存在手机的SD卡中,不过使用文件、SharePreference或数据库来保存数据会相对更简单一些,而且比起将数据保存在SD卡中会更安全。
文件存储:
文件存储是Android中最基本的一种数据存储方式,它不对存储的内容进行任何的格式化处理,所有数据都是原封不动地保存在文件当中的,因而它比较适合用于存储一些简单的文本数据或二进制数据。如果你想使用文件存储的方式来保存一些较为复杂的文本数据,就需要定义一套自己的个格式规范,这样可以方便之后将数据从文件中重新解析出来。
将数据存储到文件中:
Context类中提供了一个openfileoutput方法,可以用于将数据存储到指定的文件中,这个方法有两个参数,第一个参数是文件名,在文件创建的时候使用的就是这个名称,注意这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到\data\data
openfileoutput方法返回的是一个fileoutputstream对象,得到了这个对象之后就可以使用java流的方式将数据写到文件中了。以下是一段简单的代码实例,直接拉过来
这里对java流解释一下。通过openfileoutput方法能够得到一个fileoutputstream对象,然后借助它构建出一个outputstreamwriter对象,在截至使用outputstreamwriter构建出一个bufferedwriter对象,这样就可以通过bufferedwriter来将文本内容写入到文件中了。
在oncreate方法中获取了edittext的实例,然后重写了ondestroy方法,这样就可以保证在活动销毁之前一定会调用这个方法。再ondestroy方法中获取到edittext中输入的内容,并调用save方法把输入的内容存储到文件中,文件命名为data。
从文件中读取数据
类似于将数据存储到文件中,Context类中还提供了一个openfileinput方法,用于从文件中读取数据。这个方法要比openfileoutput简单一些,它只接受一个参数,即要读取的文件名,然后系统会自动到\data\data
修改活动中的代码
这里在oncreate方法中调用load方法来读取文件中存储的文本内容,如果读取的内容不会null,就调用edittext的settext方法将内容填充的edittext里,并调用setSelection方法将输入光标移动到文本的末尾位置以便于继续输入,然后弹出一句还原成功的提示。
代码在对字符串进行非空判断的时候使用了TextUtiles.isEmpty方法,这是一个非常好用的方法,他可以一次性进行两种空值的判断。当传入字符串等于null或者等于空字符串时,这个方法都会返回true,从而使得我们不需要先单独判断这两种空值再使用逻辑运算符连接起来了。
SharePreferences存储
不同于文件的存储方式,SharePreferences是使用键值对的方式来存储数据的。也就是说当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键。而且SharePreferences还支持多种不同的数据类型春促,如果存储的数据类型是整形,纳米读取出来的数据也是整性的;如果存储的数据是一个字符串,纳米读取出来的数据仍是字符串。
将数据存储到SharePreferences中
要使用SharePreferences储存数据,首先需要获取它的对象。android中主要提供了三种方法用于得到对象
1.Context类中的getSharePreferes方法
此方法接受两个参数,第一个参数用于指定SharePreferes文件的名称,如果指定的文件不存在则会创建一个,SharePreferences文件都是存放在data\data
2.Activity类中的getPreferences方法
这个方法和Context中的getSharedPreferences方法很相似,只不过它只接收一个操作模式参数,因为使用这个方法时会自动将当前活动的类名作为SharedPrefereces的文件名。
3.PreferenceManager类中的getDefaultSharedPreferences方法
这是一个静态方法,它接受一个Context参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件。得到了SharedPreferences对象之后,就可以开始向SharedPreferences文件中存储数据了,主要可以分三步实现。
(1)调用SharedPreferences对象的edit方法来获取一个SharedPreferences.Editor对象
(2)向SharedPreferences.Editor对象中添加数据,比如添加一个布尔型数据就使用putBoolean方法,添加一个字符串就要用putString方法
(3)调用apply方法将添加的数据提交,从而完成数据存储操作
从SharedPreferences中读取数据
从SharedPreferences文件中读取数据更加简单。SharedPreferences对象中提供了一系列的get方法,用于对存储数据进行读取,每种get方法都接受两个参数,第一个参数是键,传入存储数据时使用的键就可以得到相应的值了;第二个参数是默认值,即表示当传入的键找不到对应的值会以什么样的默认值进行返回。
实现记住密码功能
首先在oncreate方法中获取到了SharedPreference对象,然后调用它的getBoolean方法去获取remember_password这个键对应的值。一开始当然不存在对应的值,所以会使用默认值false,这样就什么都不会发生。接着再登陆成功之后,会调用CheckBos的isChecked方法俩检查复选框是否被选中,如果被选中了,则表示用户想要记住密码,这时将remeber_password设置为true,然后把account和password对应的值都存入到SharedPreferences文件当中并提交。如果没有被选中,就简单的调用一下clear方法,将SharedPreferences文件中的数据全部清除掉。
AndroidSQLite数据库存储
SQLiteOpenHelper帮助类,它是一个抽象类,有两个抽象方法,分别是onCreate和onUpgrade,我们必须在自己的帮助类中重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
SQLiteOpenHelper中还有两个非常重要的实例方法:getReadableDatabase和getWritableDatabase。这两个方法都可以创建或打开一个现有的数据库(如果数据库已经存在则直接打开,否则创建一个新的数据),并返回一个可对数据库进行读写操作的对线。不同的是,当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase方法返回的对象以只读的方式打开数据库,而getW方法则将出现异常。SQLiteOH中有两个构造方法可供重写,一般使用参数少一点的那个构造方法。这个构造方法接受四个参数,第一个是Context。第二个是数据库名。第三个是允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。构建出SQLOH的实例后,在调用gRD或者gWD方法就可以创建数据库了,数据库文件会存放在
目录下。此时重写的onCreate方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。
例子:
新建DatabaseTest项目,创建BookStore.db数据库,在数据库中新建Book表,id、作者、价格、页数、书名等列。表的创建语句为
需要在代码中去执行sql语句,新建MydatabaseH去继承SQLOH
在布局中新建一个Btn,用于创建数据库,修改活动中的代码
然后我们使用adb shell来对数据和表的创建情况进行检查。
adb是Android SDK中自带的一个调试工具,使用这个工具可以直接对链接在电脑上的手机或者模拟器进行调试操作。它存放在sdk的platform-tools目录下,如果想要在命令行中使用这个工具,就需要先把它的路径配置到环境变量里。Path中哦,把platform-tools目录配置进去。环境变量配置好后,就可以使用adb工具了。打开命令行界面,输入adb shell,就会进入到设备的控制台,然后使用cd命令进入到/data/data/com.example.databasetest/databases/目录下,并使用ls命令查看到该目录里的文件,这个目录下出现了两个数据库文件,一个是我们创建的BS.db,而另一个BookStore.db-journal则是为了让数据库能够支持事务而产生的临时日志文件,通常情况下这个文件的大小都是0字节。
接下来使用sqlite命令打开数据库,只需要键入sqlite3,后面加上数据库名即可。然后对表进行管理,先看下目录中有哪些表,键入.table命令。此时数据库中有两张表,android_metadata表是每个数据库中都会自动生成的,不用管,另一张Book表就是我们在MyDatabaseHelper中创建的了。这里还可以通过**.schema命令来查看他们的建表语句。之后键入.exit或者.quit命令可以退出数据库的编辑,再键入exit命令**就可以推出设备控制台了。
升级数据库
MyDatabaseHelper中还有一个空方法,onUpgrade方法是对于数据库进行升级的,他在整个数据库管理工作当中起着非常重要的作用。
目前DatabaseTest项目中已经有一张Book表用于存放书中的各种详细数据,我们还想再添加一张Category表用于记录图书的分类。
建表语句
但现在表并没有创建成功,原因是,此时BS数据库已经存在了,之后不管我们怎么点击按钮,MydatabaseHelper中的onCreate方法都不会再次执行,因此新添加的表也就无法得到创建了。解决这个问题的方法只需要先将程序卸载掉,然后重新运行,这时BookStore.db数据库已经不存在了,再点击然后调用oncreate方法,category表就可以创建成功了。不过通过卸载程序的方式来新增一张表毫无疑问是很极端的做法,其实我们只需要巧妙的运用SQLiteOpenHelper的升级功能就可以轻松解决这个问题,修改MydatabaseHelper中的方法。
drop语句,是如果发现数据库中已经存在Book表或Category表了,就将这两张表删除掉,然后再调用onCreate方法重新创建。这里先将已经存在的表删除掉,因为如果在创建表时发现这张表已经存在了,就会直接报错。接下来的问题时如何让onUpgrade方法能够执行,SQLiteSOpenHepler的构造方法中第四个参数,他表示数据库的版本号,之前我们传入的是1,现在只要传入一个比1大的数,就可以让onUpgrade方法得到执行了。修改活动中的代码。
添加数据,四种操作CRU,分别是添加Create,查询Retrieve,更新Update,删除delete。每一种操作又各自对应了一种SQL命令。getReadableDatabase或者getWritabledatabase方法是可以用于创建和升级数据库的,不仅如此,这两个方法还都会返回一个SQLiteDatabase对象,借助这个对象就可以对数据进行CRUD操作了。SQLiteDatabase中提供了一个insert方法,用来添加数据。它接受三个参数,第一个参数是表名,第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般我们用不到这个功能,直接传入null。第三个参数是一个ContentValues对象,它提供了一系列put方法重载,用于向ContenValues中添加数据,只需要将表中每个列明以及相应的带添加数据传入即可。
例子
还是新建一个bun用来写添加数据的逻辑。
修改活动中代码
这里只对Book表里其中四列的数据进行了组装,id那一列并没给它赋值。这是因为在前面创建表的时候,我们将id设置为自增长了,它的值会在入库的时候自动生成,所以不需要手动赋值。
更新数据:update方法,有四个参数,第一个参数是表名,第二个参数是ContenValues对象,要把更新数据在这里组装进去。第三第四个参数用于约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。
第三个参数对应的是SQL语句中的where部分,表示更新所有name等于?的行,而?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。
删除数据,delete,第一个参数是表明,第二、第三个参数是用于约束删除某一行或者某几行的数据,不指定默认所有行。
这里的意思是删除页数大于500的书。
查询数据,query方法,最短一个方法重载也需要七个参数,第一个是表明,第二个是指定去查询哪几列,如果不指定则则默认查询所有列。第三第四个约束用于查询某一行或者某几行的数据,不指定则默认查询所有行。第五个参数指定需要去group by的列,不指定则表示不对查询结果进行group by操作。第六个参数用于对group by之后的数据进行进一步的过滤,不指定则表示不过滤,。第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。
参数很多,但多数情况下只需要传入少数几个参数就可以完成查询操作了。调用query方法后会返回一个Cursor对象,查询到的所有数据都将从这个对象中取出。
moveToFirst方法将数据的指针移动到第一行的位置,然后进入了一个循环当中,去遍历查询到的每一行数据。这个循环可以通过Cursor的getColumnIndex方法获取到某一列在表中对应的位置索引。
下面列出这几种操作的代码。
1、SQLiteDataBase对象的query()接口
public Cursor query (String table, String[] columns, String selection, String[] selectionArgs,String groupBy, String having,String orderBy,String limit)
示例:
ContentValues cv = new ContentValues();
String[] args = {
String.valueOf("a")};
query("user",new String[] {
"username","password" },"username=?", args,null,null,null, null);
2、SQLiteDataBase对象的insert()接口:
publiclong insert (String table, String nullColumnHack, ContentValues values)
示例:
ContentValues cv = new ContentValues();
cv.put("username", "lanxiaofang");
cv.put("password", "123456");
insert("user", null,cv);
3、SQLiteDataBase对象的update()接口:
publicint update (String table, ContentValues values, String whereClause, String[] whereArgs)
示例:
ContentValues cv = new ContentValues();
cv.put("username", "lanxiao");
cv.put("password", "123456");
String[] args = {
String.valueOf("lanxiaofang")};
update("user",cv, "username=?",args)
4、SQLiteDataBase对象的delete()接口:
publicint delete (String table, String whereClause, String[] whereArgs)
示例:
ContentValues cv = new ContentValues();
String[] args = {
String.valueOf("lanxiaofang")};
delete("user", "username=?",args);
使用LitePal操作数据库
LitePal是一款开源的Android数据库框架,它采用了对象关系映射(ORM)的模式,并将我们平时开发最常用到的一些数据库功能进行了封装,使得不用编写一行SQL语句就可以完成各种建表和增删改查的操作。LitePal的项目主页上也有详细的使用文档,地址是:https://github.com/LitePalFramework/LitePal。
配置LitePal:
通常使用开源库需要下载开源库的jar包或者源码,再集成到我们的项目当中去。现在大多的开源项目都会将版本提交到jcenter上,我们只需要app/build.gradle文件中声明该开源库的引用就可以了。在denpendencies闭包中添加如下内容
1.3.2是版本号的意思,最新的版本号可以到LitePal的项目主页上去查看。
这样就成功把LitePal成功引入到当前项目中了,接下来需要配置litepal.xml文件。右击
创建一个assets目录,然后再assets目录下再新建一个litepal.xml文件,接着编辑litepal.xml文件中的内容,如下
其中标签用于指定数据库名,version标签用于指定数据库版本号,list标签用于指定所有的映射模式。
最后需要配置一下LitePalApplication,修改AndroidMainfest.xml中的代码,如下
这里将项目的application配置为org.litepal.LitePalApplication,这样才能让LitePal的所有功能都可以正常工作。关于application先知道必须这么写就可以了。
创建和升级数据库
之前创建数据库时通过自定义一个类继承SQLiteOpenHelper,然后在onCreate方法中编写建表语句来实现的,而使用LitePal就不用这么麻烦了。
LitePal采取的是对象关系映射(ORM)的模式,简单点说,我们使用的编程语言时面向对象语言,而使用的数据库则是关系型数据库,那么将面向对象的语言和面向关系的数据库之间建立一种映射关系,这就是对象关系映射了。它赋予了我们一个强大的功能,就是可以用面向对象的思维来操作数据库,而不再和SQL语句大交到了。
比如我们创建Book表,需要先分析表中应该包含哪些列,然后再编写出一条建表语句,最后在自定义的SQLiteOpenHelper中去执行这条建表语句。但是使用LitePal,你就可以用面向对象的思维来实现同样的功能了,定义一个Book类,如下
这是一个典型的java bean。
JavaBean是一个遵循特定写法的Java类,它通常具有如下特点: 这个Java类必须具有一个无参的构造函数 属性必须私有化。
私有化的属性必须通过public类型的方法暴露给其它程序,并且方法的命名也必须遵守一定的命名规范。
Book类就会对应数据库中的Book表,而类中的每一个字段分别对应了表中的每一个列,这就是对象关系映射最直观的体验。
接下来我们还需要将Book类添加到映射模型列表当中,修改litepal.xml中的代码。
这里使用mapping标签来声明我们要配置的映射模型类,注意一定要使用完整的类名。不管有多少模型类需要映射,都使用同样的方式配置在list标签下即可。
现在修改活动中的代码
其中,调用Connector.getDatabase方法就是一次最简单的数据库操作,只要点击按钮数据库就自动创建完成了。可以通过adb shell来查看一下数据库创建的情况。使用sqlite3命令打开BookStore.db文件,再使用.schema命令来查看建表语句。这里有三张表的建表语句,其中android_metadata表仍然不用管,table_schema表时LitePal内部使用的,我们也可以直接忽视,book表就是我们定义的Book类以及类中的字段来自动生成的。
之前使用SQLiteOpenHelper来升级数据库的方式虽然说功能实现了,但是升级数据库的时候需要把之前的表drop掉,然后再重新创建才行。这其实是一个非常严重的问题,因为这样会造成数据丢失,每当升级一次数据库,之前表中的数据就全都没了。需要通过复杂的逻辑控制来避免这种情况,但是维护成本很高。而有了LitePal,这些就都不再是问题了,使用LitePal来升级数据库非常简单,只需要修改你想改的内容,然后将版本号加1就可以啦。
比如我们想要向Book表中添加一个press(出版社)列,直接修改Book类中的代码,添加一个press字段即可,如下
与此同时,如果想添加一张Category表,那么只需要新建一个Category类就可以了,代码如下
改完了所有想改的东西,只需要记得将版本号加一就可以了。当然由于这里还添加了一个新的模型类,因此也需要将它添加到映射模型列表中。修改litepal.xml中的代码,如下
这样就成功啦。
使用LitePal添加数据
回顾一下之前添加数据的方法,我们需要创建出一个ContentValues对象,然后将所有要添加的数据put到这个ContenValues对象当中,最后再调用SQLiteDatabase的insert方法将数据添加到数据库表当中。而使用LitePal只需要创建出模型类的实例,再将所有要存储的数据设置好,最后调用一下save方法就可以了。
下面开始实现,观察现有的模型类,他们都没有继承结构。LitePal进行表管理操作时不需要模型类有任何的继承结构,但是进行CRUD操作时就不行了,必须要继承自DataSupport类才行,因此这里我们需要先把继承结构给加上。修改Book类中的代码。
接着开始向Book表中添加数据,修改MainActivity中的代码
这个save方法是从DataSupport中继承来的。除了save方法,DataSupport类还给我提供了丰富的CRUD方法。
更新数据
最简单的一种更新方式就是对已经存储的对象重新设值,然后重新调用save方法即可。那么一寸处的对象,对于LitePal来说,对象是否已存储就是根据调用model.isSaved方法的结果来判断的,返回true就表示已经存是,返回false就是为存储。那么什么时候会返回true或false呢。
实际上只有在两种情况下model.isSaved方法才会返回true,一种情况是已经调用过model.save方法去添加数据了,此时model会被认为是已经存储的对象。另一种情况是model对象是通过LitePal提供的查询API查出来的,由于是从数据库中查到的对象,因此也会被认为是已经存储的对象
更新例子:
先new出一个实例,再调用方法来设置要更新的数据,最后调用updateAll方法去执行更新操作。注意updateAll方法中可以指定一个条件约束,和SQLiteDatabase中update方法的where参数部分有点类似,但更加简洁,如果不指定则默认所有。
在使用updateAll方式,当你想吧一个字段的值更新成默认值时,是不可以使用上面的方式来set数据的。我们都知道,在java中任何一种数据类型的字段都会有默认值,例如int默认值是0 。new出一个Book对象时,其实所有字段都已经被初始化成默认值了,因此如果我们想吧数据库表中的pages列更新成0,直接调用book.setPages(0)这个方法是不可以的,因为即使不调用这行代码,pages字段本身也是0,LitePal此时是不会对这个列进行更新的。对于所有想要将为数据更新成默认值的操作,LitePal同意提供了一个setToDefault方法,然后传入相应的列名就可以实现。
使用LitePal删除数据
这里调用了deleteAll方法来删除数据,其中第一个参数用于指定删除那张表的数据,Book.class就是删除Book表中的数据,后面的参数用于指定约束条件。
查询
查询表中第一条数据
查询最后一条数据
同时LitePal爷支持原生的SQL来进行查询。