再读Android sqlite

Android原生支持sqlite数据库操作,sqlite时轻量级关系型数据库,支持标准sql语句。Android对sqlite进行良好的接口封装来避免sql注入等安全问题。

本文解决的问题:

1、Android App内如何创建数据库

2、Android App内创建数据库如何自定义文件路径

3、Android App内获取数据库流程解析

4、无Context模式使用数据库,可在uiautomator1.0测试框架和其他app_process启动的进程内使用数据库。

App内常规使用数据库

Android应用内使用数据库需要借助于SQLiteOpenHelper类实现对数据库的操作,使用数据库通过以下几步:

1、创建私有类集成自SQLiteOpenHelper方法,并覆写onCreate、onUpdate方法实现对数据库升级降级操作。

2、获取SQLiteOpenHelper对象实例。

3、获取只读、读写类型数据库SQLiteDatabase对象实例(getReadableDatabase()/getWritableDatabase()),当数据库升级或创建时才会调用onCreate、onUpdate方法。

4、使用SQLiteDatabase接口实现数据库操作(增删改查)

//1、创建私有SQLiteOpenHelper类
class DatabaseHelper extends SQLiteOpenHelper { public DatabaseHelper(Context context, String dbName, int version) {
super(context, dbName, null, version);
LogUtil.print("DatabaseHelper dbName[%s] version[%d]",dbName+"",version);
} @Override
public void onCreate(SQLiteDatabase db) {
//数据库创建时调用
} @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//数据库升级时调用
}
}
//2、获取SQLiteOpenHelper对象实例。
int dbVersion=0;
DatabaseHelper databaseHelper=new (context,"test.db",dbVersion);
//3、获取只读、读写类型数据库SQLiteDatabase对象实例
SQLiteDatabase db = databaseHelper.getWritableDatabase();
//4、使用SQLiteDatabase接口实现数据库操作(增删改查)
String sql = "select * from testTable where id=?"
Cursor cursor = db.rawQuery(sql, new String[]{"1111"});

数据库源码解析

1、SQLiteOpenHelper构造方法仅初始化参数:版本号必须大于0

public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
int minimumSupportedVersion, DatabaseErrorHandler errorHandler) {
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); mContext = context;
mName = name;
mFactory = factory;
mNewVersion = version;
mErrorHandler = errorHandler;
mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
}

