您的位置:首页 > 编程语言

第二行代码学习笔记——第六章:数据储存全方案——详解持久化技术

2017-05-03 21:31 603 查看

本章要点

任何一个应用程序,总是不停的和数据打交道。

瞬时数据:指储存在内存当中,有可能因为程序关闭或其他原因导致内存被回收而丢失的数据。

数据持久化技术,为了解决关键性数据的丢失。

6.1 持久化技术简介

数据持久化技术:指那些内存中的瞬时数据保存到设备当中,保证手机和电脑关机的情况下,数据不会丢失。

保存在内存中的数据是瞬时数据,而保存在储存设备中的数据是处于持久化状态的,持久化救赎提供了可以让数据在瞬时数据和持久状态进行转换。

Android系统中提供了3种方式用于简单的实现数据持久化功能,即文件存储,SharePreferences存储数据库存储(更安全)。还有我们可以将数据存储到SD卡中(不安全)。

6.2文件存储

文件存储是Android中最基本的一种存储方式,适合用于存储一些简单的文本数据和二进制数据。

6.2.1 将数据存储到文件中

Content类提供了一个openFileOutput()方法,可以用于将数据存储到文件中。接收两个参数,第一参数的文件名,在文件创建的时候使用的就是这个名字(不可包含路径,默认存储到/data/data/< packagename/>/files/目录下的);第二个参数文件的操作模式(可选),分为MODE_PRIVATE(默认的操作模式,当指定同样的文件名的时候,所写的内容将会覆盖源文件的内容)和MODE_APPEND(如果文件存在,就往里面追加内容,不存在就创建新文件)。

openFileOutput()方法返回的是FileOutputStream对象,如何将一段文字保存到文件中,代码如下:

