前言
不知不觉的Android基础系列已经写了十三篇了,这是第十四篇~上一篇blog记录了Android中的一种数据存储方案,即共享参数(Sharedpreferences)的使用(处女男学Android(十三)---Android 轻量级数据存储之SharedPreferences)。最近初学如何在Android中应用SQLite,写了一个基于ListView的增删查的小例子,本篇blog就记录一下我学习到的如何在Android中操作SQLite持久化客户端数据。
初始化SQLite
关于SQLite的基础知识本篇就不做介绍了,我们只需知道它也是一个关系型的轻量级数据库,并且是嵌入式的数据库引擎,较适用于移动设备的数据存储,详情可参考百科:http://baike.baidu.com/link?url=_RNKz-r1FBwEm4iVvyxLQzCuKRdR12RrHNtUUa2nhSpILvUyT3g8jxVMbQzWmRHAUaRPYBem04hwMyom3kVx0a
本节记录初始化,那无非就是建库和建表了,下面先看这样一段初始化代码:
package com.wl.cigrec.dao; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class DBHelper extends SQLiteOpenHelper { // 数据库名 private static final String DB_NAME = "mycigrec.db"; // 数据库版本 private static final int VERSION = 1; public DBHelper(Context context) { super(context, DB_NAME, null, VERSION); // TODO Auto-generated constructor stub } @Override public void onCreate(SQLiteDatabase db) { // TODO Auto-generated method stub String sql1 = "create table t_order(" + "id integer primary key autoincrement," + "count integer ," + "money real,date date,balance real,orderid varchar(20))"; String sql2 = "create table t_order_detail(" + "id integer primary key autoincrement," + "cigname varchar(20),cigprice real,cigcount integer,orderid varchar(20))"; db.execSQL(sql1); db.execSQL(sql2); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO Auto-generated method stub System.out.println("------onUpgrade called------" + "oldVersion-->" + oldVersion + "," + "newVersion-->" + newVersion); } }
这就是初始化数据库的代码了,完全参考官方文档中的示例代码,下面逐行解释一下重点内容。
Line 7 定义了一个DB帮助类并且继承了SQLiteOpenHelper类,这个类是Android提供的管理数据库的工具类,封装了很多便捷操作DB的方法,实际开发中我们一般都会选择去扩展SQLiteOpenHelper去初始化我们自己的DB,所以我这里就无视了那些基础的API方法,尽管SQLiteOpenHelper的底层依旧是使用那些基础API。
Line 15 调用了父类的带有4个参数的构造方法,看一下文档中的解释:
context参数不必多说,用于打开或创建数据库。name参数是数据库文件的文件名。factory参数用于创建cursor对象,一般默认为NULL。最后一个version参数是我们人为指定的数据库版本号,规定从1开始,否则会抛异常,可以参考源码的第100行:
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
Line 20
初始化数据库时会回调onCreat(SQLiteDatabase db)方法,一般我们会在onCreat方法中创建数据表。
Line 33
当数据库版本变更时回调该方法,比如升级软件时需要更新表结构,那我们就会在这个方法中写一些alter table的语句去执行,其实个人认为这个方法略显鸡肋,我更倾向于重新创建数据库文件。
以上就是我们的初始化工作了,完成了建库和建表,那么接下来就是如何使用DML语言去操作DB了。
操作SQLite
上一小节完成了数据库初始化,接下来就是通过SQL语句去执行更新或查询了,在Android中操作SQLite首先需要做的是打开DB,我们有两个方法可供选择,分别是:
getReadableDatabase()、getWritableDatabase()
先读一下官方文档中对getWritableDatabase()的解释~翻译仅供参考~
Create and/or open a database that will be used for reading and writing. The first time this is called, the database will be opened and onCreate(SQLiteDatabase), onUpgrade(SQLiteDatabase, int, int)
and/or onOpen(SQLiteDatabase) will be called.(创建或打开一个可读写的数据库,第一次调用之后,数据库将会被打开,并且onCreate方法、onUpgrade方法或者onOpen方法也会被调用)
Once opened successfully, the database is cached, so you can call this method every time you need to write to the database. (Make sure to call close() when you no longer need the database.) Errors
such as bad permissions or a full disk may cause this method to fail, but future attempts may succeed if the problem is fixed(一旦成功打开数据库,那么数据库将会被缓存,所以每次当你需要给数据库写入数据的时候你可以调用这个方法,当不再需要使用数据库的时候务必调用close()方法来关闭数据库,一些错误可能虎会引起这个方法调用失败,比如:错误的权限、硬盘已满,但是如果错误被修复了那么随后的尝试可能会成功)
看了getWritableDatabase()的说明之后,再看看getReadableDatabase()的说明,然后才好作比较~
Create and/or open a database. This will be the same object returned by getWritableDatabase() unless some problem, such as a full disk, requires the database to be opened read-only. In that case, a
read-only database object will be returned. If the problem is fixed, a future call to getWritableDatabase() may succeed, in which case the read-only database object will be closed and the read/write object will be returned in the future.(创建或打开一个数据库,调用这个方法将会返回和getWritableDatabase相同的对象,除非出现一些问题,比如硬盘已满,就需要已只读的方式打开数据库。如果真是那样的话,那么将返回一个只读的数据库对象。如果问题被修复了那么随后调用getWritableDatabase可能会成功,在这种情况下只读的数据库对象将会关闭并且随后会返回一个可读写的数据库对象)
不难发现,正常情况下,这两个方法没有区别,返回的都是同样的一个SQLiteDatabase对象,只是在某些异常状况下的处理方式不同,所以对我们而言使用哪个其实都无所谓。打开数据库之后,就可以对其进行增删改查操作,为了方便使用我们一般都会封装一个DAO层来操作DB,下面先贴上我简单封装的一个DAO层代码再对重点部分一一做解释:
package com.wl.cigrec.dao; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; public class DBManager { private DBHelper helper; private SQLiteDatabase database; public DBManager(Context context) { helper = new DBHelper(context); database = helper.getWritableDatabase(); } /** * 更新 * * @param sql * @param param * @return */ public boolean updateBySQL(String sql, Object[] param) { boolean flag = false; database.beginTransaction(); try { database.execSQL(sql, param); flag = true; database.setTransactionSuccessful(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { database.endTransaction(); if (database != null) database.close(); } return flag; } /** * 批量插入 * * @param sqls * @return */ public boolean insertBatch(List<String> sqls) { boolean flag = false; database.beginTransaction(); try { for (String sql : sqls) { database.execSQL(sql); } flag = true; database.setTransactionSuccessful(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { database.endTransaction(); if (database != null) database.close(); } return flag; } /** * 查询 * * @param table * @param columns * @param whereCause * @param selectionArgs * @param groupBy * @param having * @param orderBy * @param limit * @return */ public List<Map<String, String>> query(String table, String columns[], String whereCause, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) { List<Map<String, String>> list = new ArrayList<Map<String, String>>(); try { Cursor cursor = null; cursor = database.query(table, columns, whereCause, selectionArgs, groupBy, having, orderBy, limit); while (cursor.moveToNext()) { Map<String, String> map = new HashMap<String, String>(); for (int i = 0; i < cursor.getColumnCount(); i++) { String columnName = cursor.getColumnName(i); String columnValue = cursor.getString(cursor .getColumnIndex(columnName)); map.put(columnName, columnValue); } list.add(map); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (database != null) database.close(); } return list; } }
Line 30 这里封装了一个基于SQL的更新方法,包括添加或删除,由于SQLiteDatabase方法直接提供了一个execSQL方法,所以我们可以通过传入SQL语句以及占位的参数值来方便的执行DML语句。
Line 54 这里根据需求写了一个批量插入的方法,这种方式效率不是最高的,所以不推荐使用。如果有这方面需求的同学我推荐一篇很不错的博文参考一下:Android批量插入数据到SQLite数据库
Line 88 这里封装了一个通用的查询方法,没有用SQL的方式去封装是因为SQLiteDatabase自带的query方法更为通用一些,根据参数就可以看出来了。Cursor对象的用法类似于JDBC中的ResultSet,简单看一下应该都能理解,也没什么难度。
完成了DAO层的封装之后,我们就可以在Service层去调用了,以查询为例下面只列出一个service层的查询方法:
/** * 获取订单列表 * * @param context * @return List<OrderEntity> */ public List<OrderEntity> getOrderList(Context context) { DBManager dm = new DBManager(context); List<Map<String, String>> result = dm.query("t_order", new String[] { "id", "count", "money", "money", "date", "balance", "orderid" }, null, null, null, null, null, null); ArrayList<OrderEntity> list = new ArrayList<OrderEntity>(); if (result.size() > 0) { for (Map<String, String> map : result) { OrderEntity entity = new OrderEntity(); String id = map.get("id"); String count = map.get("count"); String money = map.get("money"); String date = map.get("date").toString(); String balance = map.get("balance"); String orderid = map.get("orderid"); entity.setId(Integer.parseInt(id)); entity.setCount(Integer.parseInt(count)); entity.setMoney(Double.parseDouble(money)); entity.setDate(date); entity.setBalance(Double.parseDouble(balance)); entity.setOrderid(orderid); list.add(entity); } } return list; }
一般在Service层都会对查询的数据进行一些封装和处理,当我们在Activity或者Fragment调用Service层的方法时应当返回封装好的数据,这也是最基本的MVC的应用,整个流程大体上就是这样。
导出并查看SQLite中的数据
当我们持久化数据之后通常都想知道数据是否保存成功,或者是执行更新操作之后想看看数据是否有变化,那么这时最简单的做法就是导出数据库文件,并通过SQLite的相关工具去查看即可。
当我们的应用程序运行起来之后,切换到DDMS视图,点击File Explorer选项卡,在这里可以看到如下图所示的文件列表(如果点不开第一层data文件夹的话请检查Android手机是否ROOT,只有ROOT之后才有权限查看):
依次点开我标出的文件夹,在第二层data下找项目的包名,点开之后可以看到如下结构的目录:
包名下的database文件夹下的xxx.db也就是我们的数据库文件了,这个文件名就是我们最开始在DBHelp类中定义好的数据库文件名了。最后点击右上角的图标导出数据库到本地:
完成之后我们可以看到这样一个文件:
成功导出数据库文件之后,我们可以通过SQLite相关的操作工具来查看数据库数据,我用的是SQLiteSpy,非常小巧的一个工具,当然如果需要一些高级功能的话还是推荐使用SQLiteExpertPro。很简单,点击open database打开我们的数据库文件就OK了,下面通过简单的查询SQL即可看到数据库中的数据:
总结
本篇blog记录了一些在Android操作SQLite的基本方法,个人感觉关系型数据库的基本操作方式都是大同小异,所以说如果有JDBC的开发经验的话学习SQLite还是很轻松的,最近一直在做P2P的服务端耽误了Android的学习进度和博客的更新进度,中午要喝瓶脉动,脉动回来,了解的东西越多,越会觉得自己和别的大神们差距越大,所以我还要更加努力才行,新的一年,加油!