再读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. //1、创建私有SQLiteOpenHelper类
  2. class DatabaseHelper extends SQLiteOpenHelper {
  3. public DatabaseHelper(Context context, String dbName, int version) {
  4. super(context, dbName, null, version);
  5. LogUtil.print("DatabaseHelper dbName[%s] version[%d]",dbName+"",version);
  6. }
  7. @Override
  8. public void onCreate(SQLiteDatabase db) {
  9. //数据库创建时调用
  10. }
  11. @Override
  12. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  13. //数据库升级时调用
  14. }
  15. }
  16. //2、获取SQLiteOpenHelper对象实例。
  17. int dbVersion=0;
  18. DatabaseHelper databaseHelper=new (context,"test.db",dbVersion);
  19. //3、获取只读、读写类型数据库SQLiteDatabase对象实例
  20. SQLiteDatabase db = databaseHelper.getWritableDatabase();
  21. //4、使用SQLiteDatabase接口实现数据库操作(增删改查)
  22. String sql = "select * from testTable where id=?"
  23. Cursor cursor = db.rawQuery(sql, new String[]{"1111"});

数据库源码解析

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

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

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

  • Android8.0及以下版本:区分writeable和readable数据库
  1. private SQLiteDatabase getDatabaseLocked(boolean writable) {
  2. if (mDatabase != null) {
  3. if (!mDatabase.isOpen()) {
  4. // Darn! The user closed the database by calling mDatabase.close().
  5. mDatabase = null;
  6. //如果获取只读数据库且缓存了可写数据库则直接返回
  7. } else if (!writable || !mDatabase.isReadOnly()) {
  8. // The database is already open for business.
  9. return mDatabase;
  10. }
  11. }
  12. if (mIsInitializing) {
  13. throw new IllegalStateException("getDatabase called recursively");
  14. }
  15. SQLiteDatabase db = mDatabase;
  16. try {
  17. mIsInitializing = true;
  18. if (db != null) {
  19. //如果获取可写数据库,复用缓存的只读数据库
  20. if (writable && db.isReadOnly()) {
  21. db.reopenReadWrite();
  22. }
  23. } else if (mName == null) {
  24. db = SQLiteDatabase.create(null);
  25. } else {
  26. try {
  27. //获取只读类型数据库实例
  28. if (DEBUG_STRICT_READONLY && !writable) {
  29. final String path = mContext.getDatabasePath(mName).getPath();
  30. db = SQLiteDatabase.openDatabase(path, mFactory,
  31. SQLiteDatabase.OPEN_READONLY, mErrorHandler);
  32. } else {
  33. //获取可读写数据库实例
  34. db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
  35. Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
  36. mFactory, mErrorHandler);
  37. }
  38. } catch (SQLiteException ex) {
  39. //获取数据库异常时,如果获取可写数据库,则直接抛出
  40. if (writable) {
  41. throw ex;
  42. }
  43. Log.e(TAG, "Couldn't open " + mName
  44. + " for writing (will try read-only):", ex);
  45. //获取数据库异常时,如果获取只读数据库,重试获取只读类型数据库。
  46. final String path = mContext.getDatabasePath(mName).getPath();
  47. db = SQLiteDatabase.openDatabase(path, mFactory,
  48. SQLiteDatabase.OPEN_READONLY, mErrorHandler);
  49. }
  50. }
  51. onConfigure(db);
  52. final int version = db.getVersion();
  53. //数据库升级处理逻辑
  54. if (version != mNewVersion) {
  55. if (db.isReadOnly()) {
  56. throw new SQLiteException("Can't upgrade read-only database from version " +
  57. db.getVersion() + " to " + mNewVersion + ": " + mName);
  58. }
  59. //程序运行时更新数据库:使用缓存数据库时走此逻辑,重新加载数据库
  60. if (version > 0 && version < mMinimumSupportedVersion) {
  61. File databaseFile = new File(db.getPath());
  62. onBeforeDelete(db);
  63. db.close();
  64. if (SQLiteDatabase.deleteDatabase(databaseFile)) {
  65. mIsInitializing = false;
  66. return getDatabaseLocked(writable);
  67. } else {
  68. throw new IllegalStateException("Unable to delete obsolete database "
  69. + mName + " with version " + version);
  70. }
  71. } else {
  72. //事务更新(降级/升级)数据库
  73. db.beginTransaction();
  74. try {
  75. if (version == 0) {
  76. onCreate(db);
  77. } else {
  78. if (version > mNewVersion) {
  79. onDowngrade(db, version, mNewVersion);
  80. } else {
  81. onUpgrade(db, version, mNewVersion);
  82. }
  83. }
  84. db.setVersion(mNewVersion);
  85. db.setTransactionSuccessful();
  86. } finally {
  87. db.endTransaction();
  88. }
  89. }
  90. }
  91. onOpen(db);
  92. if (db.isReadOnly()) {
  93. Log.w(TAG, "Opened " + mName + " in read-only mode");
  94. }
  95. mDatabase = db;
  96. return db;
  97. } finally {
  98. mIsInitializing = false;
  99. if (db != null && db != mDatabase) {
  100. db.close();
  101. }
  102. }
  103. }
  • Android8.1.0及以上版本:均获取writable类型数据库,仅在发生异常时返回readable类型数据库
  1. private SQLiteDatabase getDatabaseLocked(boolean writable) {
  2. if (mDatabase != null) {
  3. if (!mDatabase.isOpen()) {
  4. // Darn! The user closed the database by calling mDatabase.close().
  5. mDatabase = null;
  6. } else if (!writable || !mDatabase.isReadOnly()) {
  7. //如果获取只读数据库且缓存了可写数据库则直接返回
  8. // The database is already open for business.
  9. return mDatabase;
  10. }
  11. }
  12. if (mIsInitializing) {
  13. throw new IllegalStateException("getDatabase called recursively");
  14. }
  15. SQLiteDatabase db = mDatabase;
  16. try {
  17. mIsInitializing = true;
  18. if (db != null) {
  19. //如果缓存的数据库为只读类型的,并且当次获取可写类型数据库,则覆用缓存数据库
  20. if (writable && db.isReadOnly()) {
  21. db.reopenReadWrite();
  22. }
  23. } else if (mName == null) {
  24. db = SQLiteDatabase.createInMemory(mOpenParamsBuilder.build());
  25. } else {
  26. //获取数据库的存储路径:注-仅次一处使用到了context对象
  27. final File filePath = mContext.getDatabasePath(mName);
  28. SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
  29. try {
  30. //打开数据库实例对象
  31. db = SQLiteDatabase.openDatabase(filePath, params);
  32. // Keep pre-O-MR1 behavior by resetting file permissions to
  33. setFilePermissionsForDb(filePath.getPath());
  34. } catch (SQLException ex) {
  35. if (writable) {
  36. throw ex;
  37. }
  38. Log.e(TAG, "Couldn't open " + mName
  39. + " for writing (will try read-only):", ex);
  40. params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
  41. db = SQLiteDatabase.openDatabase(filePath, params);
  42. }
  43. }
  44. onConfigure(db);
  45. final int version = db.getVersion();
  46. if (version != mNewVersion) {
  47. if (db.isReadOnly()) {
  48. throw new SQLiteException("Can't upgrade read-only database from version " +
  49. db.getVersion() + " to " + mNewVersion + ": " + mName);
  50. }
  51. //程序运行时更新数据库:使用缓存数据库时走此逻辑,重新加载数据库
  52. if (version > 0 && version < mMinimumSupportedVersion) {
  53. File databaseFile = new File(db.getPath());
  54. onBeforeDelete(db);
  55. db.close();
  56. if (SQLiteDatabase.deleteDatabase(databaseFile)) {
  57. mIsInitializing = false;
  58. return getDatabaseLocked(writable);
  59. } else {
  60. throw new IllegalStateException("Unable to delete obsolete database "
  61. + mName + " with version " + version);
  62. }
  63. } else {
  64. //事务更新(降级/升级)数据库
  65. db.beginTransaction();
  66. try {
  67. if (version == 0) {
  68. onCreate(db);
  69. } else {
  70. if (version > mNewVersion) {
  71. onDowngrade(db, version, mNewVersion);
  72. } else {
  73. onUpgrade(db, version, mNewVersion);
  74. }
  75. }
  76. db.setVersion(mNewVersion);
  77. db.setTransactionSuccessful();
  78. } finally {
  79. db.endTransaction();
  80. }
  81. }
  82. }
  83. onOpen(db);
  84. if (db.isReadOnly()) {
  85. Log.w(TAG, "Opened " + mName + " in read-only mode");
  86. }
  87. mDatabase = db;
  88. return db;
  89. } finally {
  90. mIsInitializing = false;
  91. if (db != null && db != mDatabase) {
  92. db.close();
  93. }
  94. }
  95. }