public void save() {
String data = "Data to save";
try {
fileOutputStream = openFileOutput("data", MODE_PRIVATE);
bufferedWriter=new BufferedWriter(new OutputStreamWriter(fileOutputStream));
bufferedWriter.write(data);
}  catch (IOException e) {
e.printStackTrace();
}finally {
try {
if (bufferedWriter!=null){
bufferedWriter.close();}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}


创建FilePersistenceTest项目,并修改activity_main中的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<EditText
android:id="@+id/et_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Type something here" />
</LinearLayout>


数据回收之前,将它存储到文件当中,修改MainAcitivty中的代码如下:

public class MainActivity extends AppCompatActivity {

private EditText et_input;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_input= (EditText) findViewById(R.id.et_input);
}

@Override
protected void onDestroy() {
super.onDestroy();
String inputText = et_input.getText().toString();
save(inputText);
}

public void save(String inputText) {
FileOutputStream fileOutputStream = null;
BufferedWriter bufferedWriter=null;
try {
fileOutputStream = openFileOutput("data", MODE_PRIVATE);
bufferedWriter=new BufferedWriter(new OutputStreamWriter(fileOutputStream));
bufferedWriter.write(inputText);
}  catch (IOException e) {
e.printStackTrace();
}finally {

try {
if (bufferedWriter!=null){
bufferedWriter.close();}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}


运行程序 ,输入内容:



然后我们按下Back键,这时输入的内容已经保存到文件当中了。

查看:导航栏中的Tools—>Android—>Android Device Monitor工具。点击File Explore标签页,/data/data/包名/files/目录。



左边的按钮可以将文件导出到电脑上



用记事本打开文件:



这样,我们就把数据保存下来了。下次启动还能将数据还原到EditText,学习从文件中读取数据。

6.2.2 从文件中读取数据

Content提供了openFileInput()方法,从文件中读取数据。接收一个参数(读取的文件名)。加载目录下的文件,返回FileInputStream对象。

从文件中读取数据,代码如下:

public String load(){
FileInputStream fileInputStream=null;
BufferedReader bufferedReader=null;
StringBuilder builder=new StringBuilder();
try {
fileInputStream = openFileInput("data");
bufferedReader=new BufferedReader(new InputStreamReader(fileInputStream));
String line="";
while ((line=bufferedReader.readLine())!=null){
builder.append(line);
}

}  catch (IOException e) {
e.printStackTrace();
}
finally {

try {
if (bufferedReader!=null){
bufferedReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return builder.toString();
}


重新启动显示我们上次输入的内容,修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity {

private EditText et_input;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_input= (EditText) findViewById(R.id.et_input);

String inputText = load();
if (!TextUtils.isEmpty(inputText)){   //一次性进行两种空值的判断
et_input.setText(inputText);
et_input.setSelection(inputText.length());  //将光标移动到文本的末尾位置
Toast.makeText(this, "Restoring succeeded", Toast.LENGTH_SHORT).show();
}

}

@Override
protected void onDestroy() {
super.onDestroy();
String inputText = et_input.getText().toString();
save(inputText);
}
...
public String load(){
FileInputStream fileInputStream=null;
BufferedReader bufferedReader=null;
StringBuilder builder=new StringBuilder();
try {
fileInputStream = openFileInput("data");
bufferedReader=new BufferedReader(new InputStreamReader(fileInputStream));
String line="";
while ((line=bufferedReader.readLine())!=null){
builder.append(line);
}

}  catch (IOException e) {
e.printStackTrace();
}
finally {

try {
if (bufferedReader!=null){
bufferedReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return builder.toString();
}
}


这时我们运行程序,就会发现EditText显示我们上次输入的内容信息:



6.3 SharedPreferences储存

SharedPreferences使用键值对的方式进行存储的。当保存一条数据,需要给这个数据提供一个对应的键,在读取数据的时候通过键取值。支持多种不同的数据类型存储。

6.3.1 将数据储存到SharedPreferences中

使用SharedPreferences来存储数据,首先获取SharedPreferences对象。

Android中提供了3中获取SharedPreferences对象的三种方式:

Content类中得到getSharedPreferences()方法

接收两个参数,第一个参数用于指定SharedPreferences文件的名称,如果指定的文件不存在,则会创建一个,SharedPreferences文件存放在/data/data/< package name/>/shared_prefs/目录下的。第二个参数操作模式,目前只有MODE_PRIVATE(默认的)一种。直接传入0的效果是一样的,表示只有当前应用程序才可以对这个SharedPreferences文件进行读写。

Activity类中getPreferences()方法

只接受一个操作模式参数,因为这个方法会自动将当前活动的类名作为SharedPreferences的文件名。

PreferenceManager类中的getDefaultSharedPreferences()方法

静态方法,接收一个Content参数,自动使用当前活动程序的包名作为前缀来命名SharedPreferences文件。

SharedPreferences文件中存储数据,主要分为3步实现:

(1) 调用SharedPreferences对象的edit()方法来获取SharedPreferences.Editor对象。

(2) 向SharedPreferences.Editor对象中添加数据,比如添加字符串使用putString()方法,以此类推。

(3) 调用apply()方法将添加的数据提交,完成数据存储操作。

新建SharedPreferencesTest项目,修改activity_main.xml中的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<Button
android:id="@+id/btn_save_data"
android:text="Save Data"
android:textAllCaps="false"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</LinearLayout>


修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity {

private Button btn_save_data;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_save_data= (Button) findViewById(R.id.btn_save_data);
btn_save_data.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
editor.putString("name","Jack");
editor.putInt("age",21);
editor.putBoolean("married",false);
editor.apply();
}
});
}
}


运行程序进入主界面后,点击Save Data按钮。这时数据就已经保存成功。我们File Explorer查看,打开文件生成目录,就可以看到生成了一个data.xml文件:



将文件导出到电脑上,用记事本打开查看:



SharedPreferences文件是用XML格式来对数据进行管理的。

6.3.2 从SharedPreferences中读取数据

从SharedPreferences中读取数据,SharedPreferences对象中提供了一些列的get方法(对应SharedPreferences.Editor中的put方法),用于对储存的数据进行读取。get方法都接受两个参数,第一个参数就是键,第二个参数是默认值(表示当传入的键找不到返回这个默认值)。

修改activity_main.xml中的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<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/btn_save_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save Data"
android:textAllCaps="false" />

<Button
android:id="@+id/btn_restore_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Restore Data"
android:textAllCaps="false" />

</LinearLayout>


修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity {

private Button btn_save_data,btn_restore_data;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_save_data= (Button) findViewById(R.id.btn_save_data);
btn_restore_data= (Button) findViewById(R.id.btn_restore_data);
...
btn_restore_data.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("MainActivty", "name:"+name);
Log.d("MainActivty", "age:"+age);
Log.d("MainActivty", "married:"+married);

}
});
}
}


运行程序点击Restore Dataa按钮,查看logcat中的打印信息:



这样我们就把之前所存储的数据读取出来了。SharedPreferences程序应用场景也不少。

6.3.3 实现记住密码的功能

我们在上一章已经编写了一个登录界面,打开BroadcastBestPractice项目,修改login_main.xml文件,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
...
<CheckBox
android:id="@+id/cb_remember_password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Remember Password"
android:textSize="18sp" />

<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="Login"
android:textAllCaps="false" />

</LinearLayout>


CheckBox是一个复选框控件,表示用户是否记住密码。修改LoginActivity中的代码如下:

public class LoginActivity extends AppCompatActivity {

private SharedPreferences pref;
private SharedPreferences.Editor editor;

private EditText et_account,et_password;
private Button btn_login;
private CheckBox cb_remember_password;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);

pref= PreferenceManager.getDefaultSharedPreferences(this);

et_account= (EditText) findViewById(R.id.et_account);
et_password= (EditText) findViewById(R.id.et_password);
btn_login= (Button) findViewById(R.id.btn_login);
cb_remember_password= (CheckBox) findViewById(R.id.cb_remember_password);

boolean isRemember=pref.getBoolean("remember_password",false);
if (isRemember){
//将账号和密码设置到文本框中
String account = pref.getString("account", "");
String password = pref.getString("password", "");
et_account.setText(account);
et_password.setText(password);
cb_remember_password.setChecked(true);
}

btn_login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String account = et_account.getText().toString();
String password = et_password.getText().toString();

if (account.equals("admin")&&password.equals("123456")){

editor=pref.edit();
if (cb_remember_password.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();
}
}
});
}
}