2、获取数据库实例:真正创建数据库文件

  • Android8.0及以下版本:区分writeable和readable数据库
    private SQLiteDatabase getDatabaseLocked(boolean writable) {
if (mDatabase != null) {
if (!mDatabase.isOpen()) {
// Darn! The user closed the database by calling mDatabase.close().
mDatabase = null;
//如果获取只读数据库且缓存了可写数据库则直接返回
} else if (!writable || !mDatabase.isReadOnly()) {
// The database is already open for business.
return mDatabase;
}
} if (mIsInitializing) {
throw new IllegalStateException("getDatabase called recursively");
} SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true; if (db != null) {
//如果获取可写数据库,复用缓存的只读数据库
if (writable && db.isReadOnly()) {
db.reopenReadWrite();
}
} else if (mName == null) {
db = SQLiteDatabase.create(null);
} else {
try {
//获取只读类型数据库实例
if (DEBUG_STRICT_READONLY && !writable) {
final String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mFactory,
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
} else {
//获取可读写数据库实例
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
mFactory, mErrorHandler);
}
} catch (SQLiteException ex) {
//获取数据库异常时,如果获取可写数据库,则直接抛出
if (writable) {
throw ex;
}
Log.e(TAG, "Couldn't open " + mName
+ " for writing (will try read-only):", ex);
//获取数据库异常时,如果获取只读数据库,重试获取只读类型数据库。
final String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mFactory,
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
}
}
onConfigure(db);
final int version = db.getVersion();
//数据库升级处理逻辑
if (version != mNewVersion) {
if (db.isReadOnly()) {
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + mName);
}
//程序运行时更新数据库:使用缓存数据库时走此逻辑,重新加载数据库
if (version > 0 && version < mMinimumSupportedVersion) {
File databaseFile = new File(db.getPath());
onBeforeDelete(db);
db.close();
if (SQLiteDatabase.deleteDatabase(databaseFile)) {
mIsInitializing = false;
return getDatabaseLocked(writable);
} else {
throw new IllegalStateException("Unable to delete obsolete database "
+ mName + " with version " + version);
}
} else {
//事务更新(降级/升级)数据库
db.beginTransaction();
try {
if (version == 0) {
onCreate(db);
} else {
if (version > mNewVersion) {
onDowngrade(db, version, mNewVersion);
} else {
onUpgrade(db, version, mNewVersion);
}
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
} onOpen(db); if (db.isReadOnly()) {
Log.w(TAG, "Opened " + mName + " in read-only mode");
} mDatabase = db;
return db;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) {
db.close();
}
}
}
  • Android8.1.0及以上版本:均获取writable类型数据库,仅在发生异常时返回readable类型数据库
    private SQLiteDatabase getDatabaseLocked(boolean writable) {
if (mDatabase != null) {
if (!mDatabase.isOpen()) {
// Darn! The user closed the database by calling mDatabase.close().
mDatabase = null;
} else if (!writable || !mDatabase.isReadOnly()) {
//如果获取只读数据库且缓存了可写数据库则直接返回
// The database is already open for business.
return mDatabase;
}
} if (mIsInitializing) {
throw new IllegalStateException("getDatabase called recursively");
} SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true; if (db != null) {
//如果缓存的数据库为只读类型的,并且当次获取可写类型数据库,则覆用缓存数据库
if (writable && db.isReadOnly()) {
db.reopenReadWrite();
}
} else if (mName == null) {
db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build());
} else {
//获取数据库的存储路径:注-仅次一处使用到了context对象
final File filePath = mContext.getDatabasePath(mName);
SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
try {
//打开数据库实例对象
db = SQLiteDatabase.openDatabase(filePath, params);
// Keep pre-O-MR1 behavior by resetting file permissions to
setFilePermissionsForDb(filePath.getPath());
} catch (SQLException ex) {
if (writable) {
throw ex;
}
Log.e(TAG, "Couldn't open " + mName
+ " for writing (will try read-only):", ex);
params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
db = SQLiteDatabase.openDatabase(filePath, params);
}
} onConfigure(db); final int version = db.getVersion();
if (version != mNewVersion) {
if (db.isReadOnly()) {
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + mName);
}
//程序运行时更新数据库:使用缓存数据库时走此逻辑,重新加载数据库
if (version > 0 && version < mMinimumSupportedVersion) {
File databaseFile = new File(db.getPath());
onBeforeDelete(db);
db.close();
if (SQLiteDatabase.deleteDatabase(databaseFile)) {
mIsInitializing = false;
return getDatabaseLocked(writable);
} else {
throw new IllegalStateException("Unable to delete obsolete database "
+ mName + " with version " + version);
}
} else {
//事务更新(降级/升级)数据库
db.beginTransaction();
try {
if (version == 0) {
onCreate(db);
} else {
if (version > mNewVersion) {
onDowngrade(db, version, mNewVersion);
} else {
onUpgrade(db, version, mNewVersion);
}
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
} onOpen(db); if (db.isReadOnly()) {
Log.w(TAG, "Opened " + mName + " in read-only mode");
} mDatabase = db;
return db;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) {
db.close();
}
}
}

3、ContextImpl获取数据库路径

    public File getDatabasePath(String name) {
File dir;
File f;
if (name.charAt(0) == File.separatorChar) {
//数据库文件名为绝对路径时,例如(/sdcard/test.db)则返回该绝对路径的文件对象。
String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar));
dir = new File(dirPath);
name = name.substring(name.lastIndexOf(File.separatorChar));
f = new File(dir, name);
if (!dir.isDirectory() && dir.mkdir()) {
FileUtils.setPermissions(dir.getPath(), 505, -1, -1);
}
} else {
//如果仅指定文件名称时,通过conext对象获取数据库文件存储目录,获取该目录下的数据库文件对象想。
dir = this.getDatabasesDir();
f = this.makeFilename(dir, name);
} return f;
}

数据库本地文件路径

通过上面源码分析发现获取数据库对象的具体调用逻辑:

1、通过getWritableDatabase()方法获取数据库对象,实际是调用getDatabaseLocked(true)方法。

public SQLiteDatabase getWritableDatabase() {
synchronized (this) {
return getDatabaseLocked(true);
}
}

getDatabaseLocked(true)方法获取数据库实例:获取数据库路径源码

    private SQLiteDatabase getDatabaseLocked(boolean writable) {
SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true; if (db != null) {
...
} else if (mName == null) {
...
} else {
final File filePath = mContext.getDatabasePath(mName);
SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
try {
db = SQLiteDatabase.openDatabase(filePath, params);
...
} catch (SQLException ex) {
...
db = SQLiteDatabase.openDatabase(filePath, params);
}
}
...
mDatabase = db;
return db;
} finally {
...
}
}

