Android主要提供了3种方式用于实现数据持久化功能,分别是文件存储、SP存储和数据库存储。除了这3种方式,你还可以将数据保存在手机的SD中,不过使用文件、SP或者数据库来保存数据相对简单,更加安全。
1. 文件存储
文件存储不对存储的内容进行任何的格式化处理,所有的数据原封不动保存在文件当中,因而适合存储简单的文本数据或二进制数据。
如果你想使用文件存储的方式保存一些复杂的文本数据,就需要定义一套自己的格式规范,以便将数据从文件中重新解析出来。
1.1 将数据存储到文件中
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();
}
}
}
说明:
1. Context类中提供了一个openFileOutput方法,可以将数据存储到指定的文件中。
2. 它接收两个参数:第一个参数是文件名,只是文件名,不包含路径,默认保存到/data/data//files目录下;第二个参数是文件的操作模式,主要有两种模式:MODE_PRIVATE和MODE_APPEND.其中MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容;而MODE_APPEND则表示如果该文件已经存在,就往文件中追加内容,不存在就创建新文件。
3. 文件的操作模式本来还有另外两种:MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,这两种模式允许其他的应用程序对我们程序中的文件进行读写操作,不过太危险,在Android 4.2被废弃。
4. openFileOutput返回的是一个FileOutputStream对象,然后借助它构建出一个OutputStreamWriter对象,接着再使用OutputStreamWriter构建出一个BufferedWriter对象,这样就可以通过BufferedWriter把文本内容写入到文件中了。
1.2 数据存储到文件中的示例:
- 创建项目,修改activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.fkq.filepersistencetest.MainActivity">
<EditText
android:id="@+id/edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:hint="Type someing here" />
</RelativeLayout>
- 修改MainActivity:
public class MainActivity extends AppCompatActivity {
private EditText editText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = (EditText) findViewById(R.id.edit);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e("MainActivity","进来了吗");
String inputText = editText.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();
}
}
}
}
说明:页面展示EditText,输入内容后,点击Back键,执行onDestroy方法,方法中获取EditText中的内容,并调用save方法将文本内容保存到文件中。
- 查看数据保存到文件中的结果:
AS-Tools-Android-Android Device Monitor-File Explorer-点击左边的项目包名-查看右侧路径/data/data/com.example.filepersistencetest/files/目录,可以看到生成一个data文件-点击导出按钮-用记事本打开即可查看。
1.3 从文件中读取数据
Context类中提供了一个openFileInput方法,用于从文件中读取数据。它只接收一个参数,即要读取的文件名,然后系统会自动到/data/data/package name/files目录下去加载这个文件,并返回一个FileInputStream对象,得到了这个对象之后再通过java流的方式将数据读取出来。
private 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();
}
说明:
1. 首先通过openFileInput方法获取到一个FileInputStream对象。
2. 然后借助FileInputStream对象构建出一个InputStreamReader对象。
3. 接着借助InputStreamReader构建出一个BufferedReader对象。
4. 然后通过BufferedReader进行一行行读取,把文件中所有的文本内容读取并存放在一个StringBuilder对象中。
5. 最后将读取到的内容返回就可以了。
1.4 从文件中读取数据的示例
- 修改MainActivity:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = (EditText) findViewById(R.id.edit);
String inputText = load();
if (!TextUtils.isEmpty(inputText)) {
editText.setText(inputText);
editText.setSelection(inputText.length());
Toast.makeText(this, "还原文本成功", Toast.LENGTH_SHORT).show();
}
}
private 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();
}
说明:
1. onCrate方法中调用load方法读取文件中的存储的文本内容。
2. 读取的内容不为null,就将内容填充到EditText里,并调用setSection方法将输入光标移动到文本的末尾位置以便于继续输入,然后弹出一句还原成功的提示。
1.5 文件存储的核心
利用Context类中提供的openFieInput和openFileOutput方法,之后就是利用Java的各种流来进行读写操作。
2. SharedPreferences存储(下文简称SP存储)
SP是使用键值对的方式存储数据的,且支持多种不同的数据类型存储。
2.1 要想使用SP存储数据,首先要获得SP对象,有3中方法:
Context类中的getSharedPreferences方法:
此方法接收两个参数,第一个参数用于指定SP文件的名称,如果指定的文件不存在则会创建一个,SP文件都存放在/data/data/packname/shared_prefs目录下;第二个参数用于指定操作模式,目前只有MODE_PRIVATE这一种模式可选,和直接传入0效果相同,表示只有当前的应用程序才可以对SP文件进行读写。
Activity类中的getPreferences方法:
此方法只接收一个操作模式参数,因为使用这个方法会自动将当前活动的类名作为SP的文件名。
PreferenceManager类中的getDefaultSharedPreferences方法
此方法是一个静态方法,它接收一个Context参数,并自动使用当前应用程序的包名作为前缀来命名SP文件。
2.2 得到SP对象后,向SP文件中存储数据三步骤:
- 调用SP对象的edit方法来获取一个SharedPreferences.Editor对象。
- 向SharedPreferences.Editor对象中添加数据,比如布尔型数据就使用putBoolean方法,添加字符串数据就使用putString方法。
- 调用apply方法将添加的数据提交,从而完成数据存储操作。
SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
editor.putString("name", "Tom");
editor.putInt("age", 28);
editor.putBoolean("married", false);
editor.apply();
2.3 从SP中读取数据
SP对象中提供了一系列的get方法,用于对存储的数据进行读取,每种get方法都对应了SharedPreferences.Editor中的一种put方法,比如读取取布尔型数据就用getBoolean方法,读取字符串数据就用getString方法。
这些get方法接收两个参数,第一个参数是键,传入存储数据时使用的键就可以得到响相应的值了;第二个参数是默认值,即表示当传入的键找不到对应的值时会以什么样的默认值进行返回。
SharedPreferences preferences = getSharedPreferences("data",MODE_PRIVATE);
String name = preferences.getString("name","");
int age = preferences.getInt("age",0);
boolean married = preferences.getBoolean("married",false);
2.4 实现记住密码功能的案例
创建项目,创建LoginActivity,编辑登录activity_login布局,并修改配置文件如下:
activity_login:
<?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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="帐号:"
android:textSize="20dp" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:hint="请输入帐号" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="#20000000"></View>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="密码:"
android:textSize="20dp" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:hint="请输入密码" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:background="#20000000"></View>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="30dp"
android:padding="10dp">
<CheckBox
android:id="@+id/cb"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="记住密码"
android:textSize="20dp" />
</LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:text="登录" />
</LinearLayout>
配置文件:
<activity android:name=".MainActivity"></activity>
<activity android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
LoginActivity:
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
@Bind(R.id.et_username)
EditText etUsername;
@Bind(R.id.et_password)
EditText etPassword;
@Bind(R.id.cb_checkbox)
CheckBox cbCheckbox;
@Bind(R.id.bt_login)
Button btLogin;
private SharedPreferences pref;
private SharedPreferences.Editor editor;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
ButterKnife.bind(this);
checkIsRemember();
btLogin.setOnClickListener(this);
}
private void checkIsRemember() {
pref = PreferenceManager.getDefaultSharedPreferences(this);
boolean isRemember = pref.getBoolean("remember_password", false);
if (isRemember) {
//将帐号和密码都设置到文本框中
String account = pref.getString("account", "");
String password = pref.getString("password", "");
etUsername.setText(account);
etPassword.setText(password);
cbCheckbox.setChecked(true);
}
}
@Override
public void onClick(View v) {
String account = etUsername.getText().toString();
String password = etPassword.getText().toString();
//如果帐号是admin,密码是123456,就认为是登录成功
if (account.equals("admin") && password.equals("123456")) {
editor = pref.edit();
if (cbCheckbox.isChecked()) {//检查复选框是否被选中
editor.putBoolean("remember_password", true);
editor.putString("account", account);
editor.putString("password", password);
} else {
editor.clear();
}
editor.apply();
startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish();
} else {
Toast.makeText(this, "帐号或密码错误", Toast.LENGTH_SHORT).show();
}
}
}
说明:
1. 首先在onCreate方法中获取SharedPreferences对象,然后调用它的getBoolean方法获取remember_password这个键对应的值。一开始当然不存在对应的值,所以会使用默认值false,这样什么都不会发生。
2. 接着在登录成功之后,会调用CheckBox的isChecked方法来检查复选框是否被选中,如果被选中,则表示用户想要记住密码,这时将remember_password设置为true,然后把account和password对应的值都存入到SP文件当中并提交。如果没有被选中,就简单地调用一下clear方法,将SP文件中的数据全部清除掉。
3. 当用户选中了记住密码复选框,并成功登录一次后,remember_password键对应的值就是true了,这个时候如果再重新启动登录页面,就会从SP文件中将保存的帐号和密码都读取出来,并填充到文本输入框中,然后把记住密码复选框选中,这样就完成了记住密码的功能了。
4.当然实际的项目中还需要对密码进行加密处理,否则不安全。
3. SQLite数据库存储
SQLite数据库不仅支持标准的SQL语法,还遵循了数据库的ACID事务。
相对文件存储和SP存储,SQLite数据库适合存储大量复杂的关系型数据,比如手机的短信等等。
3.1 创建数据库
Android 提供了SQLiteOpenHelper帮助类,借助这个类可以非常简单地对数据库进行创建和升级。
SQLiteOpenHelper是一个抽象类,想使用它,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper中有两个抽象方法,分别是onCreate和onUpgrade,利用这个两个方法实现创建、升级数据库的逻辑。
SQLiteOpenHelper还有两个非常重要的实例方法:getReadableDatabase和getWriteableDatabase。这两个方法都可以创建或打开一个现有的数据库(如果存在则打开,如果不存在则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase方法则将出现异常。
SQLiteOpenHelper中有两个构造方法可重写,一般使用参数少一点的即可。这个构造方法一共接收4个参数,第一个参数是Context,第二个参数是数据库名,第三个参数允许我们在查询数据的时候返回一个自定的Cursor,一般都是传入null,第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。构建出SQLiteOpenHelper的实例后,再调用它的getReadableDatabase或getWritableDatabase方法就能够创建数据库了,数据库文件存放在/data/data/packname/databases目录下。此时onCreate方法也会执行,通常会在这里处理一些创建表的逻辑。
3.2 DatabaseTest项目示例
- 新建MyDatabaseOpenHelper类,继承SQLiteOpenHelper:
/**
* Created by FuKaiqiang on 2017/5/9.
*/
public class MyDatabaseOpenHelper 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 MyDatabaseOpenHelper(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, "数据库和表BOOK创建成功", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
说明:
1. SQLite数据类型很简单,integer表示整型,real表示浮点型,text表示文本类型,blob表示二进制类型,此外上述建表语句还使用了primary key将id列设为主键,并用autoincrement关键字表示id列是自增长的。
2. 我们把建表语句定义成了一个字符串常量,然后在onCreate方法中又调用了SQLiteDatabase的exexSQL方法去执行这条建表语句,并弹出Toast,保证在数据库创建完成的同时还能成功创建Book表。
- 修改MainActivity添加点击事件创建数据库:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private MyDatabaseOpenHelper databaseOpenHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
databaseOpenHelper = new MyDatabaseOpenHelper(this, "BookStore.db", null, 1);
Button button = (Button) findViewById(R.id.bt_createdb);
button.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_createdb:
databaseOpenHelper.getWritableDatabase();
break;
}
}
}
说明:
- 在onCreate方法中构建一个MyDatabaseHelper对象,并且通过构造函数的参数将数据库名指定为BookStore.db,版本号为1,然后在点击事件里面调用了getWritableDatabase方法。
- 当第一次点击按钮时,会检测到当前程序中并没有BookStore.db这个数据库,于是会调用MyDatabaseHelper中的onCreate方法,这样Book表也得到了创建。再次点击按钮,会发现已经存在BookStore.db数据库了,因此不会再创建一次。
- 利用adb shell 来对数据库进行查看;adb 是Android SDK自带的调试工具,它在Android SDK的platform-tools目录下,如果想要在命令行中使用这个工具,就要先配置它的环境变量:把sdk下的platform-tools路径配置到path变量下。
- adb shell 进入设备的控制台 cd /data/data/com.example.databasetest/databases 进入目录
- ls 查看该目录下的文件 sqlite3 BookStore.db 打开数据库
- .table查看数据库中有哪些表 .schema 查看建表语句
- .exit或.quit 退出数据库的编辑 再输入exit就可以退出设备控制台
3.3 升级数据库
细心的你一定发现,MyDatabaseHelper中还有一个空方法onUpgrade,其实它就是用来进行数据库升级用的。
目前DatabaseTest项目中已经有一张Book表用于存放书的各种详细数据,现在需要再添加一张Category表用于记录图书的分类,这个时候就需要升级数据库。
比如Category表中有id(主键)、分类名和分类代码这个几个列,建表语句就可以写成:
create table Category(
id integer primary key autoincrement,
category_name text,
category_code integer)
- 接下来我们添加以上表到MyDatabaseHelper中:
public class MyDatabaseOpenHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "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 mContext;
public MyDatabaseOpenHelper(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);
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "数据库和表BOOK创建成功", 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);
}
}
说明:
- 我们点击创建数据库的按钮,没有弹出创建数据库和表成功的提示,是因为数据库只会创建一次,已经存在的情况下,再点击也不会创建数据库,而不创建数据库,就不会执行MyDatabaseOpenHelper类中的onCreate方法,CATEGORY表就不会被创建。
- 解决方法就是利用onUpgrade方法,对MyDatabaseOpenHelper进行升级,代码如下:
MyDatabaseOpenHelper:
@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);
}
MainActivity:
databaseOpenHelper = new MyDatabaseOpenHelper(this, "BookStore.db", null, 2);
说明:
- 我们在onUpgrade方法中执行了两条DROP语句,如果发现数据库中已经存在BOOK表或Category表,就将这两张表删除掉,然后再调用onCreate方法重新创建,之所以删除是因为在onCreate方法中创建表时如果发现表已经存在就会直接报错,所以在这里进行删除操作。
- 为了让onUpgrade方法得到执行,修改数据库的版本号为2,表示对数据库进行升级。
- 为了验证依然选择adb shell命令处理。
3.4 添加数据
对数据的操作无非4中:CRUD。C:Create增加、R: Retrieve查询、U:Update更新、D:Delete删除。
每一种操作对应一种SQL命令:添加数据用insert、查询数据用select、更新数据使用update、删除数据用delete。
Android提供了辅助方法,不用SQL语句,也能完成CRUD操作。
SQLiteOpenHelper的getWritableDatabase和getReadableDatabase返回一个SQLiteDatabase对象,借助这个对象可以进行CRUD操作。
databaseOpenHelper = new MyDatabaseOpenHelper(this, "BookStore.db", null, 2);
SQLiteDatabase db = databaseOpenHelper.getWritableDatabase();
ContentValues values = new ContentValues();
//开始组装第一条数据
values.put("name","I am fkq");
values.put("author","fkq");
values.put("pages",454);
values.put("price",16.96);
db.insert("Book",null,values); //插入第一条数据
values.clear();
//开始组装第二条数据
values.put("name","I am Tom");
values.put("author","fkq");
values.put("pages",500);
values.put("price",19);
db.insert("Book",null,values);
说明:
- SQLiteDatabase的insert方法,接收3个参数:第一个参数表名,第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值null,传入null即可,第三个参数是一个ContentValues对象,它提供了一系列的put方法重载,用于向ContentValues中添加数据。
- id 自增长不用组装,这里插入的是两条数据,在第一次插入完数据后,要把ContentValues清空。
- 查看是否已经把数据插入到表中,可以使用adb调试工具,也可以使用可视化工具SQLite Export Professional,先导出数据库文件,然后用可视化工具打开查看即可。
- 在使用adb工具进行查看表结构的时候,输入SQL语句切记别忘记最后以”;”分号结尾。
3.5 更新数据
- 利用update方法更新数据,方法接收4个参数:第一个参数是表名;第二个参数是ContentValues对象,要把更新数据在这里组装进去;第三个和第四个参数用于约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。
SQLiteDatabase db_update = databaseOpenHelper.getWritableDatabase();
ContentValues values_update = new ContentValues();
values_update.put("price", 100);
db_update.update("Book", values_update, "name=?", new String[]{"I am fkq"});
说明:
- 构建一个ContentValues对象,并且指定一组数据,就是把价格修改为100.
- update方法的第三个参数相当于SQL语句的where部分,表示更新所有name等于?的行,问号是一个占位符。第四个参数中数组中的内容为这个占位符指定了相应的内容。意思就是把名子为“I am fkq”这本书的价格修改为100.
3.6 删除数据
- SQLiteDatabase中提供了一个delete方法,专门用于删除数据。方法接收3个参数:第一个参数仍然是表名,第二、第三个参数又是用于约束删除某一行或者某几行的数据,不指定的话默认就是删除所有行。
SQLiteDatabase db_delete = databaseOpenHelper.getWritableDatabase();
db_delete.delete("Book", "price>?", new String[]{"99"});
3.7 查询数据
SQL的全程是Structured Query Language 结构化查询语言,重心是查询!
SQLiteDatabase中提供了query方法对数据库进行查询。
query方法接收7个参数:
第一个参数表名;
第二个参数用于指定查询哪几列,如果不指定默认查询所有列;
第三、第四个参数用于约束查询某一行或某几行的数据,如果不指定则默认查询所有航的数据。
第五个参数用于指定需要去group by的列,不指定则表示不对查询结构进行group by操作。
第六个参数用于对group by 之后的数据进行进一步的过滤,不指定则表示不进行过滤。
第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。
- query方法后返回一个Cursor对象,查询到的所有的数据都是从这个对象中取出。
SQLiteDatabase db_query = databaseOpenHelper.getWritableDatabase();
Cursor cursor = db_query.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 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();
说明:查询“Book”表中的数据,返回一个游标Cursor;让游标移动到第一行,根据各列的名字取出对应的值,并打印出来。
3.8. 使用SQL操作数据库
- 添加数据:
db.execSQL("insert into Book(name,author,pages,price) values(?,?,?,?)",
new String[]{"I am baby", "I am body", "456", "16.90"});
- 更新数据:
db_update.update("Book", values_update, "name=?", new String[]{"I am fkq"});
- 删除数据:
db_delete.execSQL("delete from Book where pages>?", new String[]{"500"});
- 查询数据:
db_query.rawQuery("select * from Book", null);
4. 使用LitePal操作数据库
LitePal是一款开源的Android数据库框架,它采用了对象关系映射(ORM)的模式,并将我们平时开发最常用到的一些数据库功能进行了封装,不用编写一行SQL语句就可以完成各种建表和增删改查的操作。
LitePal采取的是对象关系映射(ORM)的模式,那什么是对象关系映射呢?简单点说:我们的编程语言是面向对象语言,而使用的数据库是关系型数据库,那么将面向对象的语言和面向对象的数据库之间建立一种映射关系,这就是对象关系映射。
4.1 LitePal创建数据库
- 添加依赖
compile 'org.litepal.android:core:1.3.2'
- 创建litepal.xml文件,目录:app/src/main/asserts
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="BookStore"></dbname>
<version value="1"></version>
<list>
</list>
</litepal>
说明:
1. 当我在创建这个文件的时候,并不能直接创建在它的目录下,方法:New-Android resource file 修改Resource type 为XML - 输入名字,然后res目录下会出现一个XML文件夹,文件夹下面是litepal.xml,然后把文件剪切到asserts目录下,删除XML空文件夹。
2. dbname标签用于指定数据库名,version标签用于指定数据库版本号,list标签用于指定所有的映射模型。
- 配置LitePalApplication在配置文件:
android:name="org.litepal.LitePalApplication"
说明:只有配置了application才能让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;
}
}
说明:一个典型的javabean类,类名代表的是表名,字段对应表中的每一个列,这就是对象关系映射最直观的体验。
- 修改litepal.xml:
<list>
<mapping class="com.fkq.litepaltest.Book"></mapping>
</list>
说明:使用mapping配置映射模型类,注意一定要使用完整的类名。不管有多少模型类需要映射,都使用相同方式配置在list标签下。
- 修改MainActivity代码,点击事件里面:
Connector.getDatabase();
说明:创建数据库最简单的操作。
4.2 升级数据库
LitePal升级数据库不会像SQLiteOpenHelper升级数据库需要先把之前的drop掉,然后再重新创建。
LitePal升级数据库非常简单,只需要改你想改的任何内容,然后将版本号加1就行了。
向Book表添加一个press(出版社)列;向数据库添加一张Category表:
Book表添加:
private String press;
public String getPress() {
return press;
}
public void setPress(String press) {
this.press = press;
}
创建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;
}
}
版本号加1,添加新的模型类:
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="BookStore"></dbname>
<version value="2"></version>
<list>
<mapping class="com.fkq.litepaltest.Book"></mapping>
<mapping class="com.fkq.litepaltest.Category"></mapping>
</list>
</litepal>
4.3 添加数据
- 只需要创建出模型类的实例,再将所有要存储的数据设置好,最后调用下save方法就可以了。
- LitePal进行CRUD操作,必须继承DataSupport类。
Book类:
public class Book extends DataSupport {}
MainActivity:
Book b = new Book();
b.setName("Tom is a book!");
b.setAuthor("fkq");
b.setPages(500);
b.setPrice(10);
b.setPress("unknown");
b.save();
4.4 更新数据
对于LitePal来说,对象是否已存储就是根据调用model.isSaved方法的结果来判断的,返回true就表示已存储,返回false就表示未存储。
只有两种情况下返回true:一种情况是已经调用过model.save方法添加数据,一种情况是model对象是通过LitePal提供的查询API查出来的,由于是从数据库查出来的,所以会被认为是已存储的对象。
更新数据的第一种方式:
Book b = new Book();
b.setName("Tom is a book!");
b.setAuthor("fkq");
b.setPages(500);
b.setPrice(10);
b.setPress("unknown");
b.save();
b.setPrice(20);
b.save();
说明:只能针对save过的对象进行操作,限制性太大。
- 更新数据的第二种方式:
Book b_update = new Book();
b_update.setPrice(14.95);
b_update.setPress("Green");
b_update.updateAll("name = ? and athor = ?","Tom is a book!","fkq");
说明:set指定的是要更新的内容;updateAll是更新的条件限制,不指定就代表对说有的数据生效。需要注意的是当我们想更新数据为默认值的时候,要用下面方法设置:
Book b_update = new Book();
b_update.setToDefault("pages");
b_update.updateAll();
4.5 删除数据
已经存储的对象调用delete方法即可,下面介绍另一种:
DataSupport.deleteAll(Book.class, "price<?", "50");
4.6 查询数据
4.6.1.查询表中所有数据:
List<Book> books = DataSupport.findAll(Book.class);
for (Book book : books) {
Log.e("MainActivity", "book name is" + book.getName());
Log.e("MainActivity", "book author is" + book.getAuthor());
Log.e("MainActivity", "book pages is" + book.getPages());
Log.e("MainActivity", "book price is" + book.getPrice());
Log.e("MainActivity", "book press is" + book.getPress());
}
4.6.2.查询表中第一条数据:
Book firstbook = DataSupport.findFirst(Book.class);
4.6.3.查询表中最后一条数据:
Book lastbook = DataSupport.findLast(Book.class);
4.6.4.select方法用于指定查询哪几列的数据,对应了SQL当中的select关键字。比如只查name和author这两列的数据:
List<Book> books = DataSupport.select("name","author").find(Book.class);
4.6.5.where方法用于指定查询的约束条件,对应了SQL当中的where关键字。比如只查页数大于400的数据:
List<Book> books_where = DataSupport.where("pages>?", "400").find(Book.class);
4.6.6.order方法用于指定结果的排序方式,对应了SQL当中的order by 关键字。比如将查询结果按照书价从高到低排序:
List<Book> books_order = DataSupport.order("price desc").find(Book.class);
说明:desc:降序;asc或者不写表示升序。
4.6.7.limit方法用于指定查询结果的数量,比如只查询表中的前3条数据:
List<Book> books_limit = DataSupport.limit(3).find(Book.class);
4.6.8.offset方法用于指定查询结果的偏移量,比如查询表中的第2条、第3条、第4条数据:
List<Book> books_offset = DataSupport.limit(3).offset(1).find(Book.class);
说明:由于limit(3)查询到的是前3条数据,再加上offset(1)进行一个位置的偏移,就能实现查询第2条、第3条、第4条数据的功能。
4.6.9.对以上5个方法进行任意的连缀组合:
List<Book> books = DataSupport.select("name", "author", "pages")
.where("pages>?", "400")
.order("pages")
.limit(10)
.offset(10)
.find(Book.class);
4.6.10.上述API满足不了的时候,支持SQL查询,利用findBySQL方法:
Cursor c = DataSupport.findBySQL("select * from Book where pages > ? and price < ?","400","20");