在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的更多相关文章

  1. sqlite在Android上的一个bug:SQLiteCantOpenDatabaseException when nativeExecuteForCursorWindow

    更多内容在这里查看 https://ahangchen.gitbooks.io/windy-afternoon/content/ ::-/com.company.product W/System.er ...

  2. 利用SQLite在android上实现增删改查

    利用SQLite在android上实现增删改查 方法: 一.直接利用database.execSQL()方法输入完整sql语句进行操作 这种方法适用于复杂的sql语句,比如多表查询等等 这里适合于增删 ...

  3. 利用SQLite在android上创建数据库

    利用SQLite在android上创建数据库 方法: 1.创建我们的数据库类继承SQLiteOpenHelper类 完成相关函数的重写和数据库对象的初始化 public MySQLiteOpenHel ...

  4. sqlite与android交互 (封装)

    学android已经有大概一周时间了吧 ,总感觉自己基础不怎么好,只能通过一点一点积累着敲来巩固平常的知识,有的时候就先不封装的敲一遍,再封装上,有些语句真的记不住,虽然知道他是什么意思,于是乎就反复 ...

  5. SQLite在Android程序中的使用方法,SQLite的增删查改方法

    Sqlite: 1.一款用来实现本地数据存储的轻量级数据管理工具,是众多用来实现数据库管理的工具之一. 2.Android已经将SQLite的代码功能吸收在它的系统中,我们可以直接在Android程序 ...

  6. 关于SQLite在Android开发中的知识点总结

    一.存放位置 1.内部存储-放在 data/data 底下, 也就是内部存储, 里面的文件以及文件夹是私有的, 其他 app 不能访问, 也随着 app 卸载而删除; 2.放在 sd 卡里面, 如果设 ...

  7. SQLite 在 Android 的应用

    Android提供了创建和使用SQLite数据库的API(Application Programming Interface,应用程序编程接口). 在Android系统中,主要由类SQLiteData ...

  8. 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 ...

  9. android安卓Sqlite数据库实现用户登录注册

    看了很多别人写的安卓SQlite数据的操作代码,一点也不通俗易懂,我觉得我写的不错,而且安卓项目也用上了,所以在博客园里保存分享一下!建立一个类 并继承SQLiteOpenHelper public ...

随机推荐

  1. Font-Awesome 体验 鼠标进入图标变大

    <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> &l ...

  2. C# .NET ASP.NET 其中关系你了解多少

    有些人一直在做这方面..但突然有人来问你这些问题..估计有很多答不上来. 1..NET是一个平台,一个抽象的平台的概念. .NET平台其本身实现的方式其实还是库,抽象层面上来看是一个平台. 个人理解. ...

  3. 表单元素的外观改变(webkit and IE10)

    1.禁止表单默认外观: input,select{ -webkit-appearance:none; appearance:none; }2.伪元素改变ie10表单元素默认外观 select::-ms ...

  4. C#秘密武器之扩展方法

    原文:C#秘密武器之扩展方法 为何要用扩展方法? 作为一个.NET程序猿,我们经常要跟.net自带类库或者第三方dll类库打交道,有时候我们未必能够通过反编译来查看它们的代码,但是我们通常需要给它们扩 ...

  5. 开发环境准备:Ruby on Rails开发环境配置

    开发环境准备:Ruby on Rails开发环境配置 前情回顾 上次讲到Vmware虚拟机的安装配置以及Scientific Linux 6.X系统的安装.这回我们的主要任务是在Linux操作系统上完 ...

  6. leetcode[164] Maximum Gap

    梅西刚梅开二度,我也记一题. 在一个没排序的数组里,找出排序后的相邻数字的最大差值. 要求用线性时间和空间. 如果用nlgn的话,直接排序然后判断就可以了.so easy class Solution ...

  7. extjs 时间可选择时分

    new Ext.form.DateTimeField({ id: 'SdDateField', width: 130, format: 'Y-m-d H:i', editable: false, va ...

  8. (蓝牙)网络编程中,使用InputStream read方法读取数据阻塞的解决方法

    问题如题,这个问题困扰了我好几天,今天终于解决了,感谢[1]. 首先,我要做的是android手机和电脑进行蓝牙通信,android发一句话,电脑端程序至少就要做到接受到那句话.android端发送信 ...

  9. hibernate 获取实体的表名、主键名、列名(转载+修改)

    package com.escs.utils; import java.util.Iterator; import org.hibernate.cfg.AnnotationConfiguration; ...

  10. 利用Readability解决网页正文提取问题

    分享: 利用Readability解决网页正文提取问题   做数据抓取和分析的各位亲们, 有没有遇到下面的难题呢? - 如何从各式各样的网页中提取正文!? 虽然可以用SS为各种网站写脚本做解析, 但是 ...