在getDatabaseLocked(true)获取数据库存储路径是调用Context的getDatabasePath(mName)方法,在Android中contex的具体实现类为ContextImpl,所以获取数据库存储路径要看ContextImpl.getDatabasePath(String)方法的实现。

 public File getDatabasePath(String name) {
File dir;
File f;
if (name.charAt(0) == File.separatorChar) {
//数据库文件名为绝对路径时,例如(/sdcard/test.db)则返回该绝对路径的文件对象。
String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar));
dir = new File(dirPath);
name = name.substring(name.lastIndexOf(File.separatorChar));
f = new File(dir, name);
if (!dir.isDirectory() && dir.mkdir()) {
FileUtils.setPermissions(dir.getPath(), 505, -1, -1);
}
} else {
//如果仅指定文件名称时,通过conext对象获取数据库文件存储目录,获取该目录下的数据库文件对象想。
dir = this.getDatabasesDir();
f = this.makeFilename(dir, name);
} return f;
}

自定义本地数据库文件路径

我们在使用数据库时有时候需要自定义数据库存储路径,下面介绍三种改变数据库文件路径的方法。

  • 一、更改context中getDatabasesDir()的返回值

    1、自定义Application

    2、覆写Application的getDatabasePath(String name)方法,在方法内指定自定义路径。

    3、在AndroidManifest.xml中指定自定义Application
1、自定义Application
public class MyApplication extends Application{
public static MyApplication getInstance()
{
return mInstance;
} @Override
public void onCreate()
{
super.onCreate();
} @Override
public File getDatabasePath(String name) {
return new File("/sdcard/");
}
} 2、在manifest文件中指定application
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.qihoo.qa">
//申请权限
...
<application
android:name=".MyApplication"
android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme"> </application>
</manifest>
4、获取DatabaseHelper,数据库文件为/sdcard/test.db
DatabaseHelper dataBaseHelper = new DatabaseHelper(context, "test.db", 0);
  • 二、构建数据库时传入绝对路径
//获取DatabaseHelper,数据库文件为/sdcard/test.db
DatabaseHelper dataBaseHelper = new DatabaseHelper(context, "/sdcard/test.db", 0);
  • 三、在SQLiteOpenHelper子类中覆写getWritableDatabase()和getReadableDatabase()

class DatabaseHelper extends SQLiteOpenHelper {
//1、指定数据库文件名
String dbName="/sdcard/test.db";
int version;
public DatabaseHelper(Context context, String dbName, int version) {
super(context, dbName, null, version);
} @Override
public void onCreate(SQLiteDatabase db) {
//数据库创建时调用
} @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//数据库升级时调用
} //覆写获取数据库实现
@Override
public SQLiteDatabase getWritableDatabase() {
return getDataBase(dbName, version);
}
//覆写获取数据库实现
@Override
public SQLiteDatabase getReadableDatabase() {
return getDataBase(dbName, version);
} //实现获取数据库具体逻辑
private SQLiteDatabase getDataBase(String fileName, int version){
try {
SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(fileName, null);
int oldVer = database.getVersion();
database.setVersion(version);
if (version > oldVer) {
this.onUpgrade(database, oldVer, version);
}
if(version<oldVer){
this.onDowngrade(database, oldVer, version);
} return database;
} catch (Exception var4) {
LogUtil.e(var4);
return super.getWritableDatabase();
}
}

注意

方法一、方法二需要传递context对象,如果Context为null,获取数据库实例时会导致空指针异常。

方法三的实现不依赖Context对象,可以将Context指定为null。

使用数据库的一些建议

  • 调用getWritableDatabase()获取数据库时会重新创建数据库实例,一般在程序中复用该数据库实例即可,如果保存多份数据库实例会导致OOM异常。
  • 数据库插入数据时使用SQLiteStatement对数据进行封装,避免sql注入相关问题。
  • 数据库查询中使用方法Cursor rawQuery(String sql, String[] selectionArgs)指定动态参数,防止sql注入及异常字符问题。

