处女男学Android(十四)---Android 重量级数据存储之SQLite

前言

不知不觉的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的学习进度和博客的更新进度,中午要喝瓶脉动,脉动回来,了解的东西越多,越会觉得自己和别的大神们差距越大,所以我还要更加努力才行,新的一年,加油!

时间: 2024-10-18 14:50:17

处女男学Android(十四)---Android 重量级数据存储之SQLite的相关文章

处女男学Android(十)---Fragment完结篇之Fragment通信和ListFragment

一.前言 前两篇blog介绍了fragment的基本用法 1.Fragment API简介:如FragmentManager.FragmentTransaction等. 2.定义方式:继承Fragment类并重写onCreat.onCreatView.onPause等. 3.引用方式:通过定义<fragment>标签或者通过FragmentTrasaction实例的add()动态添加Fragment这两种. 4.Fragment回退栈与Fragment生命周期. 关于Fragment剩余的重要

处女男学Android(十一)---Gallery、ViewPager和ViewPager+Fragment实现的Tab导航

一.前言 有阵子没更新博客了,主要是最近公司接了个P2P的金融借贷项目没人做,被拉去写服务端,所以迟迟没时间继续学习大安卓,想了想自己的安卓水平和公司的专业安卓璟博比起来依旧差距挺大,于是乎我要加把劲赶上才行,所以继续翻开李刚疯狂讲义系列,看到Gallery这个控件了,大致功能是横向滚动查看列表项,再仔细看了一下居然过时了,官方推荐用ViewPager来替代,还没学就过时了,有点不爽,干脆新的旧的一起学习一下,也好进行一下比较吧.废话不多说,首先是已经过时的Gallery. 二.画廊视图Gall

Android基础之十四数据存储 之 SQLite数据库详解

Android基础之十四数据存储 之 SQLite数据库详解 SQLite 是一款 轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百 K 的内存就足够了,因而特别适合在移动设备上使用. SQLite 不仅支持标准的 SQL 语法,还遵循了数据库的 ACID( 原子性(Atomicity) .一致性(Consistency) . 隔离性(Isolation) . 持久性(Durability))事务,所以只要你以前使用过其他的关系型数据库,就可以很快地上手 SQLite.而

HDU 6467 简单数学题 【递推公式 &amp;&amp; O(1)优化乘法】(广东工业大学第十四届程序设计竞赛)

传送门:http://acm.hdu.edu.cn/showproblem.php?pid=6467 简单数学题 Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 308    Accepted Submission(s): 150 Problem Description 已知 F(n)=∑i=1n(i×∑j=inCij) 求 F(n) m

Android应用之——不要将数据存储在Application类中

前言:最近在开发中发现了一个比较严重的问题,当我们将应用按home键放入后台运行,一段时间后,当我们再次打开应用的时候,十有八九会出现一个NullPointException的空指针异常,根据logcat的日志,就会定位到一个去全局性到变量去,这是什么原因呢?原来,是因为我们我们将很多数据放入了application中作为全局变量,导致了问题的产生,下面来说下为什么不能将数据放在application中. 一.application类的简介 Application和Activity,Servic

Android开发学习笔记:数据存取之SQLite浅析

一.SQLite的介绍 1.SQLite简介 SQLite是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入 式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了.它能够支持 Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如Tcl.PHP.Java.C++..Net等,还有ODBC接口,同样比起 Mysql.PostgreSQL这两款开源世界著名的数据库管理系统来讲,它的

Android系统的五种数据存储形式(一)

Android系统有五种数据存储形式,分别是文件存储.SP存储.数据库存储.contentprovider 内容提供者.网络存储.其中,前四个是本地存储.存储的类型包括简单文本.窗口状态存储.音频视频数据.XML注册文件的各种数据.各种存储形式的特点不尽相同,因此对于不同的数据类型有着固定的存储形式,本文为演示方便给出的案例基本相同,都是是采用账号登录来演示数据存储,保存账号和密码信息,下次登录时记住账号和密码.重在说明各种存储形式的原理. 文件存储: 以I/O流的形式把数据存入手机内存或SD卡

Android系统的五种数据存储形式(二)

之前介绍了Android系统下三种数据存储形式,今天补充介绍另外两种,分别是内容提供者和网络存储.有些人可能认为内存提供者和网络存储更偏向于对数据的操作而不是数据的存储,但这两种方式确实与数据有关,所以这里还是将这两种形式简要的说明一下. Content Provider: Content Provider,中文名是内存提供者,Android四大组件之一,内容提供者是应用程序之间共享数据的接口,以数据库形式存入手机内存,可以共享自己的数据给其他应用使用.之所以需要设计一个单独的控件来操作数据,是

Android学习之简单的数据存储

在Android中,数据存储是开发人员不可以避免的.Android为开发者提供了很多的存储方法,在前面的博客中,已经讲述了sqlite存储数据.今天将介绍用SharedPreferences来存储数据,它可以将数据保存在应用软件的私有存储区,存储区的数据只能被写入这些数据的软件读取.SharedPreference通过键值对的方法存储数据. 1.SharedPreference存储简单数据 SharedPreference可以存放简单的String.Boolean.Int等对象. 1 <Rela