3、ContextImpl获取数据库路径

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

数据库本地文件路径

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

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

  1. public SQLiteDatabase getWritableDatabase() {
  2. synchronized (this) {
  3. return getDatabaseLocked(true);
  4. }
  5. }

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

  1. private SQLiteDatabase getDatabaseLocked(boolean writable) {
  2. SQLiteDatabase db = mDatabase;
  3. try {
  4. mIsInitializing = true;
  5. if (db != null) {
  6. ...
  7. } else if (mName == null) {
  8. ...
  9. } else {
  10. final File filePath = mContext.getDatabasePath(mName);
  11. SQLiteDatabase.OpenParams params = mOpenParamsBuilder.build();
  12. try {
  13. db = SQLiteDatabase.openDatabase(filePath, params);
  14. ...
  15. } catch (SQLException ex) {
  16. ...
  17. db = SQLiteDatabase.openDatabase(filePath, params);
  18. }
  19. }
  20. ...
  21. mDatabase = db;
  22. return db;
  23. } finally {
  24. ...
  25. }
  26. }

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

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

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

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

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

    1、自定义Application

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

    3、在AndroidManifest.xml中指定自定义Application
  1. 1、自定义Application
  2. public class MyApplication extends Application{
  3. public static MyApplication getInstance()
  4. {
  5. return mInstance;
  6. }
  7. @Override
  8. public void onCreate()
  9. {
  10. super.onCreate();
  11. }
  12. @Override
  13. public File getDatabasePath(String name) {
  14. return new File("/sdcard/");
  15. }
  16. }
  17. 2、在manifest文件中指定application
  18. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  19. package="com.qihoo.qa">
  20. //申请权限
  21. ...
  22. <application
  23. android:name=".MyApplication"
  24. android:label="@string/app_name"
  25. android:icon="@drawable/ic_launcher"
  26. android:supportsRtl="true"
  27. android:theme="@style/AppTheme">
  28. </application>
  29. </manifest>
  30. 4、获取DatabaseHelper,数据库文件为/sdcard/test.db
  31. DatabaseHelper dataBaseHelper = new DatabaseHelper(context, "test.db", 0);
  • 二、构建数据库时传入绝对路径
  1. //获取DatabaseHelper,数据库文件为/sdcard/test.db
  2. DatabaseHelper dataBaseHelper = new DatabaseHelper(context, "/sdcard/test.db", 0);
  • 三、在SQLiteOpenHelper子类中覆写getWritableDatabase()和getReadableDatabase()

  1. class DatabaseHelper extends SQLiteOpenHelper {
  2. //1、指定数据库文件名
  3. String dbName="/sdcard/test.db";
  4. int version;
  5. public DatabaseHelper(Context context, String dbName, int version) {
  6. super(context, dbName, null, version);
  7. }
  8. @Override
  9. public void onCreate(SQLiteDatabase db) {
  10. //数据库创建时调用
  11. }
  12. @Override
  13. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  14. //数据库升级时调用
  15. }
  16. //覆写获取数据库实现
  17. @Override
  18. public SQLiteDatabase getWritableDatabase() {
  19. return getDataBase(dbName, version);
  20. }
  21. //覆写获取数据库实现
  22. @Override
  23. public SQLiteDatabase getReadableDatabase() {
  24. return getDataBase(dbName, version);
  25. }
  26. //实现获取数据库具体逻辑
  27. private SQLiteDatabase getDataBase(String fileName, int version){
  28. try {
  29. SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(fileName, null);
  30. int oldVer = database.getVersion();
  31. database.setVersion(version);
  32. if (version > oldVer) {
  33. this.onUpgrade(database, oldVer, version);
  34. }
  35. if(version<oldVer){
  36. this.onDowngrade(database, oldVer, version);
  37. }
  38. return database;
  39. } catch (Exception var4) {
  40. LogUtil.e(var4);
  41. return super.getWritableDatabase();
  42. }
  43. }