运行程序,输入账号admin密码123456,并选中密码复选框,点击登录,就会跳转到MainActivity。点击MainActivity中的下线广播,回到登录界面,账号密码就会自动填充到街面上:





这样,我们就是用SharedPreferences实现记住密码的功能了。(不安全,正式的项目需要结合一定的加密算法来对密码的保护)。

6.4 SQLite数据库储存

SQLite是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,只需要几百KB内存,因而特别适合在移动设备上。

SQlite不仅支持SQL语法,还遵循了数据库的ACID事务。

6.4.1 创建数据库

Android管理数据库:SQLiteOpenHelper帮助类对数据库进行创建和升级。

SQLiteOpenHelper的基本用法:SQLiteOpenHelper是一个抽象类,创建一个帮助类继承自SQLiteOpenHelper,分别必须重写onCreate()(创建)和onUpgrade()(升级)。

SQLiteOpenHelper中的getReadableDatabase()和getWritableDatabase(),创建或打开现有的数据库(如果数据库已存在则直接打开,否则一个新的数据库),并返回一个对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘已满),getReadableDatabase()只读的方式打开数据库,而getWritableDatabase()则出现异常。

SQLiteOpenHelper有两个构造方法可供重写,一般使用参数较少的构造方法(接收4个参数)。第一个参数是Context。第二个参数是数据库名称。第三个参数允许我们在查询数据的时候返回Cursor,一般传入null。第四个参数表示当前数据的版本号,用于对数据库进行升级操作。构建SQLiteOpenHelper实例之后,调用它的getReadableDatabase()或getWritableDatabase()就能创建数据库。数据库文件存放在/data/data/< package name>/databases/目录下。此时,重新onCreate()方法得到执行,处理创建表的逻辑。

创建DatabaseTest项目。

创建一个名为BookStore.db的数据库,新建Book表,表中id(主键),作者,价格,页数,书名等列。Book表的建表语句如下:

create table Book(
id integer primary key autoincrement,
author text,
price real,
pages integer,
name text)


SQLite数据类型:integer 表示整型,real 表示浮点型,text 表示文本类型,blob 表示二进制类型。primary key主键,autoincrement自增长。

新建MyDatabaseHelper类继承自SQLiteOpenHelper,代码如下:

public class MyDatabaseHelper extends SQLiteOpenHelper {

public static final String CREATE_TABLE="create table Book("
+ "id integer primary key autoincrement,"
+ "author text,"
+ "price real,"
+ "pages integer,"
+ "name text)";

private Context mContent;

public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContent=context;
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
Toast.makeText(mContent, "Create succeeded", Toast.LENGTH_SHORT).show();
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}
}


修改activity_main.xml中的代码,用于创建数据库的按钮,如下:

<?xml version="1.0" encoding="utf-8"?>
<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/btn_create_database"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Create Database"
android:textAllCaps="false" />

