第一行代码笔记⑤
持久化技术
5.1 文件存储
存储数据:利用java流实现
public class MainActivity extends AppCompatActivity {
private EditText edit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = (EditText)findViewById(R.id.edit);
}
@Override
protected void onDestroy() {
super.onDestroy();
String inputText = edit.getText().toString();
save(inputText);
}
public 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) {
e.printStackTrace();
}finally {
try {
if (writer !=null){
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
存储的默认路径是/data/data//files/
从文件中读取:
public class MainActivity extends AppCompatActivity {
private EditText edit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = (EditText) findViewById(R.id.edit);
String inputText = load();
if (!TextUtils.isEmpty(inputText)) {
edit.setText(inputText);
edit.setSelection(inputText.length()); // 将光标移动到文本的末尾
Toast.makeText(this, "Restoring succeeded",
Toast.LENGTH_SHORT).show();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
String inputText = edit.getText().toString();
save(inputText);
}
public 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) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String load() {
FileInputStream in = null;
BufferedReader reader = null;
StringBuilder context = new StringBuilder();
try {
in = openFileInput("data");
reader = new BufferedReader(new InputStreamReader(in));
String line = "";
while ((line = reader.readLine()) != null) {
context.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return context.toString();
}
}
在onCreate()方法中调用load方法去读取文件中存储的文本内容,如果读到的内容不为null,就调用EditText的setText()方法将内容填充到EditText里,并调用setSelection()方法将输入光标移动到文本的末尾位置以便于继续输入,然后弹出一句还原成功的提示
5.2 SharedPreferences存储
文件存储不适合保存一些较为复杂的数据。不同于文件的存储方式,SharedPreferences是使用键值对的方式来存储数据。
存储的默认路径是/data/data//shared_prefs/
要想使用SharedPreferences来存储数据,首先要得到SharedPreferences的对象,android中提供了三种方法
1、Context类中的getSharedPreferences()方法 第一个参数指定文件名称,第二个参数指定操作模式
2、Acitivity类中的getPreferences()方法 只有一个参数,指定操作模式,会自动将当前或者的类名作为SharedPreferences文件名
3、PreferenceManager类的getDefaultSharedPreferences()方法 ,该方法是一个静态方法,接受一个Context参数
得到SharedPreferences对象以后,就可以向SharedPreferences文件中存储对象了,分为三个步骤实现
1、调用SharedPreferences对象的edit()方法,得到一个SharedPreferences.Editor对象
2、向SharedPreferences.Editor对象中添加数据,putBoolean()、putString()等
3、调用apply()方法将已添加的数据提交,从而完成数据存储操作
@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();
}
});
Button restoreData = (Button) findViewById(R.id.restore_data);
restoreData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
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);
}
});
}
}
5.3 SQLite数据库存储
前面提到的两种存储仍然只是适用于保存一些简单的数据。Android其实内置了数据库。同时Android为了让我们能够方便地管理数据库,专门提供了一个SQLiteOpenHelper帮助类,借助这个类就可以非常简单地对数据库进行创建和升级
存储的默认路径是/data/data//databases/
1 创建数据库
新建类
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(@Nullable Context context, @Nullable String name,
@Nullable 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) {
}
}
修改MainActivity代码
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) {
dbHelper.getWritableDatabase();
}
});
}
}
修改xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/create_database"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Create database" />
</LinearLayout>
运行以后,点击Create database按钮就会创建数据库,使用adb shell去查看,需要注意的是,这里需要将SDK的platform-tools添加到系统变量里面。
2 升级数据库
上面的MyDatabaseHelper中有一个空方法onUpgrade()
如果此时我们再建一张表,onCreate()方法不会再执行,因为此时已经有BookStore.db数据库了。解决办法也很简单,可以直接将程序卸载掉,再重新运行,再创建就可以了,但是每次都通过卸载程序去新增一张表无疑是很极端的做法。使用SQLiteOpenHelper的升级功能就可以轻松的解决这个问题。
修改构造函数,只需要将数据库的版本号,之前是1,改成比1大的数,就会让onUpgrade()方法执行
dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,2);
@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);
}
adb查看结果如下图所示
3 操作数据
操作无非四种,即CRUD。
Create 添加
Retrieve 查询
Update 更新
Delete 删除
添加数据
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
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();
// 开始组装第二条数据
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);
}
});
id那一列我们没有进行封装,因为它是自增键,同时需要注意的是,每个虚拟机启动以后,数据库好像不会丢失,除非你删掉重建一个新的虚拟机。
更新数据
Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price",10.99);
// 第三四个参数,是约束更新的行数,即将名字是The Da Vinci Code的价格改为10.99
db.update("Book",values,"name = ?",new String[]{"The Da Vinci Code"});
}
});
删除数据
Button deleteButton = (Button) findViewById(R.id.delete_data);
deleteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 通过第二、三个参数,去指定删除页数超过500页的书
db.delete("Book","pages>?",new String[]{"500"});
}
});
查询数据
Button queryButton = (Button) findViewById(R.id.query_data);
queryButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 查询Book表中所有的数据
Cursor cursor = db.query("Book", null, null, null, null, null, null);
if (cursor.moveToFirst()) {
do {
// 遍历Cursor对象,取出数据并打印
// 下面这个方法会报红,是因为编译器检查,传入值要大于等于0
// 而getColumnIndex方法返回值是从-1开始的,存在不符合要求的可能性,就报错
// 可以使用注解忽视警告
@SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex("name"));
@SuppressLint("Range") String author = cursor.getString(cursor.getColumnIndex("author"));
@SuppressLint("Range") int pages = cursor.getInt(cursor.getColumnIndex("pages"));
@SuppressLint("Range") 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操作数据库
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 添加数据
db.execSQL("insert into Book(name,author,pages,price) values(?,?,?,?)", new String[]{"The Da Vinci Code", "Dan Brown", "454", "14.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);
5.4 使用LitePal操作数据库
LitePal是一款开源的Android数据库框架,它采用了对象关系映射的模式,将我们平时开发最常用到的一些数据库功能进行了封装,使得不用编写一行sql语句就能完成各种建表和增删改查操作。项目地址:https://github.com/LitePalFramework/Litepal
配置起来稍微麻烦一点
1、创建和升级数据库
首先需要先下载本地jar包
https://gitcode.net/mirrors/litepalframework/litepal/-/tree/master/downloads
接着在AndroidManifest.xml中配置
android:name="org.litepal.LitePalApplication"
最后main目录下新建assets目录和litepal.xml
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="BookStore"></dbname>
<version value="1"></version>
<list>
<mapping class="com.example.litepaltest.Book" />
</list>
</litepal>
定义Book类
package com.example.litepaltest;
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;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getPages() {
return pages;
}
public void setPages(int pages) {
this.pages = pages;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
修改MaActivity
package com.example.litepaltest;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import org.litepal.LitePal;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button createDatabase = (Button) findViewById(R.id.create_database);
createDatabase.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LitePal.getDatabase();
}
});
}
}
是不是很神奇!
上一节我们使用SQLiteOpenHelper来升级数据库的时候,虽然功能是实现了,但是每次在升级数据库的时候,都需要把之前的表drop掉,然后再重新创建才行。这是一个非常严重的问题,这会造成数据丢失,每当升级一次数据库,之前表中的数据就没了。
而有了LitePal,去升级数据库就非常简单,只需要将版本号加1就行。
比如Book类加一个press字段,与此同时我们再添加一张Category表
修改litepal.xml文件
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="BookStore"></dbname>
<version value="2"></version>
<list>
<mapping class="com.example.litepaltest.Book" />
<mapping class="com.example.litepaltest.Category"/>
</list>
</litepal>
结果如下所示
2、使用LitePal添加数据
Litepal进行表管理操作时不需要模型类有任何的继承结果,但是进行CRUD操作时,必须继承自DataSupport类
public class Book extends DataSupport {}
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
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();
}
});
3、使用LitePal更新数据
最简单的就是对已存储的对象重新设值,然后重新调用save()方法
对于LitePal来说,对象是否存储就是根据调用model.isSaved()方法的结果来判断的,返回true就是表示已存储。该方法只有在两种情况下会返回true,一种情况是已经调用model.save()方法去添加数据了,此时model会被认为是已存储的对象,另一种情况是model对象是通过LitePal提供的查询API查出来的,由于是从数据库中查到的对象,因此也被认为是已存储的对象。下面暂时介绍第一种。
Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Book book = new Book();
book.setName("The Lost Symbol");
book.setAuthor("Dan Brown");
book.setPages(540);
book.setPrice(16.95);
book.setPress("Unknow");
book.save();
book.setPrice(10.99);
book.save();
}
});
该方法只能对已存储的对象进行操作,限制性较大。一种更加灵巧的方式如下
Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Book book = new Book();
book.setPrice(14.95);
book.setPress("Anchor");
book.updateAll("name = ? and author = ?" ,"The Lost Symbol","Dan Brown");
}
});
updateAll()方法,不能使用set默认值,字段都会有默认数据类型,set默认值以后LitePal不会对这个字段进行更新。其提供了一个统一方法,setToDefault()方法。
Book book = new Book();
bood.setToDefault("pages");
book.updateAll();
这段代码的意思是,将所有书的页数都更新为0,因为updateAll()方法中没有指定
约束条件,因此更新操作对所有的数据都生效了。
4、使用LitePal删除数据
直接使用delete()
deleteAll()
Button deleteButton = (Button) findViewById(R.id.delete_data);
deleteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DataSupport.deleteAll(Book.class, "price<?", "15");
}
});
使用LitePal查询数据
Button queryButton = (Button) findViewById(R.id.query_data);
List<Book> books = DataSupport.findAll(Book.class);
for (Book book : books){
Log.d("MainAcitivity","book name is "+ book.getName());
}
LitePal的查询API有很多,可以直接参考官方文档,同时LitePal仍然支持原生的SQL语句。