注意

方法一、方法二需要传递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. 告诉大家我是如何在14:00秒杀到 《深入理解Bootstrap》

    1.打开火狐,不用IE,3个评论窗口,层叠在一起,可以点击3次哦. 2.打开一个百度的现在时间,不能看你本机的时间,要互联网的时间. 3.等时间到13:59:59,开始依次点击按钮,总有你预想不到的结 ...

  2. NPOI设置Excel中的单元格识别为日期

    只有月/日/年的格式,才能显示为Date 其他的,都是显示为Custom

  3. bzoj 4337 树的同构

    4337: BJOI2015 树的同构 Description 树是一种很常见的数据结构. 我们把N个点,N-1条边的连通无向图称为树. 若将某个点作为根,从根开始遍历,则其它的点都有一个前驱,这个树 ...

  4. EasyUI Datagrid 分页显示(客户端)

    转自:https://blog.csdn.net/metal1/article/details/17536185 EasyUI Datagrid 分页显示(客户端) By ZYZ 在使用JQuery ...

  5. E20170603-ts

    sanitize vt. 净化; 进行消毒; 使清洁; 审查; omission  n. 遗漏; 疏忽; 省略,删节; [法] 不履行法律责任; separator   n. 分离器,分离装置; 防胀 ...

  6. 基于ASP.Net Core开发一套通用后台框架记录-(总述)

    写在前面 本系列博客是本人在学习的过程中搭建学习的记录,如果对你有所帮助那再好不过.如果您有发现错误,请告知我,我会第一时间修改. 前期我不会公开源码,我想是一点点敲代码,不然复制.粘贴那就没意思了. ...

  7. 最大加权矩形 luogu1719

    题目链接:https://www.luogu.org/problemnew/show/P1719 这道题挺好做的 又是一道练前缀和的题 #include <bits/stdc++.h> # ...

  8. mysql left join 出现的结果会重复

    left join 基本用法 MySQL left join 语句格式 A LEFT JOIN B ON 条件表达式 left join 是以A表为基础,A表即左表,B表即右表. 左表(A)的记录会全 ...

  9. python自动化测试学习笔记-10YAML

    之前学习的编写测试用例的方法,都是从excel中编写接口的测试用例,然后通过读取excel文件进行接口自动化测试,这种方式我们叫做数据驱动的方式,由于excel操作起来不灵活,无法实现关联关系的接口测 ...

  10. [C++ 多线程] 学习前瞻

    1 多线程是什么? 1.1 多线程的概念? 说起多线程,那么就不得不说什么是线程,而说起线程,又不得不说什么是进程. 进程可以简单的理解为一个可以独立运行的程序单位,它是线程的集合,进程就是有一个或多 ...