</LinearLayout>


修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity {

private MyDatabaseHelper dbHelper;

private Button btn_create_database;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper=new MyDatabaseHelper(this,"BookStore.db",null,1);
btn_create_database= (Button) findViewById(R.id.btn_create_database);
btn_create_database.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dbHelper.getWritableDatabase();
}
});
}
}


运行程序,点击Create Database按钮,就会检测当前程序没有BookStore.db这个数据库,然后调用MydatabaseHelper的onCreate()方法,弹出Toast创建成功。再点击按钮就不会创建了。



换一种查看方式,使用adb shell来对数据库和表的创建情况进行检查。

adb是Android SDK中自带的调试工具,可以直接对连接电脑的手机或模拟器进行调试操作。它存放在sdk的platform-tools目录下,使用adb,我们必须把它的路径配置到环境变量里。

Windows:右击计算机—>属性—>高级系统设置—>环境变量。在系统变量里找到Path,点击编辑,将platform-tools配置进去。如图:



Linux或Mac系统,可以在home路径下编辑.bash文件,将platform-tools配置进去。

配置好环境变量,就可以使用adb工具了。打开命令界面,输入 adb shell,进入到设备的控制台。如图:



使用cd命令进入到/data/data/com.example.hjw.databasetest/databases/目录下,并使用 ls 命令查看该目录里的文件,如图:



这个目录下有两个文件,一个是我们创建的BookStore.db,另一个BookStore.db-journal是为了让数据库能支持事务而产生的临时文件,一般这个文件大小为0字节。

借助Sqlite命令打开数据库,键入sqlite3后面加数据库名,如图:



查看数据库中的表,键入 .table 命令,如图:



此时的数据库有两张表:android_metadata是每个数据库都会自动生成的(无视它)。Book就是我们通过MyDatabaseHelper进行创建的。通过 .schema 命令查看它们的建表语句,如图:



键入.exit或.quit命令退出数据库的编辑,再键入.exit退出控制台。

6.4.2 升级数据库

在我们的MyDatabaseHelper类中的onUpgrade()方法用于对数据库的升级。

再添加一张Category表(id主键,分类名,分类代码)用于记录图书的分类,建表语句如下:

create table Category(
id integer primary key autoincrement,
category_name text,
category_code integer)


将这条语句添加到MyDatabaseHelper类中,代码如下:

public class MyDatabaseHelper extends SQLiteOpenHelper {

public static final String CREATE_TABLE="create table Book("
+ "id integer primary key autoincrement,"
+ "author text,"
+ "price real,"
+ "pages integer,"
+ "name text)";
public static final String CREATE_CATEGORY="create table Category("
+ "id integer primary key autoincrement,"
+ "category_name text,"
+ "category_code integer)";

private Context mContent;

public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContent=context;
}

@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContent, "Create succeeded", Toast.LENGTH_SHORT).show();
}

@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);
}
}


在onUpgrade()方法执行两条DROP语句,如果存在表就把删了,在调用onCreate()重新创建。

修改MainActivity中MyDatabaseHelper构造方法的版本号,代码如下:

public class MainActivity extends AppCompatActivity {

private MyDatabaseHelper dbHelper;

private Button btn_create_database;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper=new MyDatabaseHelper(this,"BookStore.db",null,2);
btn_create_database= (Button) findViewById(R.id.btn_create_database);
btn_create_database.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dbHelper.getWritableDatabase();
}
});
}
}


这里指定数据库的版本号为2,表示对数据库进行升级。重新运行程序,点击Create Database按钮,就会再次弹出创建成功。通过 adb shell 打开BookStore.db数据库,键入 .table 命令,如图:



键入.schema命令查看建表语句,如图:



由此可见Category表已经创建成功,升级完毕。

6.4.3 添加数据

对表中的数据4中操作:即CRUD。C代表添加(create)insert,R代表查询(Retrieve)select,U代表更新(Update)update,D代表删除(Delete)delete。

调用SQLiteOpenHelper的getReadableDatabase()或getWritableDatabase()方法可用于创建和升级数据库,返回SQLiteDatabase对象,对数据进行CRUD操作。

向数据库中的表添加数据,SQLiteDatabase提供了insert()方法,接收3个参数,第一个参数是表名,第二个参数值未指定添加数据的情况下给某写可为空的列自动赋值为NULL,直接传入null即可。第三个参数是ContentValues对象,提供了put方法重载(用于添加数据)。

