Sqlite in Android
在Android上保存本地数据有三种方式,SharedPreferences、Files和Sqlite。SharedPreferences主要是用来保存键值对形式的程序配置信息,与ini、properties操作类似。Files主要是用来保存图片、音视频等二进制数据。Sqlite是一种轻量级的关系型数据库,运行在android系统中为应用程序提供按照表结构存储数据的功能。Sqlite并不想与传统的大型的关系型数据库竞争,它的目标很明确,在硬件资源有限的情况下,为应用程序提供基本的数据库操作功能。因此,Sqlite在嵌入式系统、智能移动设备上广泛应用。同样,因为轻量的原因,Sqlite在处理并发性的能力上比起大型的数据库就要欠缺很多。这个问题不是Sqlite的缺陷而是它的特点,作为开发人员需要掌握它。Sqlite提供的是文件锁,即在同一时间内,只能有一个程序对它进行写操作,可以有多个程序对它进行读操作。这对于在嵌入式系统中运行的程序来说,应该足够了,并且程序的结构也可以很清晰,实现一个全局的写操作连接由整个程序共享,并且通过代码控制其并发操作,同时允许多个读操作连接并发执行。但是这种想法在Android上完全是错误的。
在Android的官网上有一个操作Sqlite的例子,正因为是官方发布的例子,那么可以想象有多少开发人员会在实际的项目中用到它。
public class FeedReaderDbHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version.
public static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = "FeedReader.db";
public FeedReaderDbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public void onCreate(SQLiteDatabase db) {
db.execSQL(SQL_CREATE_ENTRIES);
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// This database is only a cache for online data, so its upgrade policy is
// to simply to discard the data and start over
db.execSQL(SQL_DELETE_ENTRIES);
onCreate(db);
}
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
onUpgrade(db, oldVersion, newVersion);
}
}
Google为Android开发出了一个SQLiteOpenHelper来管理Sqlite,包括创建数据库文件、创建表和数据库更新。在使用Sqlite时,程序实例化SQLiteOpenHelper的子类,然后再进行相关操作。
FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext()); SQLiteDatabase db = mDbHelper.getWritableDatabase(); // Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_ENTRY_ID, id);
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_CONTENT, content); // Insert the new row, returning the primary key value of the new row
long newRowId;
newRowId = db.insert(
FeedEntry.TABLE_NAME,
FeedEntry.COLUMN_NAME_NULLABLE,
values);
以上代码的标题赫然写着:Put Information into a Database。紧接着,下面代码的标题是:Read Information from a Database。
SQLiteDatabase db = mDbHelper.getReadableDatabase(); // Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
FeedEntry._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_UPDATED,
...
}; // How you want the results sorted in the resulting Cursor
String sortOrder =
FeedEntry.COLUMN_NAME_UPDATED + " DESC"; Cursor c = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The columns to return
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);
看到这里,自信的开发人员可能已经感到在Android上操作Sqlite已经没有问题了,一个getWritableDatabase()和一个getReadableDatabase()。再加上Sqlite文件锁这个特点,那么程序理所当然是将所有涉及写操作的函数用synchronized保护起来,而读操作可以任意执行,强大的Google已经分离了WritableDatabase和ReadableDatabase。但事实并非如此,官网上的这个例子以及这些API的命名产生了很大的混淆。Kevin Galligan在他的博客《Android Sqlite Locking》中讨论过这个问题,并专门写了一个测试程序证明多线程操作Sqlite时会导致数据库操作失败。为了突出问题便于测试,在该测试程序的基础上做了一些修改以表明:在正常情况下,同一个SQLiteOpenHelper的getWritableDatabase()和一个getReadableDatabase()返回的是同一个SQLiteDatabase并且是线程安全的。因此,数据库操作会串行执行,而并不能实现一个写、多个读的结构。下面代码故意使用getReadableDatabase()实现写操作,getWritableDatabase()实现读操作,同时在LogCat中打印出不同线程中SQLiteDatabase对象的Hash Code。
public void insert(String desc) {
SQLiteDatabase readableDatabase = getReadableDatabase();
Log.i(Thread.currentThread().toString(), "insert: " + readableDatabase.hashCode());
ContentValues contentValues = new ContentValues();
contentValues.put("description", desc);
readableDatabase.insertOrThrow("session", null, contentValues);
}
public int count() {
SQLiteDatabase writableDatabase = getWritableDatabase();
Log.i(Thread.currentThread().toString(), "count: " + writableDatabase.hashCode());
Cursor cursor = writableDatabase.rawQuery(
"select count(*) from session", null);
cursor.moveToFirst();
return cursor.getInt(0);
}
通过上面的测试可以看到,一个SQLiteOpenHelper实际代表着一个数据库连接,对于一个数据库(在Sqlite中相当于一个.db文件)同时只能建立一个连接执行数据库操作,所以在程序中操作同一个数据库时SQLiteOpenHelper应该是唯一的,SQLiteDatabase对象可以有多个,并且能在多线程中执行操作。在Android自带的Samples里有一个Notepad例子,它采用了ContentProvider作为操作Sqlite的接口。这种方式就是让系统初始化一个SQLiteOpenHelper对象,供其它程序使用。它有一个明显的缺陷,针对每个表都需要实现一个专门的ContentProvider从而使得表间操作相当繁琐。
OrmLite这个持久层的框架解决了上述问题。在OrmLite里是使用OpenHelperManager来管理SQLiteOpenHelper,通过查看源代码可以看到OpenHelperManager使用了单例模式来确保SQLiteOpenHelper的唯一性。在下面的工厂方法中有两个小技巧:第一,Context实际上是获取的整个应用程序的上下文;第二,设置了一个引用计数器,instanceCount,确保程序通过releaseHelper()能够正确关闭SQLiteOpenHelper。
private static <T extends OrmLiteSqliteOpenHelper> T loadHelper(Context context, Class<T> openHelperClass) {
if (helper == null) {
if (wasClosed) {
logger.info("helper was already closed and is being re-opened");
}
if (context == null) {
throw new IllegalArgumentException("context argument is null");
}
Context appContext = context.getApplicationContext();
helper = constructHelper(appContext, helperClass);
logger.trace("zero instances, created helper {}", helper);
DaoManager.clearDaoCache();
instanceCount = 0;
}
instanceCount++;
logger.trace("returning helper {}, instance count = {} ", helper, instanceCount);
@SuppressWarnings("unchecked")
T castHelper = (T) helper;
return castHelper;
}
Sqlite in Android的更多相关文章
- sqlite在Android上的一个bug:SQLiteCantOpenDatabaseException when nativeExecuteForCursorWindow
更多内容在这里查看 https://ahangchen.gitbooks.io/windy-afternoon/content/ ::-/com.company.product W/System.er ...
- 利用SQLite在android上实现增删改查
利用SQLite在android上实现增删改查 方法: 一.直接利用database.execSQL()方法输入完整sql语句进行操作 这种方法适用于复杂的sql语句,比如多表查询等等 这里适合于增删 ...
- 利用SQLite在android上创建数据库
利用SQLite在android上创建数据库 方法: 1.创建我们的数据库类继承SQLiteOpenHelper类 完成相关函数的重写和数据库对象的初始化 public MySQLiteOpenHel ...
- sqlite与android交互 (封装)
学android已经有大概一周时间了吧 ,总感觉自己基础不怎么好,只能通过一点一点积累着敲来巩固平常的知识,有的时候就先不封装的敲一遍,再封装上,有些语句真的记不住,虽然知道他是什么意思,于是乎就反复 ...
- SQLite在Android程序中的使用方法,SQLite的增删查改方法
Sqlite: 1.一款用来实现本地数据存储的轻量级数据管理工具,是众多用来实现数据库管理的工具之一. 2.Android已经将SQLite的代码功能吸收在它的系统中,我们可以直接在Android程序 ...
- 关于SQLite在Android开发中的知识点总结
一.存放位置 1.内部存储-放在 data/data 底下, 也就是内部存储, 里面的文件以及文件夹是私有的, 其他 app 不能访问, 也随着 app 卸载而删除; 2.放在 sd 卡里面, 如果设 ...
- SQLite 在 Android 的应用
Android提供了创建和使用SQLite数据库的API(Application Programming Interface,应用程序编程接口). 在Android系统中,主要由类SQLiteData ...
- what are Datatypes in SQLite supporting android
As said at Datatypes In SQLite Version 3: Datatypes In SQLite Version 3 Most SQL database engines (e ...
- android安卓Sqlite数据库实现用户登录注册
看了很多别人写的安卓SQlite数据的操作代码,一点也不通俗易懂,我觉得我写的不错,而且安卓项目也用上了,所以在博客园里保存分享一下!建立一个类 并继承SQLiteOpenHelper public ...
随机推荐
- 防SQL注入:生成参数化的通用分页查询语句
原文:防SQL注入:生成参数化的通用分页查询语句 前些时间看了玉开兄的“如此高效通用的分页存储过程是带有sql注入漏洞的”这篇文章,才突然想起某个项目也是使用了累似的通用分页存储过程.使用这种通用的存 ...
- Facebook Hack 语言 简介
1. Hack 是什么? Hack 是一种基于HHVM(HipHop VM 是Facebook推出的用来执行PHP代码的虚拟机,它是一个PHP的JIT编译器,同时具有产生快速代码和即时编译的优点.)的 ...
- JavaScript中的单引号和双引号报错的解决方法
在使用JavaScript显示消息或者传递字符数据的时候,经常会碰到数据中夹杂单引号(')或者双引号("),这种语句往往会造成JavaScript报错.对此一般采用/'或者/"的解 ...
- Robotium源码分析之Instrumentation进阶
在分析Robotium的运行原理之前,我们有必要先搞清楚Instrumentation的一些相关知识点,因为Robotium就是基于Instrumentation而开发出来的一套自动化测试框架.鉴于之 ...
- Objective-C系列
我的Objective-C系列文章和坚持写博客的感想 做iOS开发有一段时间了,也有自己上线的App产品,也在坚持着发表技术博客总结自己所学的东西.在写博客的时候虽然博文中不免有错别字,但每句话都 ...
- Linux的错误码
在使用时需要包含头文件 #include <errno.h> merlin@tfAnalysis:~/projects/tfradius$ cat /usr/include/asm-gen ...
- C/C++中常量字符串管理
为了节省内存,C/C++把常量字符串放到单独的一个内存区域.当几个指针赋值给相同的常量字符串时,它们实际上会指向相同的内存地址.但用产量初始化字符数组,结果却不同. 下面是一个小程序示例: #incl ...
- 使用JSmooth制造java jar文件可以运行exe文件教程图像
这是我之前在个人博客3yj上面写的一篇文章,如今转载过来,原文地址 (这不是广告哦) 几年前,刚接触java的是.就想用一些方法把自己的劳动果实保护起来,曾经也用过非常多这种工具.有一个特别好用,今天 ...
- Newtonsoft.Json 将C#对象转化为json格式
object To json public static string ObjectToJson(object obj) { JsonSerializerSettings jss = new Json ...
- 将DataTable内容导出到Excel表格的两种方法
方法一:循环DataTable单元格内容拼接字符串,利用StreamWriter的Write方法将字符串写入Excel文件中 这种方法很实现很简单.拼接字符串时,每个单元格之间添加'\t'(表示一个占 ...