====================================
====== 第六章:数据存储全方案 — 详解持久化技术 ======
====================================
持久化技术就是将那些内存中的瞬时数据保存到存储设备中。
Android中主要通过3种用于简单实现数据持久化的功能:1、文件储存。2、SharedPreference存储。3、数据库存储。
除此之外,还可以将数据保存在手机的SD卡中。
6.2、文件存储
Context类提供了一个openFileOutput()方法,可以用于将数据储存在指定的文件中。方法接收两个参数,第一个是文件名,不包含路径,因为所有的文件都是默认存储在/data/data/<packagename>/files/目录下的。第二个参数是文件的操作模式,主要由两种模式可选:MODE_PRIVATE和MODE_APPEND。其中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.printStatckTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码解释:
通过openFileOutput()方法得到一个FileOutputStream对象,然后再借助它来构建一个OutputStreamWriter对象,接着再使用OutputStreamWriter构建出一个BufferWriter对象。BufferWriter对象就可以写入文件中了。
现在创建一个FilePersistenceTest项目。
1、修改activity_main.xml文件:
<LinearLayout xmlns:android=“http://schemas.android.com/apl/res/android”
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
<EditText
android:id=“@+id/edit”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:hint=“Type something here” />
</LinearLayout>
只有一个输入框,如果输入了内容点击了返回,里面内容就消失了。
2、修改MainActivity的代码
public class MainActivity extends AppCompatActivity {
private EditText editText;
@Override
protected void onCreate(Bundle saveInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = (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;
BufferWriter writer = null;
try {
out = openFileOutput(“data”, Context.MODE_PRIVATE);
writer = new BufferWriter(new OutputStreamWriter(out));
writer.write(inputText);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
重写了onDeatroy()方法,可以保证在活动销毁之前保存数据。
那么,我们怎么知道我们保存的数据是否已经成功了呢。使用Android Device Monitor工具(在AndroidStudio导航栏的Tools —> Android中可以看到工具列表)
Device File Exployor进行查询,输入/data/data/com.example.filepersistencetest/files/目录,可以看到生成了一个data文件。
我们打开这个文件发现数据已经保存成功。
现在我们需要从文件中读取数据。
6.2.2 从文件中读取数据:
Context类中还提供了一个openFileInput()方法,用于从文件中读取数据,这个方法只接受一个参数,即要读取的文件的文件名。然后系统会自动到/data/data/<package name>/files/目录下去加载这个文件,并返回一个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();
}
上面代码也很简单,先通过openFileInput()方法传入文件名获取到一个FIleInputStream对象,然后借助他构建出一个InputStreamReader对象,接着再根据它构建一个BufferedReader对象,这样我们就可以通过BufferedReader进行一行还地读取,把文件中所有的文本内容全部取出来,并存放在一个StringBuilder对象中。
3、完善代码,取数据。修改MainActivity的代码:
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(input.length()); // 将光标定位到文本的末尾以便继续输入
Toast.makeText(this, “Restoreing succeeded”, Toase.LENGTH_SHORT).show();
}
…
public String load() {
FileInputStream in = null;
BufferReader reader = null;
StringBuilder content = new StringBuilder();
try {
in = openFileInput(“data”);
reader = new BufferedReader(new InputStreamReader(in));
。。。跟上面的代码保持一样。
上述代码中用到了TextUtils.isEmpty()方法,这是一个非常好用的方法,可以一次性进行两种空值的判断。当传入的字符串等于null或者等于空字符串的时候,方法都会返回true。
文件存储方面的知识核心技术就是Context类中提供的openFileInput()和openFileOutput()方法。
6.3 SharePreferences存储
SharePreferences是使用键值对方式来存储数据的。跟iOS中类似。如果存储的数据类型是正行,那么读取出来的数据也是正行的。如果存储的数据是一个字符串,那么读取出来的数据也是字符串。
6.3.1 将数据存储到SharePreferences中
先要获得SharePreferences对象。Android主要提供了三种方法用于得到SharePreferences对象:
1、Context类中的getSharePreferences()方法。
2、Activity类中的getPerferences()方法;
3、PreferenceManager类中的getDefaultSharePreferences()方法。
1、此方法接收两个参数,第一个用于指定SharePreferences文件的名称,如果文件不存在则创建一个。SharePreferences文件是存放在/data/data/<package name>/shared_prefs/目录下的。第二个参数用于指定操作模式(目前只有MODE_PRIVATE这一只模式可选),直接传0也可。
2、此方法与方法1类似。不过他只接受一个操作模式的参数。因为使用这个方法会自动将当前活动的类名作为SharePreferences的文件名。
3、第三个方法这是一个静态方法。它接收一个Context的参数,并自动使用当前应用程序的包名作为前缀来命名SharePreferences文件。
当得到了SharePreferences对象之后,就可以向文件中存储数据了,存储主要分为3个步骤:
1、调用SharePreferences对象的edit()方法来获取一个SharePreferences.Editor对象。
2、向SharePreferences.Editor对象中添加数据,比如添加一个布尔类型的数据就可以使用putBoolean()方法,添加一个字符串数据就可以使用putString()方法,以此类推。
3、调用apply()方法将添加的数据提交。从而完成数据存储操作。
现在来实践:创建一个SharePreferencesTest项目。
1、然后修改activity_main.xml文件
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“vertical” >
<Button
android:id=“@+id/save_data”
androdi:layout_width=“match_pareng”
android:layout_height=“wrap_contetn”
android:text=“Save data” />
</LinearLayout>
2、修改MainActivity中的代码
public class MainActivity extends AppCompatActity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstaneState);
setContentView(R.layout.activity_main);
Button saveData = (Button) findViewById(R.id.save_data);
saveData.setOnClickLisener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SharePreferences.Editor editor = getSharePreferences(“data”, MODE_PRIVATE).edit();
editor.putString(“name”, “Tom”);
editor.putInt(“age”, 28);
editor.putBool(“married”, false);
editor.apply();
}
});
}
}
可以看到,SharePreferences文件是使用XML格式来对数据进行管理的。
6.3.2 从SharePreferences中读取数据。
读取数据非常简单。SharePreferences对象中有一系列的get方法,可以用于数据的读取。每一种get方法都对应了SharedPreferences.Editor中的一种put方法。比如读取布尔类型就用getBoolean方法,
现在创建一个新的SharedPreferencesTest项目:
1、activity_main.xml代码:
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“vertical” >
<Button
android:id=“@+id/save_data”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
androd:text=“Save data” />
<Button
android:id=“@+id/restore_data”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:text=“Restore data” />
</LinearLayout>
上面增加了一个还原数据的按钮,用于从SharedPreferences文件中读取数据。
public class MainActivity extends AppCompatActivity {
@Overrdie
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
…
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);
}
});
}
}
SharedPreferences pref = getSharedPreferences(“data”, MODE_PRIVATE);
通过文件名,获取模式获取到SharedPreferences对象,然后通过getString(),getInt(),getBoolean()等传入对应的key来获取对应的value。
很多应用程序里面的偏好设置都是使用到了SharedPreferences技术。
6.3.3 实现记住密码功能。
public class LoginActivity extends BaseActivity {
private SharePrefereneces pref;
private ShaerPrefereneces.Editor editor;
private EditText accountEdit;
private EditText passwordEdit;
private Button login;
private CheckBox remenberPass;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.id.activity_login);
pref = PreferenceManager.getDefaultSharePreferences(this);
accountEdit = (EditText) findViewById(R.id.account);
passwordEdit = (EditText) findViewById(R.id.password);
rememberPass = (CheckBox) findViewById(R.id.remenber_pass);
login = (Button) findViewById(R.id.login);
boolean isRemenber = pref.getBoolean(“remenber_password”, false);
if (isRemenber) {
// 将账号和密码都设置到文本框中
String account = pref.getString(“account”, “”);
String password = pref.getString(“password”, “”);
accountEdit.setText(account);
passwordEdit.setText(password);
remenberPass.setChecked(true);
}
login.setOnClickLinstener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String accont = accountEdit.getText().toString();
String password = passwordEdit.getText().toString();
// 如果账号是admin和密码是123456.就认为登录成功
if (account.equals(“admin”) && password.eqauls(“123456”)) {
editor = pref.edit();
if (remenberPass.isChceked()) {
editor.putBoolean(“remember_password”, true);
editor.putString(“accont”, account);
editor.putString(“password”, password);
} else {
editor.clear(); // 清空数据
}
editor.apply(); // 同步一下
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent)’;
finish();
} else {
Toask.make(LoginActivy.this, “account or password is invalid”, Toast.LENGTH_SHOT).show();
}
}
});
}
}
直接存储明文的用户名密码是不安全的,实际项目中需要结合一定的加密算法来进行密码保护。
6.4 SQLite数据库存储
Android系统是内置了数据库的。
6.4.1 创建数据库
Android为了我们更好的管理数据库,专门提供了一个SQLiteOpenHelper的帮助类(抽象类),有两个抽象方法:onCreate()和onUpgrade()方法。必须重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
SQLiteOpenHelper中还有两个非常重要的实例方法:getReadableDatabase()和 getWritableDatabase()。当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase()返回的对象将以只读的方式打开数据库。getWritableDatabase()将会报错
SQLiteOpenHelper有两个构造方法。构造方法有四个参数。构建完实例之后再调用get方法就能创建数据库了,然后重写的onCreate()方法就能得到执行,在onCreate()方法里面处理创建表的一些逻辑。
数据库文件存储在/data/data/<package name>/databases/目录下
新建一个DataBaseTest项目。
1、创建一个Book表,建表语句如下所示:
create table Book (
id integer primary key autoincrement,
author text,
price real,
pages integer,
name text)
integer表示整形,real表示浮点型,text表示文本类型,blob表示二进制类型。primary key将id类设置为主键,autoinrement关键字表示id列是自增长的。
2、用代码去执行这条SQL语句,才能完成创建表的操作。新建MyDatabaseHelper类继承自SQLiteOpenHelper,代码如下:
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 succeeded”, Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
使用execSQL()方法创建表
3、修改activity_main.xml文件
<LinearLayout xmlns:android=“http://schemas.andrdoid.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent” >
<Button
android:id=“@+id/create_database”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:text=“Create database” />
</LinearLayout>
布局文件很简单,只是加入了一个按钮
4、修改MainActivity代码:
public class MainActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.id.activity_main);
dbHelper = new MyDatabaseHelper(this, “BookStore.db”, null, 2);
Button createDatabase = (Button) findViewById(R.id.create_database);
createDatabase.setOnClickLisener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 第一次点击的时候创建数据库,再次点击不会重复创建
dbHelper.getWriteableDatabase();
}
});
}
}
如果这时候我们再新增一个新表Category表,我们修改MyDatabaseHelper的内容
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_CATEGORY = “create table Categoty (”
“id integer primary key autoincrement,”
“categoty_name text,”
“categoty_code integer)”;
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATRGORY);
Toast.makeText(mContext, “Create succeeded”, Toast.LENGTH_SHORT).show();
}
@Override
public void onUpdate(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(“drop table if exists Book”);
db.execSQL(“drop table if exists”
}
}
关键点:1、修改onUpdate方法,如果表存在则移除掉。2、修改MyDatabaseHelper的创建,修改version为2,只要传入的数字大于1,就可以执行onUpdata方法了。
6.4.3 添加数据:C
我们需要学习CRUD的操作。每一个操作都会代表一个SQL命令。
C:Create添加:insert操作
R:Retrieve查询:select操作
U:Update更新:update操作
D:Delete删除:delete操作
Android中无需去编写SQL语句,也能轻松完成所有的CRUD操作。
前面我们已经知道,调用SQLiteOpenHelper的getReadableDatabase()或getWritableDatabase()方法是可以创建和升级数据库的。不仅如此,还会返回一个SQLiteDatabase对象,借助这个对象就可以对数据进行CRUD操作了。
1、SQLiteDatabase的insert()方法,接收三个参数,1是表名 2是用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般用不到,直接传null,3是ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。
添加一个按钮:
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onCliK(View view) {
SQLiteDatabase db = dbHelper.getWriteabelDatabase();
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();
}
});
我们只对Book表的其他四条数据进行赋值,没有对id进行操作,因为前面我们已经设置了id为自增长的了。
6.4.4 更新数据:U
SQLiteDatabase也提供了update()方法进行数据的更新,这个方法接收4个参数:1表名。2ContentValues对象,把需要更新的数据组装进去。3和4用于约束更新某一行或者某几行的数据,不指定的话默认就是更新所有行。
添加一个Button,
Button updateData = (Button) findViewById(R.id.updata_date);
updateData.setOnClickLisener(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 = ?的行
第四个参数是提供的一个字符串数组为第三个参数中的每一个占位符指定相应的内容。
6.4.5 删除数据:
删除数据调用SQLiteDatabase的delete()方法。这个方法接收三个参数:1、表名。2和3用于约束删除某一行或某几行的数据。不指定的话就是删除所有行。
继续添加一个Button,
Button deleteButton = (Button) findViewById(R.id.delete_data);
deleteButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete(“Book”, “pages > ?”, “new String[] {“500”}” );
}
});
6.4.6 查询数据
查询数据调用SQLiteDatabase的query()方法进行查询。这个方法的参数非常复杂,最短的一个方法也需要重载7个参数。
参数1 table、表名
参数2 columns、指定去查询那几行
参数3 selection(where字段) 和4selectionArgs、用于约束查询某一行或某几行的数据,不指定则默认查询所有行
参数5groupBy、用于指定需要去group by的列,如果不指定,则表示不对查询数据进行group by操作。
参数6 having、用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。
参数7 orderBy、指定查询结果的排序方式,不指定则表示使用默认的排序方式。
调用query()方法会返回一个Cursor对象。查询到的数据都是从这个对象中取出。
继续添加一个Button
Button query_data = (Button) findViewById(r.id.query_data);
query_data.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view){
SQLiteDatabaes db = dbHelper.getWritableDatabase();
// 查询Book表中的所有数据
Cursor cursor = db.query(“Book”, null, null, null, null, null, null);
if (cursor.moveToFirst()) {
do {
String name = cursor.getString(cursor.getColumnIndex(“name”));
String author = cursor.getString(cursor.getColumnIndex(“author”);
String pages = cursor.getString(cursor.getColumnIndex(“price”);
double price = cursor.getDouble(corsor.getColumnIndex(“price”);
} while (cursor.moveToNext()) ;
}
cursor.close();
}
});
6.4.7 使用SQL语句直接操作数据库:
1、添加数据:
db.execSQL(“insert into Book (name, author, pages, price) values(?,?,?,?)”, new String[] {“The Da Vinci Code”, “Dan Brown”, “454”, “16.96”});
2、更新数据:
db.execSQL(“update Book set price = ? where name = ?”, new String[] {“10.99”,”The Da Vinci Code”};
3、删除数据:
db.execSQL(“delete from Book where pages > ?”, new String[] {“500”});
4、查询数据
db.rawQuery(“select * from Book”,null);
6.5 使用LitePal操作数据库
1、需要编辑app/build.gradle文件,在dependencies中添加如下内容
dependencies {
compile ‘org.litepal.android:core:1.3.2’
}
添加这行声明,前面部分是固定的,最后的1.3.2是版本号的意思,最新的版本号可以去LitePal的项目主页上查看
接下来需要配置litepal.xml文件。
2、右键app/src/main目录 —> New —> Directory创建一个assets目录,然后在assets目录下再建一个litepal.xml文件,接着编辑litepal.xml文件中的内容。
<?xml version=“1.0” encoding=“utf-8” ?>
<litepal>
<dbname value=“BookStore” > </dbname>
<version value=“1” ></version>
<list>
</list>
</litepal>
3、修改AndroidMainifest.xml中的代码
<mainifest xmlns:androdi=“http://schemas.android.com/apl/res/android”
package=“com.example.litepaltest”>
<application
android:name=“org.litepal.LitePalApplication”
android:allow
。。。
算了,数据库的之后再继续看了
。。。