添加数据,修改activity_main中的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<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/btn_insert_data"
android:textAllCaps="false"
android:text="Insert Data"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>


修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity {

private MyDatabaseHelper dbHelper;

private Button btn_create_database,btn_insert_data;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper=new MyDatabaseHelper(this,"BookStore.db",null,2);
btn_create_database= (Button) findViewById(R.id.btn_create_database);
btn_insert_data= (Button) findViewById(R.id.btn_insert_data);
...
btn_insert_data.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values=new ContentValues();
values.put("name","第一行代码");
values.put("author","郭霖");
values.put("pages",545);
values.put("price",54.5);
db.insert("Book",null,values);  //插入第一条数据
values.clear();
values.put("name","Android群英传");
values.put("author","徐宜生");
values.put("pages",421);
values.put("price",48.9);
db.insert("Book",null,values);  //插入第二条数据

}
});
}
}


运行程序,点击Insert Data按钮,数据添加成功。打开BookStore.db查看,输入select * from Book;(注意:;必须加上才能查询),如图:



我们可以看到刚才添加的数据。

6.4.4 更新数据

SQLiteDatabase中的update()方法,对数据进行更新,接收4个参数,第一个参数表名。第二个参数ContentValues对象(更新数据)。第三,四约束更新第几行数据,默认更新所有行。

更新书的价格,修改activity_main中的代码:

<?xml version="1.0" encoding="utf-8"?>
<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/btn_update_data"
android:textAllCaps="false"
android:text="Update Data"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>


修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity {

private MyDatabaseHelper dbHelper;

private Button btn_create_database,btn_insert_data,btn_update_data;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper=new MyDatabaseHelper(this,"BookStore.db",null,2);
btn_create_database= (Button) findViewById(R.id.btn_create_database);
btn_insert_data= (Button) findViewById(R.id.btn_insert_data);
btn_update_data= (Button) findViewById(R.id.btn_update_data);
...
btn_update_data.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values=new ContentValues();
values.put("price","48.9");
db.update("Book",values,"name = ?",new String[]{"第一行代码"});
}
});
}
}


运行程序,点击以下Update Data按钮,再次输入查询语句查询表中的情况,如图:



我们看到”第一行代码”的价格修改成功为48.9。

6.4.5 删除数据

SQLiteDatabase中的delete()方法,对数据进行删除,接收三个参数,第一个参数是表名。第二,三约束哪一行 ,默认删除所有。

修改activity_main中的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<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/btn_delete_data"
android:textAllCaps="false"
android:text="Delete Data"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>


修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity {

private MyDatabaseHelper dbHelper;

private Button btn_create_database,btn_insert_data,btn_update_data,btn_delete_data;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper=new MyDatabaseHelper(this,"BookStore.db",null,2);
btn_create_database= (Button) findViewById(R.id.btn_create_database);
btn_insert_data= (Button) findViewById(R.id.btn_insert_data);
btn_update_data= (Button) findViewById(R.id.btn_update_data);
btn_delete_data= (Button) findViewById(R.id.btn_delete_data);
...
btn_delete_data.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book","pages > ?",new String[]{"500"});
}
});
}
}


运行程序,点击一下Delete Data按钮,再次输入查询语句查看,如下:



这样超过500页的书,就删除了。

6.4.6 查询数据

SQL的全称是“结构化查询语言(Structured Query Language)”。

SQLiteDatabase提供了query()方法用于对数据库进行查询。这个方法的参数非常复杂,最少的也需要传入7个参数,第一个参数表名。第二个参数指定去查哪几列(不指定默认查询全部)。第三,第四个参数用于约束查询一行或某几行数据(不指定默认查询所有行)。第五个参数用于指定group by的列(不指定默认不操作)。第六个参数对group by之后的数据进行过滤(不指定不进行过滤)。第七个参数指查询结果的排序方式(不指定使用默认的排序方式)。参考下表:



调用query()方法后返回Cursor对象,查询到所有数据都从这个对象中取出。

修改activity_main.xml中的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<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/btn_query_data"
android:textAllCaps="false"
android:text="Query Data"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>


修改MainActivity中的代码如下:

package com.example.hjw.databasetest;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

private static final String TAG = "MainActivity";
private MyDatabaseHelper dbHelper;

