本篇主要涉及Android中的数据持久化技术。
一、 文件存储
不对存储内容进行任何格式化处理,原封不动的保存到文件中。适合存储一些简单的文本数据或二进制数据。
(一) 存储
1. 获取FileOutputStream对象:使用Context类中的 openFileOutput()
方法,接收两个参数:(文件名, 操作模式)。
- 文件默认存储到
/data/data/<package name>/files/
目录下; - 两种操作模式可选:MODE_PRIVATE(默认)和MODE_APPEND 。
2. 构建出OutputStreamWriter对象,再借此构建出BufferedWriter对象,调用它的 write
方法将数据写入文件。
public void save(String inputText) {
FileOutputStream out = null;
BufferedWriter writer = null;
try {
out =openFileOutput("data", Context.MODE_PRIVATE); // 获取FileOutputStream对象
writer = new BufferedWriter(new OutputStreamWriter(out); // 构建BufferedWriter对象
writer.write(inputText);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close()
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(二) 读取
1. 获取FileInputStream对象:使用Context类中的 openFileInput(文件名)
方法。
2. 构建出InputStreamReader,再借此构建出BufferedReader对象,读取到StringBuilder中,最后返回。
public String load() {
FileInputStream in = null;
BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try {
in = openFileInput("data"); // 获取FileInputStream对象
reader = new BufferedReader(new InputStreamReader(in)); // 构建BufferedReader对象
String line = "";
while ((line = reader.readLine()) != null) {
content.append(line); // 逐行读取,若内容不为空添加到StringBuilder中
}
} catch (IOException e) {
e.printStackTrace;
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return content.toString();
}
二、 SharedPreference存储
采用键值对的方式存储数据,支持多种不同的数据类型存储,使用XML格式来管理数据的。
(一) 存储
1. 获取SharedPreferences对象,有三种方法:
- Context类中的
getSharePregerences(文件名, 操作模式)
方法。
- SharedPregerences文件都存放在
/data/data/<package name>/shared_prefs/
目录下 - 操作模式只有MODE_PRIVATE可选,和传入0效果一样
- SharedPregerences文件都存放在
- Activity类中的
getPreferences(操作模式)
方法。
- 只接受一个操作模式参数,自动将当前活动类名作为文件名
- PreferenceManager类中的
getDeafaultSharedPreferences(context)
方法。
- 是一个静态方法,接收一个context参数,使用当前应用程序包名作为前缀来命名文件
2. 向SharedPreferences文件中存储数据
(1) 调用SharedPreferences对象的 edit()
方法来获取一个SharedPreferences.Editor对象;
(2) 向Editor对象中添加数据,根据数据类型调用 putString()
、putBoolean()
等方法;
(3) 调用 apply()
方法提交数据。
SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
editor.putString("name", "Tom");
editor.putInt("age", 28);
editor.putBoolean("married", false);
editor.apply();
(二) 读取
使用 getSring()
、getBoolean()
等方法,接收两个参数:(键, 默认值)。
SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
String name = pref.getString("name", "");
int age = pref.getInt("age", 0);
boolean married = pref.getBoolean("married", false);
Log.d("MainActivity", "name is " + name);
Log.d("MainActivity", "age is " + age);
Log.d("MainActivity", "married is " + married);
三、 SQLite数据库存储
适合存储大量复杂的关系型数据。
(一) 创建数据库
- 借助抽象类SQLiteOpenHelper,继承此类后重写
onCreate()
、onUpgrade()
方法来创建和升级数据库。 - SQLiteOpenHelper的构造方法接收4个参数:(Context, 数据库名, null, 版本号)。第三个参数允许在查询数据时返回一个自定义Cursor,一般传入null。
- 两个实例方法
getReadableDatabase()
、getWritableDatabase()
用于创建或打开现有数据库,并返回一个可进行读写操作的对象。 - 数据库文件存放在
/data/data/<package name>/databases/
目录下。
public class MyDatabaseOpenHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table BOOK ("
+ "id integer primary key antoincrement, " // 设置为主键,自增长
+ "author text" // text表示文本类型
+ "price real, " // real表示浮点型
+ "pages integer, " // 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 succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
public class MainActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
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) {
dbHelper.getWritableDatabase();
}
});
}
}
(二) 升级数据库
1. 将建表语句添加到MyDatabaseHelper中;
2. onUpgrade()
中执行DROP语句,再调用 onCreate()
方法重新创建;
3. 修改MainActivity中构造方法的版本号,使 onUpgrade()
能够执行。
@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);
}
(三) 添加数据
使用SQLiteDatabase的 insert()
方法,接收三个参数:(要添加数据的表名, null, ContentValues对象)
- 第二个参数用于在未指定数据的情况下给某些为空的列自动赋值NULL,一般用不到直接传入null;
- 第三个参数的ContentValues对象提供一系列
put()
方法重载用于向其中添加数据,传入 (列名, 待添加数据) 即可。
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); // 插入第一条数据
values.clear();
// 开始组装第二条数据
...
db.insert("Book", null, valuse); // 插入第二条数据
(四) 更新数据
使用 update()
方法,接收四个参数:(表名, ContentValues对象, 约束语句, 字符串数组)。
- 第二个参数中把要更新的数据组装进来;
- 第三个参数用于约束更新某一行或某几行的数据,对应SQL语句的where部分,包含占位符,例如”name = ?”;
- 第四个参数用一个字符串数组指定占位符的内容,如
new String[] {"The Da Vinci Code"}
。
SQLiteDatabase db = dbHelper.getWritaberDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
db.update("Book", values, "name = ?", new String[] {"The Da Vinci Code"}); // 更新书名为"The Da Vinci Code"的数据
(五) 删除数据
使用 delete()
方法,接收三个参数:(表名, 约束语句, 字符串数组)。
- 和insert类似,第二三个参数用于约束删除某一行或某几行数据。
db.delete("Book", "pages > ?", new String[] {"500"}); // 删除页数超过500的书
(六) 查询数据
1. 使用 query()
方法,最短的方法重载接收七个参数;
2. 查询完得到一个Cursor对象,调用 moveToFirst()
将数据指针移动到第一行位置(返回True表示移动成功数据不为空);
3. 通过一个循环遍历查询到的每一行数据,循环中可以通过Cursor的 getColumnIndex()
方法获取到某一列在表中对应的位置索引,将其传入对应取值方法中就可以读取到数据了;
4. 最后调用 colse()
方法关闭Cursor。
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 | 指定查询结果的排序方式 |
// 查询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("MainActivity", "book name is " + name);
Log.d("MainActivity", "book author is " + author);
Log.d("MainActivity", "book pages is " + pages);
Log.d("MainActivity", "book price is " + price);
} while (cursor.moveToNext());
}
cursor.close();
(七) 使用SQL操作数据库
- 添加数据
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
new String[] {"The Da Vinci Code", "Dan Brown", "454", "16.96"});
- 更新数据
db.execSQL("update Book set price = ? where name = ?", new String[] {"10.99", "The Da Vinci Code"});
- 删除数据
db.execSQL("delete from Book where pages > ?", new String[] {"500"});
- 查询数据
db.rawQuery("select * from Book", null);
四、 使用LitePal操作数据库
LitePal是一款开源的Android数据库框架,它采用了对象关系映射(ORM)的模式,并对常用数据库功能进行了封装。
(一) 配置LitePal
- 在app/build.grale文件中声明开源库的引用:
compile 'org.litepal.android:core:1.4.1'
- 配置litepal.xml。在app/src/main下创建一个assets目录,在其中创建一个litepal.xml文件,编辑其中内容:
<?xml version = "1.0" encoding="utf-8"?>
<litepal>
<!-- 数据库名 -->
<dbname value="BookStore" ></dbname>
<!-- 数据库版本号 -->
<version value="1" ></version>
<!-- 指定所有映射模型 -->
<list>
</list>
</litepal>
- AndroidManifest中配置LitePalApplication。
<application
android:name="org.litepal.LitePalApplication"
...>
...
</application>
(二) 创建和升级数据库
1.创建数据库
(1) 根据要创建的表定义一个类,定义相应字段并生成对应的getter和setter方法;
(2) 将创建的类添加到映射模型列表中,使用<mapping>
标签;
(3) 调用 Connector.getDatabase()
创建数据库。
/** 根据Book表的内容定义Book类 */
public class Book{
private int id;
private String author;
private double price;
private int pages;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
... //生成各个字段对应的getter和setter方法
}
<!-- 添加到映射模型列表中 -->
<list>
<mapping class="com.example.litepaltest.Book"></mapping>
</list>
@Override
public void onClick(View v) {
Connector.getDatabase(); // 创建数据库
}
2. 升级数据库
更改想要修改的任何内容,然后将版本号加1即可。升级会保留之前表中的所有数据。
(三) 添加数据
创建出模型实例(要进行CRUD操作模型类必须继承字DataSupport类),调用set方法将所有要存储的数据设置好,最后调用 save()
方法即可。
public class Book extends DataSupport {
...
}
Book book = new Book();
book.setName("The Da Vinci Code");
book.setAuthor("Dan Brown");
book.setPages(454);
book.setPrice(16.96);
book.setPress("UnKnow");
book.save():
(四) 更新数据
- new出模型实例,调用set方法设置要更新的数据,最后调用
updateAll()
方法执行更新操作(updateAll中可以指定约束条件)。 - 注意:要更新为默认值,使用
setToDefault()
方法。
/** 将书名为The Lost Symbol并且作者是Dan Brown的书价格更新为14.95,出版社更新为Anchor */
Bool book = new Book();
book.setPrice(14.95);
book.setPress("Anchor");
book.updateAll("name = ? and author = ?", "The Lost Symbol", "Dan Brown");
/** 将所有书页更新为0(默认值) */
Book book = new book;
book.setToDefault("pages");
book.updateAll();
(五) 删除数据
- 调用已存储对象(调用过save方法或通过LitePal提供的查询API查出来的对象)的
delete()
方法。 - 调用
DataSupport.deleteAll()
方法,接收三个参数:(要删除数据的表名, 约束条件, 占位符内容)
/** 删除Book表中价格低于15的书 */
DataSupport.deleteAll(Book.class, "price < ?", "15");
(六) 查询数据
- 调用
findAll()
方法,返回值为对应类型的List集合。
List<Book> books = DataSupport.finAll(Book.class);
for (Book book : books) {
Log.d("MainActivity", "book name is " + book.getName());
Log.d("MainActivity", "book author is " + book.getAuthor());
Log.d("MainActivity", "book pages is " + book.getPages());
Log.d("MainActivity", "book price is " + book.getPrice());
Log.d("MainActivity", "book press is " + book.getPress());
}
findFirst()
、findLast()
分别查询第一条和最后一条数据.- 通过连缀查询定制更多查询功能:
方法 | 对应SQL部分 | 描述 |
---|---|---|
select() | select | 指定查询哪几列数据 |
where() | where | 指定查询的约束条件 |
order() | order by | 指定结果的排序方式(desc降序,asc或不写为升序) |
limit() | limit | 指定查询结果的数量 |
offset() | limit | 指定查询结果的偏移量 |
/** 查询Book表中第11~20条页数大于400的name、author、pages这三列数据,并将结果按页数升序排列 */
List<Book> books = DataSupport.select("name", "author", "pages")
.where("pages > ?", "400")
.order("pages") // 默认为asc升序
.limit(10) // 只查前10条
.offset(10) // 偏移10个位置
.find(Book.class);
- 也支持使用原生SQL进行查询:调用
DataSupport.findBySQL(SQL语句, 占位符的值)
,返回一个Cursor对象。
Cursor c = DataSupport.findBySQL("select * from Book where pages > ? and price < ?", "400", "20");