前面我们介绍了Android数据存储的两种方法:文件存储和SharedPreference存储,这一篇我们来学习一下android存储数据的另外一种方式——SQLite数据库存储。
1、SQlite数据库简介
现在的主流移动智能设备中,比如Android手机、iPhone手机,平板等都是使用SQLite数据库作为存储复杂数据的存储引擎。那么什么是SQLite数据库呢?
SQLite是D.Richard Hipp用C语言编写的开源嵌入式数据库引擎,它支持大多数的SQL92标准,并且可以在所有主要的操作系统上运行。SQLite由以下几个部分组成:SQL编译器、内核、后端以及附件。SQLite通过利用虚拟机和虚拟数据库引擎(VDBE),是调试、修改和扩展SQLite的内核变得更加方便。所有SQL语句都被编译成易读的、可以在SQLite虚拟机中执行的程序集。
SQlite数据库是一个轻量级的关系型数据库,不需要像其它关系型数据库一样需要安装,Android已经将SQLite数据库内置在系统中,内置的版本是3.0版本。SQlite支持标准的SQL语法,还支持ACID(数据库事务)原则,占用资源非常少,非常适合在移动设备中使用。
袖珍型的SQLite数据库就可以支持高达2TB大小的数据库,每个数据库都是以单个文件的形式存在,这些数据都是以B-Tree的数据结构形式存储在磁盘上。每一个数据库是一个文件,数据库中可以包含多个表,表中可以包含多个字段。
SQLite数据库支持NULL、INTEGER、REAL、TEXT和BLOB数据类型,分别代表空值、整型值、浮点值、字符串文本、二进制对象。SQLite采用动态数据类型,当某个值插入到数据库时,SQLite将会检查它的类型,如果该类型与关联的列不匹配,SQLite则会尝试将该值转换成该列的类型,如果不能转换,则该值将作为本身的类型存储,SQLite称这为“弱类型”。但有一个特例,如果是INTEGER PRIMARY KEY,则其他类型不会被转换,会报一个“datatype missmatch”的错误。简单的说就是:我们可以各种数据类型的数据保存到任何字段中而不用关心字段声明的数据类型。
下面是Android系统中SQLite数据库的几个关键类:
-
SQLiteOpenHelper:数据库抽象类,我们通过继承该类,获取数据库实例,然后可以重写数据库创建、更新版本方法和关闭数据库
-
SQLiteDatabase:数据库访问类:我们通过获取这个类的实例来对数据库做一些CRUD操作
-
Cursor:游标:类似于JDBC里的结果集
2、SQLiteOpenHelper创建数据库与版本管理
我们继承SQLiteOpenHelper这个类的时候必须要实现两个方法: onCreate(SQLiteDatabase sqLiteDatabase)和onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1),onCreate方法是我们第一次使用应用的时候生成数据表;onUpgrade方法是数据库版本发生变更的时候调用。还有必须要实现一个构造方法,下面是代码示例:
public class DBOpenHelper extends SQLiteOpenHelper {
private static String DB_NAME = "db_test.db";
private static int DB_VERSION = 1;
public DBOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/**
* 数据库第一次创建的时候调用
*
* @param sqLiteDatabase
*/
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
}
/**
* 数据库版本发生改变的时候调用
*
* @param sqLiteDatabase
* @param i
* @param i1
*/
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
上述代码启动会创建一个db_test.db的数据库文件,在data/data/包名/database目录下就可以看到创建的数据库文件,将其导出之后就可以用图形化工具查看。
3、SQLite数据库图形化工具
SQLite图形化工具很多,推荐使用SQLite expert,这是官网下载地址。这个图形化工具使用也很简单,直接将从Android Device Monitor导出的数据库文件导入就可以查看数据。这个可视化工具实用不是很复杂,就不在做很多的介绍了。
4、SQLite数据库使用
下面我们进行数据库的基本使用,在Android开发中,我们不需要写很多SQL语句,Android已经帮我们封装好了,我们直接调用就可以了。Android提供的CRUD操作的方法介绍:
数据增加:
insert(String table, String nullColumnHack, ContentValues values)
参数解析:第一个是数据表名;第二个是设置为null值的数据表名;第三个是插入的数据,用ContentValues封装。
数据删除:
delete(String table, String whereClause, String[] whereArgs)
参数解析:第一个是数据表名;第二个是where条件;第三个是约束
数据更新:
update(String table, ContentValues values, String whereClause, String[] whereArgs)
参数解析:第一个是数据表名;第二个是改变的数据;第三个是where条件;第四个是约束
数据查询:
query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal)
query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)
query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
参数解析:
table:表名称
colums:表示要查询的列所有名称集
selection:表示WHERE之后的条件语句,可以使用占位符
selectionArgs:条件语句的参数数组
groupBy:指定分组的列名
having:指定分组条件,配合groupBy使用
orderBy:y指定排序的列名
limit:指定分页参数
distinct:指定“true”或“false”表示要不要过滤重复值
Cursor:返回值,相当于结果集ResultSet
下面通过实现数据库的基本CRUD操作体会一下:
Activity代码:
package com.example.datasave;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Devin on 2016/7/20.
*/
public class SQLiteActivity extends AppCompatActivity {
private Button btn_sqlite_insert;
private Button btn_sqlite_delete;
private Button btn_sqlite_update;
private Button btn_sqlite_select;
private SQLiteDatabase mDatabase;
private DBOpenHelper mDBOpenHelper;
private static final String TAG = "SQLiteActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sqlite);
mDBOpenHelper = new DBOpenHelper(this);
mDatabase = mDBOpenHelper.getWritableDatabase();
btn_sqlite_insert = (Button) findViewById(R.id.btn_sqlite_insert);
btn_sqlite_insert.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
insert();
}
});
btn_sqlite_delete = (Button) findViewById(R.id.btn_sqlite_delete);
btn_sqlite_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
delete(5);
}
});
btn_sqlite_update = (Button) findViewById(R.id.btn_sqlite_update);
btn_sqlite_update.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
update(6);
}
});
btn_sqlite_select = (Button) findViewById(R.id.btn_sqlite_select);
btn_sqlite_select.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
select();
}
});
}
private void select() {
Cursor cursor = mDatabase.query("student", null, null, null, null, null, null);
List<StudentModel> students = new ArrayList<>();
while (cursor.moveToNext()) {
StudentModel model = new StudentModel();
model.setS_id(cursor.getInt(cursor.getColumnIndex("s_id")));
model.setName(cursor.getString(cursor.getColumnIndex("name")));
model.setSex(cursor.getString(cursor.getColumnIndex("sex")));
model.setGrade(cursor.getString(cursor.getColumnIndex("grade")));
students.add(model);
Log.i(TAG, "数据---->>" + model.toString());
}
cursor.close();
ToastUtils.showToast(this, students.toString());
}
private void update(int s_id) {
ContentValues values = new ContentValues();
values.put("grade", "2013级信计班");
mDatabase.update("student", values, "s_id=?", new String[]{"" + s_id + ""});
ToastUtils.showToast(this, "更新了s_id为" + s_id + "的数据");
}
private void insert() {
ContentValues values = new ContentValues();
values.put("name", "李四");
values.put("sex", "男");
values.put("grade", "2012级数应班");
mDatabase.insert("student", null, values);
ToastUtils.showToast(this, "插入数据成功");
}
private void delete(int position) {
mDatabase.delete("student", "s_id=?", new String[]{"" + position + ""});
ToastUtils.showToast(this, "删除了s_id为" + position + "的数据");
}
}
SQLiteOpenHelper代码:
package com.example.datasave;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* Created by Devin on 2016/7/20.
*/
public class DBOpenHelper extends SQLiteOpenHelper {
private static String DB_NAME = "db_test.db";
private static int DB_VERSION = 1;
private static final String TEST_TABLE = "CREATE TABLE student (s_id INTEGER PRIMARY KEY AUTOINCREMENT," +
"name VARCHAR(20),sex VARCHAR(10),grade VARCHAR(100))";
public DBOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/**
* 数据库第一次创建的时候调用
*
* @param sqLiteDatabase
*/
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(TEST_TABLE);
}
/**
* 数据库版本发生改变的时候调用
*
* @param sqLiteDatabase
* @param i
* @param i1
*/
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
布局文件非常简单,就不再贴代码了,下面是实现效果图:
由于是录制GIF,速度比较慢,这样就可以基本实现数据库的CRUD操作。
5、SQLite数据库事务
上面我们介绍了SQLite的基本操作,现在我们来介绍一下SQLite数据库的事务,首先,来了解一下什么是事务?
什么是事务?
数据事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。简单的说,就是多个操作捆绑到一起执行,只有所有的操作都成功了,事务才算执行完毕,才会提交,只要有一个操作没有完成,事务都会回滚,所有的操作都不回提交。举一个例子就是:张三向李四转账1000块,张三的账户上要减少1000块,李四的账户上要增加1000块,只有两个操作都完成,事务才会提交。假如张三的账户减少了1000块,但是李四的账户上没有增加1000块;或者张三的账户没有减少1000块,但是李四的账户增加了1000块,这显然都是不可以的。
比如下面的例子代码:
/**
* 事务测试
*/
public void payment() {
SQLiteDatabase db = mDBOpenHelper.getWritableDatabase();
//开启事务
db.beginTransaction();
try {
db.execSQL("update person set amount=amount-10 where personid=?", new Object[]{1});
db.execSQL("update person set amount=amount+10 where personid=?", new Object[]{2});
//设置事务标志为成功,当结束事务时就会提交事务
db.setTransactionSuccessful();
} catch (Exception e) {
e.printStackTrace();
} finally {
//结束事务
db.endTransaction();
}
}
简单的说就是:只有写在事务里的所有数据库操作都成功,事务才会提交,否则,事务回滚回原来的状态。在data/data/<包名>/database/目录下,我们创建数据库的时候也会创建一个xxx.db-journal让数据库支持事务的临时日志文件。
6、SQLite数据库二进制大文件保存和读取
一般情况下,我们很少在数据库里面存储大文件,视频、音频、图片等我们都是保存路径,但是有些特殊情况下,我们需要将大文件保存到数据库里面,下面我们就实现一个简单的例子:
SQLiteDatabase db = mDBService.getWritableDatabase(); // 得到数据库
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
((BitmapDrawable) iv.getDrawable()).getBitmap().compress(
CompressFormat.PNG, 100, baos);//压缩为PNG格式,100表示跟原图大小一样
Object[] args = new Object[] {baos.toByteArray() };
db.execSQL(INSERT_SQL, args);
baos.close();
db.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//3.从数据库中取图片
public void getPhoto() {
String SELECT_SQL = "SELECT photo FROM launcher";
ImageView appIcon = (ImageView) v.findViewById(R.id.appicon);//v是我在类中定义的一个view对象,跟前面保存图片一样
byte[] photo = null;
mDBService = new DBService(getContext());
SQLiteDatabase db = mDBService.getReadableDatabase();
Cursor mCursor = db.rawQuery(SELECT_SQL, null);
if (mCursor != null) {
if (mCursor.moveToFirst()) {//just need to query one time
photo = mCursor.getBlob(mCursor.getColumnIndex("photo"));//取出图片
}
}
if (mCursor != null) {
mCursor.close();
}
db.close();
ByteArrayInputStream bais = null;
if (photo != null) {
bais = new ByteArrayInputStream(photo);
appIcon.setImageDrawable(Drawable.createFromStream(bais, "photo"));//把图片设置到ImageView对象中
}
//appIcon显示的就是之前保存到数据库中的图片
}
7、SimpleCursorAdapter绑定数据库数据
这种比较少用,我们就实现一个简单的例子:
Map<String, String> map = new HashMap<String, String>();
ListView listView = (ListView) this.findViewById(R.id.list);
Cursor cursor = getContentResolver().query(
ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
if(cursor != null){
startManagingCursor(cursor);
}
ListAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1, cursor,
new String[] { PhoneLookup.DISPLAY_NAME },
new int[] { android.R.id.text1 });
listView.setAdapter(adapter);
stopManagingCursor();
SQLite数据库就简单介绍到这里,网络上有很多好的数据库操作框架,后面再介绍。