private Button btn_create_database,btn_insert_data,btn_update_data,btn_delete_data,btn_query_data;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper=new MyDatabaseHelper(this,"BookStore.db",null,2);
btn_create_database= (Button) findViewById(R.id.btn_create_database);
btn_insert_data= (Button) findViewById(R.id.btn_insert_data);
btn_update_data= (Button) findViewById(R.id.btn_update_data);
btn_delete_data= (Button) findViewById(R.id.btn_delete_data);
btn_query_data= (Button) findViewById(R.id.btn_query_data);
...
btn_query_data.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
//查询表中的所有数据
Cursor cursor = db.query("Book", null, null, null, null, null, null);
if (cursor.moveToFirst()){
//遍历Cursor对象,取出数据并打印
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(TAG, "book name is : "+name);
Log.d(TAG, "book author is : "+author);
Log.d(TAG, "book pages is : "+pages);
Log.d(TAG, "book price is : "+price);
}while (cursor.moveToNext());
}
cursor.close();
}
});
}
}


这里查询所有数据(第一个参数Book,后面都为null),得到Cursor对象,调用moveToFirst()方法将数据指针移动到第一位,进入循环,取值。最后别忘了调用close()方法关闭Cursor对象。

运行点击Query Data按钮,打印logcat如下:



6.4.7 使用SQL操作数据库

使用SQL操作数据库。

添加数据

db.execSQL("insert into Book(name,author,pages,price) values(?,?,?,?)",
new String[]{"第一行代码","郭霖","545","54.5"});

db.execSQL("insert into Book(name,author,pages,price) values(?,?,?,?)",
new String[]{"Android群英传","徐宜生","489","48.9"});


更新数据

db.execSQL("update Book set price = ? where name = ?",new String[]{"48.9","第一行代码"});


删除数据

db.execSQL("delete from Bool where price > ?",new String[]{"500"});


查询数据

db.rawQuery("select * from Book",null);


6.5 使用LitePal操作数据库

新建LitePalTest项目。

6.5.1 LitePal简介

LitePal是一款开源的Android数据库框架,采用对象关系映射(ORM)模式,对常用的数据库的一些功能进行封装,不用编写一行SQL语句,实现建表以及CURD操作。

LitePal主页有使用文档,地址:https://github.com/LitePalFramework/LitePal

6.5.2 配置LitePal

使用LitePal第一步,添加依赖:

compile 'org.litepal.android:core:1.5.1'


配置litepal.xml文件。右键app/src/main目录—>New—>Directory,创建assets目录,在这个目录下新建litepal.xml文件,编写内容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<litepal>
<dbname value="BookStore"></dbname>
<version value="1"></version>

<list>
//所有的映射模型
</list>

</litepal>


配置LitePalApplication,修改AndroidManifest.xml中的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.hjw.litepaltest">

<application
android:name="org.litepal.LitePalApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
</application>

</manifest>


LitePal配置完成。

6.5.3 创建和升级数据库

将DatabaseTest中的activity_main.xml文件复制到LitePal项目中来。

对象关联映射(ORM)模式:编程语言是面向对象语言,数据库是关系型数据库,将面向对象语言和面向关系的数据库建立一种映射关系。

使用LitePal,面向对象思维来实现,定义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;
}

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;
}
}


接下来,我们将类添加到映射模型当中,修改litepal.xml文件,代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<litepal>
<dbname value="BookStore"></dbname>
<version value="1"></version>

<list>
<mapping class="com.example.hjw.litepaltest.Book"/>
</list>

</litepal>


配置我们的映射模型类,一定要使用完整的类名。

自动创建数据库,修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity {

private Button btn_create_database;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_create_database= (Button) findViewById(R.id.btn_create_database);
btn_create_database.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Connector.getDatabase();
}
});
}
}


Connector.getDatabase()是最简单的数据库操作。运行程序,点击Create Database按钮,通过adb查看数据库创建情况,如图:



使用sqlite3命令打开BookStore.db文件,键入.scheme查看建表语句,如图:



android_metadata无视它;table_schama是Litepal内部使用的,无视它;book表根据Book类中字段自动生成的。

LitePal升级数据库解决数据丢失的问题,添加想要修改的内容,版本号加1。

添加press(出版社),在Book类中添加,代码如下:

public class Book {
...
private String press;
...
public String getPress() {
return press;
}

public void setPress(String press) {
this.press = press;
}
}