再读Android sqlite的更多相关文章

  1. Android+Sqlite 实现古诗阅读应用(二)

    传送门:Android+Sqlite 实现古诗阅读应用(一) Hi,又回来了,最近接到很多热情洋溢的小伙伴们的来信,吼开心哈,我会继续努力的=-=! 上回的东西我们做到了有个textview能随机选择 ...

  2. Android SQLite (一) 数据库简介

    大家好,今天来介绍一下SQLite的相关知识,并结合Java实现对SQLite数据库的操作. SQLite是D.Richard Hipp用C语言编写的开源嵌入式数据库引擎.它支持大多数的SQL92标准 ...

  3. Android SQLite 简单使用演示样例

    SQLite简单介绍 Google为Andriod的较大的数据处理提供了SQLite,他在数据存储.管理.维护等各方面都相当出色,功能也很的强大. 袖珍型的SQLite能够支持高达2TB大小的数据库, ...

  4. android SQLite数据库(转)

    Android数据库 之 SQLite数据库   Android数据库  一.关系型数据库SQLIte 每个应用程序都要使用数据,Android应用程序也不例外,Android使用开源的.与操作系统无 ...

  5. Android SQLite总结(一) (转)

    Android SQLite总结(一)  郑海波 2012-08-21 转载请声明:http://blog.csdn.net/nuptboyzhb/article/details/7891887 前言 ...

  6. Android SQLite 数据库详细介绍

    Android SQLite 数据库详细介绍 我们在编写数据库应用软件时,需要考虑这样的问题:因为我们开发的软件可能会安装在很多用户的手机上,如果应用使用到了SQLite数据库,我们必须在用户初次使用 ...

  7. Android Sqlite 导入CSV文件 .

    http://blog.csdn.net/johnnycode/article/details/7413111 今天遇到 Oracle 导出的12万条CSV格式数据导入 Android Sqlite ...

  8. Android SQLite 数据库 增删改查操作

    Android SQLite 数据库 增删改查操作 转载▼ 一.使用嵌入式关系型SQLite数据库存储数据 在Android平台上,集成了一个嵌入式关系型数据库--SQLite,SQLite3支持NU ...

  9. Android SQLite总结[转载]

    [转载] :http://blog.163.com/zqy216_2008/blog/static/4119371820119954812509/ 最近在做的项目涉及到了SQLite,大学时没有好好学 ...

随机推荐

  1. 函数计算 触发式计算 日志 MP3 图片 合成视频

    函数计算 触发式计算   日志  MP3 图片 合成视频 [start_time]:20120511 06:59:11 [20120511 06:59:11_0.4950568322522534]: ...

  2. ubutu14.04无法使用sudo,也无法切换到root用户去解决问题怎么办?

    一不小心,修改了/etc/sudoers文件. 惨了. 无法使用sudo了,啥都干不成了. 最最关键的是,也无法用root登录. 本想着要重装系统了. 后来发现了神奇的ubuntu安全模式. 1.重启 ...

  3. iOS:界面适配(二)--iPhone/iPad适配(关于xib)

    本文纯属个人看法,强迫症后遗症 版本:xcode 6.0 + iOS SDK 8.0 讨论范围:控制器的view(创建VC时自带的xib) ------------------------------ ...

  4. 洛谷P1291 [SHOI2002]百事世界杯之旅——期望DP

    题目:https://www.luogu.org/problemnew/show/P1291 水水的经典期望DP: 输出有毒.(其实也很简单啦) 代码如下: #include<iostream& ...

  5. 洛谷 P1582 倒水

    题目描述 一天,CC买了N个容量可以认为是无限大的瓶子,开始时每个瓶子里有1升水.接着~~CC发现瓶子实在太多了,于是他决定保留不超过K个瓶子.每次他选择两个当前含水量相同的瓶子,把一个瓶子的水全部倒 ...

  6. linux rpm 安装

    1.rpm 安装rpm -ivh package_name-i:install的意思-v:查看更详细的安装信息-h:以安装信息栏显示安装进度rpm -ivh package_name --test 2 ...

  7. python自动化测试学习笔记-9测试框架

    学习了这么久的python,我们已经可以自己搭建一个简单的测试和框架了,先从简单的开始,有时我们编写接口的测试用例会用excel进行编写,以下面的接口测试用例模板为例,进行编写:

  8. [GDOI2014]拯救莫莉斯

    题目描述 莫莉斯·乔是圣域里一个叱咤风云的人物,他凭借着自身超强的经济头脑,牢牢控制了圣域的石油市场. 圣域的地图可以看成是一个n*m的矩阵.每个整数坐标点(x , y)表示一座城市吗,两座城市间相邻 ...

  9. Hadoop Hive概念学习系列之HiveQL编译基础(十)

    由客户端提交的HiveQL语句将最终被转换为一个或多个MapReduce任务并提交由Hadoop执行.不包含聚合和连接的简单SELECT语句可以使用一个单独的只包含Map阶段的任务实现.使用GROUP ...

  10. C#WinForm的DataGridView控件显示行号

    public void ShowIndex(DataGridView dgv)        {                       for (int i = 0; i < dgv.Ro ...