持久化技术简介
数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑关机的情况下,这些数据仍然不会丢失。保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据时处于持久状态的,持久化技术则提供了一种机制可以让数据在瞬时状态和持久状态之间进行转换。
1. 文件存储
文件存储是Android中最基本的一种数据存储方式,它不对存储的内容进行任何格式化的处理,所有数据都是原封不动的保存到文件当中,因而它比较适合用于存储一些简单的文本数据或者二进制数据。
将数据存储到文件中去
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type somthing here."/>
</LinearLayout>
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);
}
private 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();
}
}
}
}
openFileOutput()接收两个参数,第一个参数是文件名,这个文件名不可以包含路径,默认存储到/data/data/< package name>/files/目录下,第二个参数是文件的操作模式,主要有两种模式可选,MODE_PRIVATE和MODE_APPEND。其中MODE_PRIVATE是默认操作模式,表示当指定同样的文件名时,所写入的内容会覆盖原文件中的内容,而MODE_APPEND表示当文件存在时,往文件中追加内容。
当程序退出时,程序保存EditText中的数据,因为我没有找到存储数据的文件,所以通过从文件中读取数据来验证是否存入
从文件中读取数据
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();
edit.setText(inputText);
}
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 {
try{
if(reader!=null){
reader.close();
}
}catch (IOException e ){
e.printStackTrace();
}
}
return content.toString();
}
}
openFileInput()只接受一个参数,即要读取的文件名,然后系统会自动到/data/data/< package name>/files/目录下去加载这个文件
TextUtils.isEmpty(String):当传入的字符串是null或者是空字符串时都会返回true
2. SharedPreferences
不同于文件存储,SharedPreferences是使用键值对的方式来存储数据的。也就是说当保存一条数据时,需要给这条数据提供一个相应的键,这样在读取数据的时候就可以通过这个键把相应的值取出来。而且,sharedPreferences还支持多种不同的数据类型存储。
将数据存储到SharedPreferences
1. 首先要获取到SharedPreferences对象,Android提供了三种方法用于得到SharedPreferences对象
- Context类中的getSharedPreferences()方法
此方法接收两个参数,第一个参数用于指定SharedPreferences文件的名称,如果指定的文件不存在则会创建一个,存放在/data/data/< package name>/shared_prefs/目录下,第二个参数用于指定操作模式,目前只有MODE_PRIVATE一种模式可选,表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。 - Activity类中的getPreferences()方法
与getSharedPreferences方法类似,但是这个方法只接受一个参数,因为使用这个方法时,会自动将当前活动的类名作为SharedPreferences的文件名。 - PreferencesManager类中的getDefaultSharedPreferences()方法
这是一个静态方法,它接受一个context参数,并自动将当前应用程序的包名作为前缀来命名SharedPreferences文件
2. 得到了SharedPreferences对象之后,就可以开始向SharedPreferences文件中存储数据了,主要分为三步
(1) 调用SharedPreferences对象的edit()方法来获取一个SharedPreferences.Editor对象
(2) 向SharedPreferences.Editor对象中添加数据,putBoolean(),putString()…
(3) 调用apply()方法将添加的数据提交,从而完成数据的存储操作
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<Button
android:id="@+id/save_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save data"/>
</LinearLayout>
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 view) {
SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit();
editor.putString("name","tom");
editor.putInt("age",28);
editor.putBoolean("married",false);
editor.apply();
}
});
}
}
从SharedPreferences中读取数据
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent">
<Button
android:id="@+id/save_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save data"/>
<Button
android:id="@+id/restore_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Restore data"/>
</LinearLayout>
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);
Button restoreData = (Button)findViewById(R.id.restore_data);
saveData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit();
editor.putString("name","tom");
editor.putInt("age",28);
editor.putBoolean("married",false);
editor.apply();
}
});
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);
Log.d("MainActivity", "age is "+age);
Log.d("MainActivity", "married is "+married);
}
});
}
}
3. 实现记住密码功能
登陆界面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<TextView
android:layout_gravity="center_vertical"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="Account"/>
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:id="@+id/account"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<TextView
android:layout_width="90dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textSize="18sp"
android:text="Password"/>
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:id="@+id/password"
android:inputType="textPassword"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/remember_pass"
android:text="Remember Password"/>
</LinearLayout>
<Button
android:layout_width="match_parent"
android:layout_height="60dp"
android:id="@+id/login"
android:text="Login"/>
</LinearLayout>
public class LoginActivity extends BaseActivity {
private EditText accountEdit;
private EditText passwordEdit;
private Button login;
private SharedPreferences pref;
private SharedPreferences.Editor editor;
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 isRemenber = pref.getBoolean("remember_password",false);
if(isRemenber){
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("123456")){
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, "account or password is invalid", Toast.LENGTH_SHORT).show();
}
}
});
}
}
第一次去调用getBoolean()方法去获取remember_password这个键对应的值,当然是不存在的,所以会使用默认值false,接着在登录成功之后,会调用checkbox的isChecked()方法来检查复选框是否被选中,如果选中了,则表示用户想要记住密码,这时将remember_password设置为true,然后把account和password对应的值都存入SharedPreferences中并提交。如果未选中,就调用一下clear()将SharedPreferences中的数据全部清除掉。
4.SQLite 数据库
1. 创建数据库
- 1. Android专门提供了一个SQLiteOpenHelper帮助类,借助这个类就可以简单的对数据库进行创建和升级。
- 2. SQLiteOpenHelper类是一个抽象类,有两个抽象方法,分别是onCreate()和onUpgrade()。
- 3. SQLiteOpenHelper中还有两个重要的实例方法,getWritableDatabase()和getReadableDatabase()。这两个方法都可以创建或者打开一个现有的数据库(如果数据库已经存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。区别是当数据库不可写入的时候(比如磁盘空间已满),getReadableDatabase()方法返回的对象将以只读方式去打开数据库,而getWritableDatabase()方法则出现异常。
- 4. SQLiteOpenHelper有两个构造方法,一般使用参数少一点的那个构造方法。
这个构造方法有四个参数,第一个参数是context,第二个是数据库名,第三个参数允许我们在查询数据的时候返回一个自定义的cursor,一般传入null,第四个参数表示当前数据库的版本号。 - 5. 建表语句
这里我们希望创建一个名为BookStore.db的数据库,然后再这个数据库中新建一张book表
create table book(id integer primary key autoincrement,author text,price real,pages integer,name text)
表中id(主键),作者,价格,页数,和书名
integer表示整型,real表示浮点型,text表示文本类型,blob表示二进制类型,上述建表语句中我们使用 了primary key 将id列设为主键,并用autoincrement关键字表示id列是自动增长的。
步骤
1. 新建MyDatabaseHelper继承SQLiteOpenHelper
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/create_database"
android:text="Create database"/>
</LinearLayout>
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 sqLiteDatabase, int i, int i1) {
}
}
我们把建表语句定义成了一个字符串常量,然后在onCreate()方法中又调用了SQLiteDatabase的execSQL()方法去执行这条建表语句,并弹出一个toast提示创建成功,这样可以保证在数据库创建成功的同时还能成功创建book表。
2. 修改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 view) {
dbHelper.getWritableDatabase();
}
});
}
}
3. 创建成功后的查看
- Android SDK 自带的一个调试工具 adb(在sdk目录下的platform-tools)
1. 配置环境变量
右击计算机->属性->高级系统设置->环境变量,然后找到path并点击编辑,将sdk目录下platform-tools路径配置进去
2. 使用
- 打开控制台,并输入adb shell(注意:只有在模拟器运行时才有效),#是超级管理员,$是普通管理员,以下操作只有超级管理员可以,输入 su 即可成为超级管理员
- 接下来使用 cd 命令进入到 /data/data/com.example.(项目名小写)/databases/,并使用 ls 查看该目录下的文件,一个是我们创建的BookStore.db,而另外一个则是为了让数据库能够支持事物而产生的临时日志文件。
- 接下来借助 sqlite命令打开数据库,只需要键入 sqlite3 ,后面加上数据库名。
- 然后键入 . table 来查看当前数据库中有哪些表
- 我们还可以通过 . schema 来查看他们的建表语句
2. 升级数据库
onUpgrade()方法对数据库进行升级
在BookStore.db中想要再添加一张category表。
1. 在MyDatabaseHelper中添加如下代码。
public static final String CREATE_CATEGORY = "create table category ("
+"id integer primary key autoincrement,"
+"category_name text,"
+"category_code integer)";
2. 在onCreate()方法中添加db.execSQL(CREATE_CATEGORY);
3. 修改onUpgrade()方法如下
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);
}
在onUpgrade()方法中执行了两条DROP语句,如果发现数据库中已经存在book表或category表了,就将这两个表删除,然后再调用onCreate()方法重新创建。
4. 在MainActivity中
dbHelper = new MyDatabaseHelper(this,"BookStore.db",null,2);
修改构造方法第四个参数(版本),只要传入比之前1大的数,就可以让onUpgrade()方法执行。
3. 添加数据
SQLiteDatabase提供了一个insert()方法,用于添加数据,它接受三个参数,第一个参数是表名,第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值null,一般用不到,直接传入null,第三个参数是一个ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。
1.在布局中添加一个按钮
<Button
android:id="@+id/add_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add data"/>
2. 修改MainActivity代码,开始添加数据
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 Da Vinci Code");
values.put("author","Dan Brown");
values.put("pages",454);
values.put("price",16.96);
db.insert("book",null,values);
values.put("name","The Lost Symbol");
values.put("author","Dan Brown");
values.put("pages",510);
values.put("price",19.98);
db.insert("book",null,values);
}
});
4.更新数据
SQLiteDatabase提供了一个update()方法,用于对数据的更新,这个方法接受四个参数,第一个参数是表名,第二个参数是ContentValues对象,把要更新的数据在这里组装进去,第三,第四个参数用于约束某一行或某几行中的数据,不指定的话就是默认更新所有行。
1. 添加一个按钮
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/update_data"
android:text="Update data"/>
2. 修改MainActivity代码,更新数据
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"});
}
});
onUpdate() 方法,使用第三,四个参数来指定具体更新哪几行,。第三个参数对应的是SQL语句的where部分,表示更新所有name=?的行,而?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。
5. 删除数据
SQLiteDatabase提供了一个delete()方法,用于删除数据,这个方法接收三个参数,第一个参数是表名,第二,三个参数用于约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。
1. 添加一个按钮,点击删除数据
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/delete_data"
android:text="Delete data"/>
2. 修改MainActivity代码,删除数据
Button deleteData = (Button)findViewById(R.id.delete_data);
deleteData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("book","pages > ?",new String[]{"500"});
}
});
通过第二,三个参数来指定删除那些页数大于500的书。
6. 简单查询数据
SQLiteDatabase中提供了一个query()方法用于对数据进行查询,这个方法的参数非常复杂,最短的一个方法重载也需要传入7个参数。
第一个参数:表名
第二个参数:用于指定查询哪几列,不指定则默认查询所有列
第三,四个参数:用于查询某一行或某几行数据,不指定则默认查询所有行
第五个参数:用于指定需要去group by的列,不指定则表示不对查询结果进行group by操作
第六个参数:用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤
第七个参数:用于指定查询结果的排序方式,不指定则表示默认的排序方式
1.添加一个button
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/query_data"
android:text="Query data"/>
2.修改MainActivity代码
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{
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();
}
});
首先,我们在查询按钮的点击事件里面调用了SQLiteDatabase的query()方法去查询数据,这里的query()方法非常简单,只是用了第一个参数去指明查询book表,后面参数全为null,查询完之后得到一个cursor对象,接着我们调用它的moveToFirst()方法将数据的指针移动到第一行的位置,然后进入了一个循环当中,去遍历查询每一行数据,通过cursor的getColumnIndex()方法获取到某一列在表中对应的位置索引,然后将这个索引传入到相应的取值方法中,就可以得到从数据库中读取到的数据了。