在添加一张Category表,新建Category类,代码如下:

public class Category {

private int id;
private String categoryName;
private int categoryCode;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getCategoryName() {
return categoryName;
}

public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}

public int getCategoryCode() {
return categoryCode;
}

public void setCategoryCode(int categoryCode) {
this.categoryCode = categoryCode;
}
}


修改litepal.xml中的代码如下:

<?xml version="1.0" encoding="UTF-8" ?>
<litepal>
<dbname value="BookStore"></dbname>
<version value="2"></version>

<list>
<mapping class="com.example.hjw.litepaltest.Book"/>
<mapping class="com.example.hjw.litepaltest.Category"/>
</list>

</litepal>


重新运行程序,点击Create Database按钮,查看新的建表语句,如图:



book表中新增press列,category表也创建成功了,LitePal保存之前所有的数据,不用担心数据的丢失。

6.5.4 使用LitePal添加数据

LitePal添加数据:创建出模型类的实例,设置要保存的数据,调用save()方法。

LitePal进行表管理操作时不需要模型类有任何的继承结构,进行CRUD操作必须继承自DataSupport类才行。修改Book类中代码如下:

public class Book extends DataSupport{
...
}


向book表中添加数据,修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity {

private Button btn_create_database,btn_insert_data;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_create_database= (Button) findViewById(R.id.btn_create_database);
btn_insert_data= (Button) findViewById(R.id.btn_insert_data);
...
btn_insert_data.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Book book=new Book();
book.setAuthor("郭霖");
book.setPages(545);
book.setName("第一行代码");
book.setPrice(54.5);
book.setPress("图灵出版社");
book.save();
}
});
}
}


创建Book实例,调用set方法设置数据,最后调用book.save()进行保存。save()是从DataSupport类中继承而来的。

运行程序,点击Instert Data,添加成功。打开BookStore.db数据库,输入查询语句select * from book,如图:



可以看到,数据全部精确无误的添加成功了。

6.5.5 使用LitePal更新数据

最简单的更新方式已存储的对象重新设值,重新调用save()即可。

对象是否已存储,model.isSaved()方法才会返回true,一种情况已经调用model.save()添加数据,另一种情况是通过LitePal提供的查询API查出来。

通过第一种情况,修改MainActivity中的代码:

public class MainActivity extends AppCompatActivity {

private Button btn_create_database,btn_insert_data,btn_update_data;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_create_database= (Button) findViewById(R.id.btn_create_database);
btn_insert_data= (Button) findViewById(R.id.btn_insert_data);
btn_update_data= (Button) findViewById(R.id.btn_update_data);
...
btn_update_data.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Book book=new Book();
book.setAuthor("徐宜生");
book.setPages(436);
book.setName("Android群英传");
book.setPrice(43.6);
book.setPress("图灵出版社");
book.save();
book.setPrice(38.6);
book.save();
}
});
}
}


运行程序,点击Update Data按钮,再次键入查询语句,如图:



这种方法只能对已储存的对象操作,限制性比较大。

更加灵巧的更新方式,修改MainActivity中的代码:

public class MainActivity extends AppCompatActivity {

private Button btn_create_database,btn_insert_data,btn_update_data;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_create_database= (Button) findViewById(R.id.btn_create_database);
btn_insert_data= (Button) findViewById(R.id.btn_insert_data);
btn_update_data= (Button) findViewById(R.id.btn_update_data);
...
btn_update_data.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Book book=new Book();
book.setPrice(28.6);
book.setPress("未知");
book.updateAll("name = ? and author = ?","Android群英传","徐宜生");

}
});
}
}


首先创建Book实例,调用setPrice()和setPress()来更新数据,再调用updateAll()方法去执行更新操作。updateAll()指定一个条件约束,不指定则更新所以。

运行程序,点击Update Data按钮,再次键入查询,如图:



想要将数据更新成默认值,使用LitePal提供了setToDefault()方法,传入相应列的名字就好。比如:

Book book=new Book();
book.setToDefault("pages");
book.updateAll();


这段代码的意思是,将所有的书页数更新为0。

6.5.6 使用LitePal删除数据

LitePal删除数据的两种方式,第一种比较简单,调用过save()方法的对象,或者通过LitePal提供的API查出来的对象,直接调用已存储的delete()方法就可以了。

另一种删除数据的方式,修改MainActivity中的代码如下:

