读书笔记系列:第一行代码 Android
6.1 持久化技术简介
三种数据持久化方式:文件存储、SharedPreference存储以及数据库存储,除此之外还可以存储在SD卡中(不安全)
6.2 文件存储
该方法不对存储的内容做格式化处理都是直接保存到文件中,所以适合存一些简单的文本数据或二进制数据,要想存复杂文本数据,就需要自己定义格式规范方便解析。
6.2.1 将数据存储到文件中
Context类中有一个openFileOutput方法,可将数据保存到指定文件中,需要两个参数,第一个是文件名(文件名不包含路径,所有都默认存在/data/data/<packagename>/files/
目录下),第二个参数是文件的操作模式,MODE_PRIVATE和MODE_APPEND,一个是覆盖一个是追加。
openFileOutput()方法返回的是FileOutputStream对象,得到这个对象后就可以使用JAVA方式将数据写入到文件中了,以下是一段简单的示例:
public void save(){
String data = "data to save";
FileOutputStream out = null;
BufferedWriter writer = null;
try{
out = openFileOutput("data", Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null){
writer.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
一个保存下来输入值的具体案例
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();
}
}
}
}
从文件中读取数据
Context类中还有一个OpenFileInput()方法,可以从文件中读取数据,它只接收文件名参数并返回FileInputStream对象,得到这个对象后就可以用JAVA流的方式读取出来数据了
以下是一段读文本数据的示例:
public 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) {
e.printStackTrace();
} finally {
if (reader != null){
try{
reader.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
return content.toString();
}
onCreate()方法中调用 load()方法来读取文件中存储的文本内容,如果读到的内容不为null,就调用EditText的setText()方法将内容填充到EditText里,并调用setSelection()方法将输入光标移动到文本的末尾位置以便于继续输人,然后弹出一句还原成功的提示。
注意,上述代码在对字符串进行非空判断的时候使用了 Textutils.isEmpty()方法,它可以一次性进行两种空值的判断。当传入的字符串等于 null 或者等于空字符串的时候,这个方法都会返回 true,就不用再分别单独判断了。
SharedPreferences存储
该方式使用键值对存储数据,并且存的数据是整数的话读的数据也是,存的是字符串的话,读的也是字符串。
将数据存到SharedPreferences中
安卓中有三种方法获取SharedPreferences对象
- Context类中的getSharedPreferences方法:该方法接收两个参数(文件名、工作模式),该方法的文件都是在
/data/data/<package name>/shared_prefs/
目录下。第二个参数只有 MODE_PRIVATE 这一个模式可以选,默认(0),表示只有当前app才能对文件读写。 - Activity类中的 getPreferences() 方法:与上一个类似,但只有一个参数,因为该类会自动将当前活动类作为文件名。
- PreferenceManager 类中的 getDefaultSharedPreferences() 方法:这是个静态方法,接收一个Context参数,并自动使用当前应用程序的包名作为前缀来命名 SharedPreferences 文件。存储数据主要分三步:(1)调用 SharedPreferences 对象的 edit()方法,返回一个 SharedPreferences.Editor对象 (2)向其中添加数据,如布尔型则putBoolean(), 字符串则 putString(), 以此类推 (3)调用apply()方法数据提交【整个流程挺容易理解的 安全性 也类似于数据库】
public class MainActivity extends AppCompatActivity {
@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
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/save_data"
android:text="Save data"/>
保存的文件是xml格式的,可知SharedPreferences使用xml格式进行管理。
6.3.2 从SharedPreferences中读数据
直接使用getInt()等类似的get函数即可,接收一个参数,第二个参数默认值(找不到的话返回什么)。
Button restoreData = (Button) findViewById(R.id.restore_data);
restoreData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
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);
}
});
6.3.3 实现记住密码功能
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/remember_pass"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Remember password"
android:textSize="18sp"/>
</LinearLayout>
checkbox是个复选框
public class MainActivity extends AppCompatActivity {
private SharedPreferences pref;
private SharedPreferences.Editor editor;
private EditText accountEdit;
private EditText passwordEdit;
private Button login;
private CheckBox rememberPass;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
pref = PreferenceManager.getDefaultSharedPreferences(this);
accountEdit = (EditText) findViewById(R.id.account);
passwordEdit = (EditText) findViewById(R.id.password);
rememberPass = (CheckBox) findViewById(R.id.remember_pass);
login = (Button) findViewById(R.id.login);
boolean isRemember = pref.getBoolean("remember_password", false);
if(isRemember){
// 将账号和密码都设置到文本框中
String account = pref.getString("account", "");
String password = pref.getString("password", "");
accountEdit.setText(account);
passwordEdit.setText(password);
rememberPass.setChecked(true);
}
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String account = accountEdit.getText().toString();
String password = passwordEdit.getText().toString();
//如果账号密码对就登录成功
if(account.equals("admin") && password.equals("13123")){
editor = pref.edit();
if(rememberPass.isChecked()){ //检查复选框是否被选中
editor.putBoolean("remember_password", true);
editor.putString("account", account);
editor.putString("password", password);
}else{
editor.clear();
}
editor.apply();
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();;
}else{
Toast.makeText(LoginActivity.this, "feifamima", Toast.LENGTH_SHORT).show();
}
}
});
}
}
6.4 SQLite数据库存储
6.4.1 创建数据库
安卓提供了抽象类 SQLiteOpenHelper 管理数据库,要使用它就需要自己创建类继承它。其中有两个抽象函数 onCreate() 和 onUpgrade() ,我们要重写这两个方法。
除此之外,getReadableDatabase() 和 getWritableDatabase(),都可以创建或打开一个现有数据库,并返回一个可对数据库读写的对象,不同的是,数据库不可写入时,getReadableDatabase() 方法将以只读方式打开,而另一个则抛出异常。
SQLiteOpenHelper 类的构造函数有两个,其中一个接收4个参数(常用),Context、数据库名、第三个参数允许在查询时返回我们自定义的Cursor,一般传入null。最后一个是版本号。数据库文件会存放在 /data/data/<package name>/database/
目录下,此时,重写的 onCreate 方法也会得到执行,所以通常在其中加创建表的逻辑。
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, "create success", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
}
}
建一个类,之后再main中测试
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 view) {
dbHelper.getWritableDatabase();
}
});
}
}
6.4.2 升级数据库
上面代码中可以看到 MyDatabaseHelper 中还有个空方法 onUpgrade() 这个函数用于对数据库升级。
如果我们再在onCreate函数中添加一个数据库执行语句,这条语句将不会执行,我们之前说到过只在数据库创建时才会执行其中代码,那么要想实现新建表、修改表等操作,该怎么办呢?
我们可以在 SQLiteOpenHelper 的第四个参数中输入一个比1大的值,就可以让它执行 onUpgrade() 函数了。
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
修改 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);
}
其中先删除了表,又创建了表。
6.4.3 添加数据
SQLiteDatabase 中提供了 insert() 函数可以插入数据,它接收3个参数,第一个是表名,第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值 NULL,一般用不到,直接传null。第三个是一个 ContentValues 对象,提供了一系列 put() 方法重载,用于向 ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
// 开始组装第一条数据
values.put("name", "The dasdad");
values.put("author", "dasdad");
values.put("pages", 3213);
values.put("price", 3213);
db.insert("Book", null, values); // 插入第一条数据
values.clear();
// 组装第二条数据
values.put("name", "dasdad");
// 等等等
db.insert("Book", null, values); // 插入第二条数据
}
});
6.4.4 更新数据
updata() 方法 和 insert() 方法类似,接收4个参数,第一个参数是表名,第二个是 ContentValues 对象(组装更新数据),第三、四个参数用于约束更新某一行或者某几行的数据,不指定的话就是更新所有行。
Button updateData = (Button) findViewById(R.id.Update_data);
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
db.update("Book", values, "name = ?", new String[]{"the da Vinci code"})
}
});
这里第三个参数对应的是SQL中的where语句,表示更新所有name等于?的行,?使一个占位符,可以通过第四个参数的字符串为第三个参数的每个占位符指定相应的内容,则上述代码含义是更新“the da Vinci code”价格为10.99.
6.4.5 删除数据
delete() 三个参数:表名,第二三个参数用于约束删除某一行或某几行数据,不指定则删除所有行。
db.delete("Book", "pages > ?", new String[]{"500"});
6.4.6 查询数据
query()需要七个参数(不全要)
Button queryData = (Button) findViewById(R.id.query_data);
queryData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
// 查询表中的所有数据
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 page = cursor.getInt(cursor.getColumnIndex("pages"));
}while(cursor.moveToNext());
}
cursor.close();
}
});
6.5 使用 LitePal 操作数据库
在app/build.gradle中的denpendencies中添加内容
implementation 'org.litepal.guolindev:core:3.2.3'
报错解决地址
然后在,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>
数据库名和数据库版本,list中是指定的映射模型。最后还需配置下litepal,修改AndroidManifest文件:
<application
<!-- 添加的内容-->
android:name="org.litepal.LitePalApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LitePalTest"
tools:targetApi="31">
6.5.3 创建和升级数据库
LitePal 采用的是对象关系映射(ORM)模式(将面向对象的语言和面向关系的数据库间建立映射关系)
首先创建一个Book类
public class Book {
private int id;
private String author;
private double price;
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;
}
}
Book类其实就是数据库中的Book表,其中的每个字段就代表每一个列。接下来修改litepal.xml中代码:
<list>
<mapping class="com.example.litepaltest.Book"/>
</list>
现在只要任意执行一次数据库操作,数据库就会被创建
Button createDatabas = (Button) findViewById(R.id.create_database);
createDatabas.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Connector.getDatabase();
}
});
Connector.getDatabase()方法就是一次简单的数据库操作。
如果我们想更新数据库,那么直接修改Book类中的代码,添加相应字段, 并将版本号加一即可。
更新数据得话直接在原对象基础上set就行。
Book book = new Book();
book.setId(12);
book.setAuthor("dsad");
book.updateAll("name = ? and author = ?", "The Lost Symbol", "Dan bda");
还可以这样更新
要想将数据更改为默认值可以使用 setToDefault() 方法,ru:
// 将所有书的页数都变为0
Book book = new Book();
book.setToDefault("pages");
book.updateAll();
删除数据
DataSupport.deleteAll(Book.class, "price < ?", "15");
查询语句
list<Book> books = DataSupport.findAll(Book.class);
除此之外,还有很多的api