博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Sqlite in Android
阅读量:6693 次
发布时间:2019-06-25

本文共 5975 字,大约阅读时间需要 19 分钟。

  在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 keysContentValues 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 rowlong 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 CursorString 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 loadHelper(Context context, Class
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; }

 

转载于:https://www.cnblogs.com/gofblogs/p/3270323.html

你可能感兴趣的文章
PHP-CPP开发扩展(一)
查看>>
Git常用命令
查看>>
【html】使用img标签和背景图片之间的区别
查看>>
JDK源码阅读(一) ArrayList
查看>>
Quartz1.8.5例子(六)
查看>>
leetcode524
查看>>
leetcode806
查看>>
(29)odoo的可用小图标
查看>>
MVC ViewBag传值
查看>>
通过面试题学习零散知识:Java面试题整理
查看>>
达成目标5步法则——雷达里奥/核聚
查看>>
CentOS虚拟机通过主机网络上网
查看>>
Redis架构设计
查看>>
nio编程
查看>>
【竞赛笔记】飞思卡尔智能车竞赛
查看>>
codevs 1291 火车线路
查看>>
2017 国庆湖南 Day3
查看>>
位置参数,默认参数,非关键字可变长参数,关键字可变长参数
查看>>
Linux下vi命令
查看>>
btrfs文件系统管理与应用
查看>>