public class MainActivity extends AppCompatActivity {

private Button btn_create_database,btn_insert_data,btn_update_data,btn_delete_data;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_create_database= (Button) findViewById(R.id.btn_create_database);
btn_insert_data= (Button) findViewById(R.id.btn_insert_data);
btn_update_data= (Button) findViewById(R.id.btn_update_data);
btn_delete_data= (Button) findViewById(R.id.btn_delete_data);
...
btn_delete_data.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DataSupport.deleteAll(Book.class,"price < ?","30");
}
});
}
}


调用DataSupport.deleteAll()方法来删除数据,第一个参数指删除哪张表中的数据(Book.class),后面的参数指定约束条件。

运行程序,点击Delete Data按钮,查询表中的数据,如图:



价格低于30的书就被删除了,如果不指定约束条件,就意味着删除所有的数据。

6.5.7 使用LitePal查询数据

使用LitePal查询这张表中的所有数据,代码如下:

List<Book> books=DataSupport.findAll(Book.class);


修改MainActiviy中的代码:

public class MainActivity extends AppCompatActivity {

private static final String TAG = "MainActivity";
private Button btn_create_database,btn_insert_data,btn_update_data,btn_delete_data,btn_query_data;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_create_database= (Button) findViewById(R.id.btn_create_database);
btn_insert_data= (Button) findViewById(R.id.btn_insert_data);
btn_update_data= (Button) findViewById(R.id.btn_update_data);
btn_delete_data= (Button) findViewById(R.id.btn_delete_data);
btn_query_data= (Button) findViewById(R.id.btn_query_data);
...
btn_query_data.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
List<Book> books = DataSupport.findAll(Book.class);
for (Book book :books) {
Log.d(TAG, "book name is  "+book.getName());
Log.d(TAG, "book author is  "+book.getAuthor());
Log.d(TAG, "book pages is  "+book.getPages());
Log.d(TAG, "book price is  "+book.getPrice());
Log.d(TAG, "book press is  "+book.getPress());
}
}
});
}
}


运行程序,点击Query Data按钮,打印logcat日志信息:



除了findAll()方法,LitePal还提供了其它非常有用的API:

查询Book表中第一条数据代码如下:

Book firstBook=DataSupport.findFirst(Book.class);


查询Book表中最后一条数据代码如下:

Book lastBook=DataSupport.findLast(Book.class);


通过连缀查询指定更多的查询功能:

select()用于查询哪几列的数据,对应了SQL当中的select关键字。比如只查name和author这两列的数据,代码如下:

List<Book> books=DataSupport.select("name","author").find(Book.class);


where()用于指定查询条件的约束,对应了SQL当中的where关键字。比如只查询大于400页,代码如下:

List<Book> books=DataSupport.where("pages > ?","400").find(Book.class);


order()用于指定结果的排序,对应了SQL当中的order by关键字。比如将查询到的数价按从高到低排列,代码如下:

List<Book> books=DataSupport.oder("price desc").find(Book.class);


desc表示降序排列,asc或不写表示升序排列。

limit()用于指定查询结果的数量,比如只查询表中的前3条数据,代码如下:

List<Book> books=DataSupport.limit(3).find(Book.class);


offset()用于指定查询结果的偏移量,比如查询表中的第2 3 4 条数据,代码如下:

List<Book> books=DataSupport.limit(3).offset(1).find(Book.class);


5个方法任意连缀组合,完成复杂的操作:

List<Book> books=DataSupport.select("name","author","pages")
.where("pages > ?","400")
.order("pages")
.limit(10)
.offset(10)
.find(Book.class);


这段代码表示,查询Book表中第11-20条满足页数大于400页这个条件name,author和pages这3条数据,查询结果按升序排列。

LitePal支持原生SQL语句的查询:

Cursor cursor=DataSupport.findBySQL("select * from Bool where pages > ?and price < ?","500","30");


调用DataSupport.findBySQL()进行原生查询。返回Cursor对象,之前学的方法一一取值。

6.6 小结与点评

解决命令窗口,显示中文乱码的问题:在命令同行输入:

chcp 65001


对Android常用的数据持久化方式进行了详细的讲解,包括文件存储,SharedPreferences存储,以及数据库存储。

文件存储:简单文本数据或二进制数据。

SharedPreferences存储:存储键值对。

数据库:存储复杂的关系型数据库。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: