Android 系统主要提供了三种方式用于简单地实现数据持久化功能,即文件存储、SharedPreference 存储以及数据库存储。
一. 文件存储
文件存储不对存储的内容进行任何的格式化处理,所有数据都是原封不动地保存到文件当中的,因此比较适合用于存储一些简单的文本数据或二进制数据。
1. 存储
(1)首先通过openFileOutput()
方法得到一个 FileOutputStream 对象,该方法要传入两个参数,文件名和写入方式 Content.MODE_PRIVATE、Content.MODE_APPEND;
(2)然后借助 FileOutputStream 对象,构建出一个 OutputStreamWriter 对象;
(3)再借助 OutputStreamWriter 对象,构建出一个 BufferedWriter 对象;
(4)最后可以通过 BufferedWriter 对象的write()
方法将文本内容写入到文件中了;
(5)写入完毕要调用close()
关闭 BufferedWriter 对象。
private EditText edit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = (EditText) findViewById(R.id.edit_text);
}
@Override
protected void onDestroy(){
super.onDestroy();
String inputText = edit.getText().toString();
save(inputText);//退出前保存输入的文本
}
public void save(String inputText){
FileOutputStream fileOutputStream = null;
BufferedWriter bufferedWriter = null;
try{
fileOutputStream = openFileOutput("data",Context.MODE_PRIVATE);//该方法返回一个FileOutputStream对象
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);//传入fileOutputStream得到一个OutputStreamWriter对象
bufferedWriter = new BufferedWriter(outputStreamWriter);//传入outputStreamWriter得到一个BufferedWriter对象
bufferedWriter.write(inputText);//将文本写入文件
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if(bufferedWriter != null){
bufferedWriter.close();//关闭BufferedWriter
}
}catch (Exception e){
e.printStackTrace();
}
}
}
2. 读取
(1)首先通过openFileInput()
方法得到一个 FileInputStream 对象,该方法只要传入一个参数,文件名;
(2)然后借助 FileInputStream 对象,构建出一个 InputStreamReader 对象;
(3)再借助 InputStreamReader 对象,构建出一个 BufferedReader 对象;
(4)最后可以通过 BufferedReader 对象的readLine()
方法读取文件中的文本;
(5)读取完毕要调用close()
关闭 BufferedReader 对象。
private EditText edit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = (EditText) findViewById(R.id.edit_text);
String inputText = load();//打开程序就恢复数据
if(!TextUtils.isEmpty(inputText)){//TextUtils.isEmpty(text)可以判断为空字符或null,当传入的参数为上述两种情况都会返回true
edit.setText(inputText);//读取文本放到EditView上
edit.setSelection(inputText.length());//将输入光标放置到文本末尾
Toast.makeText(this,"恢复成功",Toast.LENGTH_SHORT).show();
}
}
public String load(){
FileInputStream fileInputStream = null;
BufferedReader bufferedReader = null;
StringBuilder content = new StringBuilder();
try{
fileInputStream = openFileInput("data");
bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
String line = "" ;
while((line = bufferedReader.readLine()) != null){
content.append(line);//将内容都读取出来并存放在StringBuilder对象中
}
}catch(Exception ex){
ex.printStackTrace();
}finally {
try{
if(bufferedReader != null){
bufferedReader.close();
}
}catch (Exception ex){
ex.printStackTrace();
}
}
return content.toString();
}
二. SharedPreferences 存储
SharedPreferences 是使用键值对的方式来存储数据的。
1. 存储
(1)首先要获取 SharedPreferences 对象,主要有三种方法:
- Context 类中的
getSharedPreferences()
方法,它接收两个参数,文件名称和操作模式(MODE_PRIVATE:表示只有当前的应用程序才可以对 SharedPreferences 文件读写; MODE_MULTI_PROCESS:表示多个进程对同一个 SharedPreferences 文件进行读写。) - Activity 类中的
getPreferences()
方法,它接收一个参数,操作模式,它会自动将当前活动的类名作为文件名。 - PreferenceManager 类中的
gerDefaultSharedPreferences()
方法,这是一个静态方法,接收一个 Context 参数,并且自动使用当前应用程序的包名作为前缀来命名文件。
(2)调用 SharedPreferences 对象的edit()
方法来获取一个 SharedPreferences.Edit 对象。
(3)向 SharedPreferences.Editor 对象中添加数据,putString()
、putBoolean()
等。
(4)调用commit()
将添加的数据提交。
private Button saveData;
...
saveData = (Button) findViewById(R.id.save);
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.commit();
2. 读取
(1)首先,获取 SharedPreferences 对象;
(2)然后,利用getString()
、getInt()
等获取数据。传入两个参数,一是 key,二是默认值,即如果找不到该 key,则返回该默认值。
private Button loadData;
...
loadData = (Button) findViewById(R.id.load);
loadData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences sharedPreferences = getSharedPreferences("data", MODE_PRIVATE);
String name = sharedPreferences.getString("name", "");
int age = sharedPreferences.getInt("age", 0);
boolean married = sharedPreferences.getBoolean("married", false);
Log.d("2333", "name is " + name);
Log.d("2333", "age is " + age);
Log.d("2333", "married is " + married);
}
});
三. SQLite
SQL : Structured Query Language 结构化查询语言
借助 SQLiteOpenHelper 帮助类可以非常简单地对数据库进行创建和升级。SQLiteOpenHelper 是个抽象类,需要重写两个抽象方法,分别是onCreate()
和onUpgrade()
;它还有两个非常重要的实例方法,getReadableDatabase()
和getWritableDatabase()
,这两个方法都可以创建或者打开一个现有的数据库(没有的话就新建),并返回一个可对数据库进行读写操作的对象。
SQLiteOpenHelper 中有两个构造方法可供重写。一般使用参数少一点的方法,该方法接收四个参数:Context,数据库名,Cursor(一般为null)和 version 版本号。
1. 创建数据库 create
(1)新建 MyDatabaseHelper 类继承 SQLiteOpenHelper,并重写onCreate()
、onUpgrade()
和构造方法。
(2)在onCreate()
中调用 SQLiteDatebase 的execSQL()
方法去执行这条建表语句。
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, "创建成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
(3)在 MainActivity 中调用 MyDatabaseHelper 对象的getReadableDatabase()
或getWritableDatabase()
创建出数据库。因为检测到并没有 BookStore.db 这个数据库,于是会创建该数据库并调用 MyDatabaseHelper 中的onCreate()
方法,这样 Book 表也得到创建。
private MyDatabaseHelper helper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
helper = new MyDatabaseHelper(this,"BookStore.db",null,1);
Button create = (Button) findViewById(R.id.create);
create.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
helper.getWritableDatabase();
}
});
}
2. 升级数据库 upgrade
注意:
(1)当相同版本的数据库 BookStore 已经存在了,则 MyDatabaseHelper 中的onCreate()
方法就不会执行了。
(2)如果创建表时发现该表已经存在,则会直接报错,所以需要将已经存在的表删除。
(3)只有当版本号比原来高,onUpdate()
方法才会执行。
helper = new MyDatabaseHelper(this, “BookStore.db”, null, 2);
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "创建成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists Book");//两条DROP语句,发现存在Book或Category表,就将它们删除
db.execSQL("drop table if exists Category");
onCreate(db);
}
3. 添加数据 insert
CRUD:C 代表添加(Create),R 代表查询(Retrieve),U 代表更新(Update),D 代表删除(Delete)。
SQL 语言:添加数据使用 insert,查询数据使用 select,更新数据使用 update,删除数据使用 delete 。
SQLiteDatabase 中提供了一个insert()
方法,这个方法就是专门用于添加数据的,它接收三个参数,第一个表名(向哪添加数据);第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般用不上这个功能,直接传入null;第三个是一个 ContentValues 对象,它提供了一系列的put()
方法重载,用于向 ContentValues 中添加数据。
(1)先获取 SQLiteDatabase 对象,然后使用 ContentValues 来对要添加的数据进行组装。
(2)接下来调用insert()
方法将数据添加到表中。
Button add = (Button) findViewById(R.id.add);
add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
//开始组装第一条数据
values.put("name","达芬奇");
values.put("author","Dan Brown");
values.put("pages",454);
values.put("price",16.96);
db.insert("Book",null,values);//插入第一条数据
values.clear();
//开始组装第二条数据
values.put("name","The Lost Symbol");
values.put("author","Dan Brown");
values.put("pages",510);
values.put("price",19.95);
db.insert("Book",null,values);//插入第二条数据
}
});
4. 更新数据 update
SQLiteDatabase 中的update()
方法接收四个参数:第一个表名,指定去更新哪张表的数据;第二个参数是 ContentValues 对象,把更新数据组装进去;第三个、第四个参数用于约束更新某一行或几行中的数据,不指定的话默认更新所有行。
(1)在按钮点击事件里构建一个 ContentValues 对象,并给它指定一组数据。
(2)然后调用update()
方法去执行具体的更新操作。
Button update = (Button) findViewById(R.id.update);
update.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price",10.99);
db.update("Book",values,"name=?",new String[]{"达芬奇"});
}//第三个参数对应的是SQL语句的where部分,表示去更新所有name等于?的行
//?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每一个占位符指定相应的内容
//因此,上述代码表达的意图是,将名字是 达芬奇 的这本书的价格改成 10.99
});
5. 删除数据 delete
SQLiteDatabase 中的delete()
方法接收三个参数,第一个参数是表名;第二、第三个参数又是用于去约束删除某一行或几行的数据,不指定的话默认是删除所有行。
Button delete = (Button) findViewById(R.id.delete);
delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = helper.getWritableDatabase();
db.delete("Book","pages > ?",new String[]{"500"});
}//意思是,删除Book表中页数大于500的数据
});
6. 查询数据 query
query() 方法参数 | 对应 SQL 部分 | 描述 |
---|---|---|
table | from table_name | 指定查询的表名 |
columns | select column1,column2 | 指定查询的列名 |
selection | where column = value | 指定 where 的约束条件 |
selectionArgs | - | 为 where 中的占位符提供具体的值 |
groupBy | group by column | 指定需要 group by 的列 |
having | having column = value | 对 group by 后的结果进一步约束 |
orderBy | order by column1,column2 | 指定查询结果的排序方式 |
(1)调用 SQLiteDatabase 的query()
方法去查询数据。
(2)查询完得到一个 Cursor 对象,这个对象是指向第一条记录之前的。接着调用它的moveToFirst()
方法将数据的指针移动到第一行的位置,然后进入循环,去遍历查询到的每一行数据。在这个循环中通过 Cursor 的getColumnIndex()
方法获取到某一列在表中对应的位置索引,然后传入到相应的取值方法中,就可以得到从数据库中读取到的数据了。
Button query = (Button) findViewById(R.id.query);
query.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = helper.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"));
String author = cursor.getString(cursor.getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
double price = cursor.getDouble(cursor.getColumnIndex("price"));
Log.d("233333","name is " + name);
Log.d("233333","author is " + author);
Log.d("233333","pages is " + pages);
Log.d("233333","price is " + price);
}while(cursor.moveToNext());//为false时跳出循环
}
cursor.close();
}
});
7. 使用 SQL 操作数据库
除了查询数据是用 SQLiteDatabase 的rawQuery()
方法,其他的操作都是调用的execSQL()
方法。
- 添加数据:
db.execSQL("insert into Book(name,author,pages,price) values(?,?,?,?)",new String[]{"The Lost Symbol","Dan Brown","510","19.95"});
- 更 新 数 据 :
db.execSQL("update Book set price=? where name=?", new String[]{"10.99","达芬奇密码"});
- 删除数据:
db.execSQL("delete from Book where pages>?",new String[] {"500"});
- 查询数据:
db.rawQuery("select * from Book",null);
查询数据方法:
首先,获取 Cursor:
Cursor cursor=null;
cursor=db.rawQuery("select * from Book", null);
然后,利用 cursor 得到返回的数据:
while(cursor.moveToNext())
{
String author=cursor.getString(cursor.getColumnIndex("author"));
String name=cursor.getString(cursor.getColumnIndex("name"));
double price=cursor.getDouble(cursor.getColumnIndex("price"));
int pages=cursor.getInt(cursor.getColumnIndex("pages"));
Log.i("main", author);
Log.i("main", name);
Log.i("main", price+"");
Log.i("main", pages+"");
}
四. 最佳实践
1. 使用事务
事务操作可以保证某一系列的操作要么全部完成,要么全部都不完成,保证安全。
当执行操作出现故障时,事务不会成功,则以上的操作不会完成,数据库不会变化。
(1)开启事务: db.beginTransaction();
(2)执行数据库操作
(3)事务成功:db.setTransactionSuccessful();
(4)结束事务
以下就是 Android 中事务的标准用法:
Button replace = (Button) findViewById(R.id.replace);
replace.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = helper.getWritableDatabase();
db.beginTransaction();//开启事务
try{
db.delete("Book",null,null);
if(true){
throw new NullPointerException();//手动抛出一个异常,模仿事务失败
}
ContentValues values = new ContentValues();
values.put("name","权力的游戏");
values.put("author","George Martin");
values.put("pages",720);
values.put("price",20.85);
db.insert("Book",null,values);
db.setTransactionSuccessful();//事务已经执行成功
}catch(Exception ex){
ex.printStackTrace();
}finally{
db.endTransaction();//结束事务
}
}
});
删除掉手动抛出异常那行代码,再重新运行一下程序,此时点击按钮数据就会被替换成新数据了。
2. 升级数据库最佳写法
前面学习升级数据库的方式太粗暴,为了保证数据库中的表是最新的,就删除掉了当前所有的表,然后又重新创建表,真的很傻。
其实应该这么做:每一个数据库版本都会对应一个版本号,当指定的数据库版本号大于当前数据库版本号的时候,就会进入到onUpgrade()
方法中去执行更新操作。这里需要为每一个版本号赋予它各自改变的内容,然后在onUpgrade()
中添加一个 switch 语句,对当前数据库的版本号进行判断,再执行相应的改变。
注意:switch 中每一个 case 的最后都是没有使用 break 的,这是为了保证在跨版本升级的时候,每一次的数据库修改都能被全部执行到。
databaseHelper = new MyDatabaseHelper(this, "BookStore", null, 1);
比如 app 一共更新了两次,第一次添加了 table Category,第二次在 table Book 中添加 category_id.
于是,就有三个版本:
版本号 | 数据操作 |
---|---|
version = 1 | 建数据库,添加 table Book |
version = 2 | 添加 table Category |
version = 3 | 在 table Book 中添加 category_id |
主程序中: databaseHelper = new MyDatabaseHelper(this, "BookStore", null, 3);
将版本号改为 3:
如果用户是第一次安装,则直接进入onCreate()
,直接安装最新版本;
如果用户版本号是 1,则进入onUpgrade()
, 连续进行了两次升级;
如果用户版本号是 2,则进入onUpgrade()
,只进行了最后一次升级。