Android官方默认的数据库就是SQLite数据库,并且相应的Android API 也对SQLite数据做了一定的支持,下面主要通过四个方面来了解一下,具体在Android的程序代码当中如何使用相应的数据库。
一、创建数据库,(SQLiteOpenHelper) Android系统当中,数据库其实就是一个文件系统的文件,不过这个文件是以DB结尾的文件。创建数据库,主要分为两种,就是使用默认的SQLiteOpenHelper来创建属于当前应用程序的沙盒数据库,什么是沙盒数据库呢,就是默认的db文件在/data/data当中,第二种创建数据库就是在SD卡当中创建数据库。下面通过两个示例来了解一下。
1、使用默认的SQLiteOpenHelper创建沙盒数据库。 使用这种方式,主要就是让数据库当中的数据只给当前的应用来使用,当然通过ContentProvider属于另当别论了,想要创建一个数据库,很简单,我们只需要创建一个类,让这个来继承相应的SQLiteOpenHelper即可,重写相应的方法,来让Android来进行回调,完成一个数据库的创建,下面就是示例代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 import android.content.Context;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;public class KeyValueDataHelper extends SQLiteOpenHelper { private static final String DB_NAME = "test.db" ; private static final int DB_VERSION = 1 ; private static final String DB_INIT_TABLE = "CREATE TABLE test(id INTEGER " + "PRIMARY KEY NOT NULL,key TEXT,value TEXT)" ; public KeyValueDataHelper (Context context) { super (context, DB_NAME, null , DB_VERSION); } @Override public void onCreate (SQLiteDatabase db) { db.execSQL(DB_INIT_TABLE); } @Override public void onConfigure (SQLiteDatabase db) { super .onConfigure(db); } @Override public void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) { } @Override public void onDowngrade (SQLiteDatabase db, int oldVersion, int newVersion) { super .onDowngrade(db, oldVersion, newVersion); } @Override public String getDatabaseName () { return super .getDatabaseName(); } @Override public SQLiteDatabase getReadableDatabase () { return super .getReadableDatabase(); } @Override public SQLiteDatabase getWritableDatabase () { return super .getWritableDatabase(); } @Override public void setWriteAheadLoggingEnabled (boolean enabled) { super .setWriteAheadLoggingEnabled(enabled); } }
上述代码当中,应该注意的东西都使用注释的形式,进行了相应的标注,这里还有两个比较容易混淆的两个概念,和一个关于初始化SQLiteOpenHelper的相应问题,需要我们具体的进行讨论。
1.1、getReadableDatabase()方法和getWritableDatabase()的区别究竟是什么? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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); } } public static SQLiteDatabase openDatabase (String path, CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) { SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler); db.open(); return db;
在源码当中找到了,上述一段代码。可以大体看一下,上述代码用语言总结一下就是
getWritableDatabase()和getReadableDatabase()方法都可以获取一个用于操作数据库的SQLiteDatabase实例。但是getWritableDatabase()方法以读写方式打开数据库,一旦数据库的磁盘空间满了,数据库就只能读而不能写,getWritableDatabase()打开数据库就会出错。getReadableDatabase()方法先以读写方式打开数据库,倘若使用如果数据库的磁盘空间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。
一般情况下两者返回情况都是相同的,唯一的区别是:在数据库仅开放只读权限或磁盘已满时,getReadableDatabase只会返回一个只读的数据库对象。
1.2、SQLiteOpenHelper的初始化,做了哪些重量级的操作,真正的初始化工作,是在什么地方开始做的。 通过研究SQLiteOpenHelper的源码,我们发现正常去和数据库建立连接不是在new SQLiteOpenHelper的时候触发的,真正触发的点就是getWritableDatabase()和getReadableDatabase(),所以在使用的时候,切记UI线程当中最好不要去调用getReadableDatabase/getWritableDatabase这两个方法,这只是为了我们程序的稳定性来说的。
2、在文件系统的SD当中创建数据库。 通过研究和仿照SQLiteOpenHelper,可以提取到精髓的地方,那就是可以使用Google的方式,在SD卡当中创建一个数据库,首先说说在什么情况下,需要在SD卡上面创建我们的数据库,也就是说在SD卡上面创建数据库的优点是什么?
2.1、当系统卸载我们的应用的时候,当我们重新安装应用的时候,数据不会丢失。 2.2、方便备份、恢复。 当然缺点也是有的,数据库不安全等。笔者这里以技术的角度来说明,如何在SD卡上面创建我们的数据,但是不代表笔者赞同这一种做法,这种做法会很严重的破坏Android的生态,对于用户来说,安装了应用,在卸载了应用以后,数据没有清干净,这就代表着,应用程序不是以一个完整沙盒的模式呈现给操作系统,操作系统也不方便管理应用程序,
笔者在这里的建议就是,最好使用Google原生提供的目录来存储哪些需要存储的数据。给出以下目录,供各位参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 File cacheDir = getCacheDir(); Log.d("TAG" , "getCacheDir() : " + cacheDir.getAbsolutePath()); File fileDir = getFilesDir(); Log.d("TAG" , "getFilesDir() : " + fileDir.getAbsolutePath()); File dir = getDir("fileName" , MODE_PRIVATE); Log.d("TAG" , "getDir() : " + dir.getAbsolutePath()); File externalCacheDir = getExternalCacheDir(); Log.d("TAG" , "getExternalCacheDir() : " + externalCacheDir.getAbsolutePath()); File externalFilesDir = getExternalFilesDir(Environment.DIRECTORY_MUSIC); Log.d("TAG" , "getExternalFilesDir() : " + externalFilesDir.getAbsolutePath()); File[] externalCacheDirs = getExternalCacheDirs(); for (int i = 0 ; i < externalCacheDirs.length; i++) { Log.d("TAG" , "getExternalCacheDirs() " + i + " : " + externalCacheDirs[i].getAbsolutePath()); } File[] externalFilesDirs = getExternalFilesDirs(Environment.DIRECTORY_MUSIC); for (int i = 0 ; i < externalFilesDirs.length; i++) { Log.d("TAG" , "getExternalFilesDirs() " + i + " : " + externalFilesDirs[i].getAbsolutePath()); } File[] externalMediaDirs = getExternalMediaDirs(); for (int i = 0 ; i < externalMediaDirs.length; i++) { Log.d("TAG" , "getExternalMediaDirs() " + i + " : " + externalMediaDirs[i].getAbsolutePath()); } File databasePath = getDatabasePath("database.db" );
SD卡创建数据库的具体思路是: 通过android的SQLiteOpenHelper类的源码,可以看到SQLiteOpenHelper类的getWritableDatabase这个接口实际上调用的是Context的openOrCreateDatabase方法,而这个方法是不支持带路径的数据库名称的,也就是说,用这个方法创建的数据库只能放在/data/data/包名称/ 目录下;要想在SD卡上创建数据库,我们可以调用SQLiteDatabase类的openOrCreateDatabase方法,这个方法是支持带路径的数据库名称的。
下面来看一则示例,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 import android.database.sqlite.SQLiteDatabase;import android.os.Environment;import java.io.File;import java.io.IOException;public class DatabaseSDManager { private static final String DATABASE_FILE_DIRECTORY = "/dateBase" ; private static final String DATABASE_PATH = Environment.getExternalStorageDirectory().getPath() + File.separator + DatabaseSDManager.class.getPackage().getName() + DATABASE_FILE_DIRECTORY; private String mDataBaseName ; private SQLiteDatabase.CursorFactory mFactory ; private SQLiteDatabase mSQLiteDatabase ; public DatabaseSDManager (String dtaBaseName) { this (dtaBaseName,null ); } public DatabaseSDManager (String dataBaseName, SQLiteDatabase.CursorFactory factory ) { this .mDataBaseName = dataBaseName ; this .mFactory = factory ; } public SQLiteDatabase getDataBase () { if (mSQLiteDatabase == null ){ mSQLiteDatabase = SQLiteDatabase. openOrCreateDatabase(initDatabaseFilePath(), this .mFactory); } return mSQLiteDatabase; } private File initDatabaseFilePath () { boolean sdIsExist = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); if (!sdIsExist){ throw new IllegalArgumentException("SD is not Find, Please check Sd state" ); } File dirfile = new File(DATABASE_PATH + File.separator); if (!dirfile.exists()){ if (!dirfile.mkdirs()){ throw new RuntimeException("create file is fail" ); } } boolean isCreateDbFileSuccess = false ; File dbFile = new File(dirfile, this .mDataBaseName); if (!dbFile.exists()){ try { isCreateDbFileSuccess = dbFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } else { isCreateDbFileSuccess = true ; } return isCreateDbFileSuccess ? dbFile : null ; } public void close () { if (mSQLiteDatabase != null ){ mSQLiteDatabase.close(); mSQLiteDatabase = null ; } } }
上述的Demo当中,我们没有加入Version的维护,只是一个简单的小例子,如果在开发工作当中,必定会加入版本的维护,具体代码如何实现,可以具体参考SQLiteOpenHelper的实现,
还有一种实现方式便是我们自定义Context来实现,我们通过源码知道,我们使用SQLiteOpenHelper的时候,具体的路径是写死的,我们可以自定义Context来实现我们的这个功能,具体的源码实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 import android.content.Context;import android.content.ContextWrapper;import android.database.DatabaseErrorHandler;import android.database.sqlite.SQLiteDatabase;import android.util.Log;import java.io.File;import java.io.IOException;public class DataBaseContext extends ContextWrapper { public DataBaseContext (Context base) { super (base); } @Override public File getDatabasePath (String name) { boolean sdExist = android.os.Environment.MEDIA_MOUNTED.equals(android.os.Environment.getExternalStorageState()); if (!sdExist) { Log.e("SD卡管理:" , "SD卡不存在,请加载SD卡" ); return null ; } else { String dbDir = android.os.Environment.getExternalStorageDirectory().toString(); dbDir += "/scexam" ; String dbPath = dbDir + "/" + name; File dirFile = new File(dbDir); if (!dirFile.exists()) dirFile.mkdirs(); boolean isFileCreateSuccess = false ; File dbFile = new File(dbPath); if (!dbFile.exists()) { try { isFileCreateSuccess = dbFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } else isFileCreateSuccess = true ; if (isFileCreateSuccess) return dbFile; else return null ; } } @Override public SQLiteDatabase openOrCreateDatabase (String name, int mode, SQLiteDatabase.CursorFactory factory) { SQLiteDatabase result = SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null ); return result; } @Override public SQLiteDatabase openOrCreateDatabase (String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { SQLiteDatabase result = SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null ); return result; } }
然后,我们使用的时候只要按照,最初的时候就OK。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import android.content.Context;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;public class DbOpenHelper extends SQLiteOpenHelper { private static final String DB_NAME = "test.db" ; private static final int VERSION = 1 ; public DbOpenHelper (Context context) { super (context instanceof DataBaseContext ? context : new DataBaseContext(context), DB_NAME, null , VERSION); } @Override public void onCreate (SQLiteDatabase db) { db.execSQL("CREATE TABLE IF NOT EXISTS exam_type (id integer primary key autoincrement, type_name varchar(100), type_id INTEGER)" ); } @Override public void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) { } }
到此,关于数据库的创建的所有知识都梳理了一遍。
二、管理数据库 据Google官方所说,一个打开数据库的连接,将会占用1kb的内存,在内存受限的移动设备来说,这1kb足够引起我们的注意,现在来讨论一下,如何管理一个数据库的生命周期,这里笔者给出两种管理策略,这两种策略也是我们日常开发当中常常使用的,具体的策略如下所示:
1、让应用程序来一直持有相应的数据库连接的引用,也就是一直持有SQLitDatabase这个类的引用。 这种方式很简单,就是申明一个成员变量来保持当前数据库引用。如果我们这样做了,其实就代表着这一块的内存,我们无法回收,并且无法重用。这里需要注意的是,当我们的应用程序至于后台,并且在手机内存不足的情况下回收内存的时候,如果我们没有显示的关闭和数据库的连接,那么应用程序将会报错。所以在使用这一种的策略的时候,一定要注意关于db的关闭问题。
其次还有一个需要我们注意的是在Activity当中持有一个数据库的连接的时候,必要的是在Actiivty销毁的时候,关闭该连接,其实如果我们的应用程序当中存在大量的数据访问工作,我们需要在Application当中去持有这个连接引用,好方便在多个Actiivty,Service当中去获取这个引用,具体实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import android.app.Application;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;import suansuan.com.contentprovider.sql.KeyValueDataHelper;public class DemoApplication extends Application { private SQLiteOpenHelper mDbHelper ; private Thread mUiThread ; @Override public void onCreate () { super .onCreate(); mDbHelper = new KeyValueDataHelper(this ); mUiThread = Thread.currentThread() ; } public SQLiteDatabase getSQLiteDatabase () { if (Thread.currentThread().equals(mUiThread)){ throw new RuntimeException("Database opened on main Thread" ); } return mDbHelper.getWritableDatabase(); } }
2、需要连接的时候再获取。 使用这种策略,必须要清楚当前应用程序当中有哪些对象获取了数据库的连接,关于Java的垃圾回收机制,我们知道当一个对象只有不被任何对象引用的时候,才会被回收,因此,如果我们关闭数据库而没有释放所有对它的引用,将是毫无用处的,因为除非释放最后一个引用,否则该对象不能被回收。我们知道SQLiteOpenHelper、SQLiteQuery、SQLiteCursor保存着数据库的引用,所以在不用的时候,记得回收。
三、操作数据库 1、基本SQL语句的嵌入 Android,当中提供了SQLiteDatebase这个类来操作数据库,这个类提供了一个最基本的和数据库交互的方法,那就是execSQL这个方法,通过这个最基本的方法,我们就可以去操作数据库,可以使用这个方法去创建表结构等,但是使用这种方法,有很多的确定,下列就来描述一下,对开发人员来说很不要的缺点。
A、在SQLite数据库当中有一部分的命令不是直接解释给SQL的,而是面向SQLite的,我们称这些为元命令,使用execSQL方法的第一个缺点就是不能执行所谓的元命令。常见元命令有以下这几种
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 .nullvalue STRING 在 NULL 值的地方输出 STRING 字符串。 .output FILENAME 发送输出到 FILENAME 文件。 .output stdout 发送输出到屏幕。 .print STRING... 逐字地输出 STRING 字符串。 .prompt MAIN CONTINUE 替换标准提示符。 .quit 退出 SQLite 提示符。 .read FILENAME 执行 FILENAME 文件中的 SQL。 .schema ?TABLE? 显示 CREATE 语句。如果指定了 TABLE 表,则只显示匹配 LIKE 模式的 TABLE 表。 .separator STRING 改变输出模式和 .import 所使用的分隔符。 .show 显示各种设置的当前值。 .stats ON |OFF 开启或关闭统计。 .tables ?PATTERN? 列出匹配 LIKE 模式的表的名称。 .timeout MS 尝试打开锁定的表 MS 毫秒。 .width NUM NUM 为 "column" 模式设置列宽度。 .timer ON |OFF 开启或关闭 CPU 定时器。
B、不能打包多条SQL语句,按照规定在使用方法execSQL这个方法的时候,按理应该只有一条语句。如果我们非要插入多条语句,并且使用分号隔开,那么以第一个分号结尾以后的语句将被忽略。
C、不能使用query相关的语句,execSQL这个方法是没有返回值的,所以涉及到查询语句将不能被使用。并且当我们使用execSQL这个方法查询时候,会抛出异常,提示要使用别的方法。这里会出现一个rawSQL的方法,其实这个方法也属于低级的方法。
一般来说,execSQL、rawSQL这两个方法来说就像是一起冷兵器,开发人员很少使用,最多execSQL用来创建表结构,触发器结构,而rawSQL开发人员基本就没有用过。不是作为编程执行SQL的一般性工具,相反,它们应该是用作访问不常用的SQL的最后手段,
为了替代应用程序代码中嵌入未经检查、非类型化的SQL字符串,Android SQLite API把SQL语义放入到API方法当中,主要分为四类,删除、更新、插入、查询。
2、删除 2.1、对外提供接口 通过观察SQLiteDatabase的API,我们不难发现有一个方法,是删除表里面的数据的,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public int delete (String table, String whereClause, String[] whereArgs) { acquireReference(); try { SQLiteStatement statement = new SQLiteStatement(this , "DELETE FROM " + table + (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : "" ), whereArgs); try { return statement.executeUpdateDelete(); } finally { statement.close(); } } finally { releaseReference(); } }
通过源码,我们不难看出,这里SQLiteDatabase把相应的操作是委托给了SQLiteStatement,让其进行了相应的删除操作,并且相应的SQL语句也是在这里拼接的。具体内部是如何实现的,不是本章的内容。如果感兴趣可以给下层追一下。
3、更新 实现更新操作的Android API这一次列操作也是比较少的,包括两个常用方法update、updateWithOnConflict,这两个的具体实现如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public int update (String table, ContentValues values, String whereClause, String[] whereArgs) { return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE); } public int updateWithOnConflict (String table, ContentValues values, String whereClause, String[] whereArgs, int conflictAlgorithm) { if (values == null || values.size() == 0 ) { throw new IllegalArgumentException("Empty values" ); } acquireReference(); try { StringBuilder sql = new StringBuilder(120 ); sql.append("UPDATE " ); sql.append(CONFLICT_VALUES[conflictAlgorithm]); sql.append(table); sql.append(" SET " ); int setValuesSize = values.size(); int bindArgsSize = (whereArgs == null ) ? setValuesSize : (setValuesSize + whereArgs.length); Object[] bindArgs = new Object[bindArgsSize]; int i = 0 ; for (String colName : values.keySet()) { sql.append((i > 0 ) ? "," : "" ); sql.append(colName); bindArgs[i++] = values.get(colName); sql.append("=?" ); } if (whereArgs != null ) { for (i = setValuesSize; i < bindArgsSize; i++) { bindArgs[i] = whereArgs[i - setValuesSize]; } } if (!TextUtils.isEmpty(whereClause)) { sql.append(" WHERE " ); sql.append(whereClause); } SQLiteStatement statement = new SQLiteStatement(this , sql.toString(), bindArgs); try { return statement.executeUpdateDelete(); } finally { statement.close(); } } finally { releaseReference(); } }
可以看出这里,update属于updateWithOnConflict的子集,上述做了两件事,和上述的删除操作类似,拼接SQL语句,创建一个SQLiteStatement,最后通过statement去执行相应的操作,这里说一下这两个更新方法的区别是什么?
updateWithOnConflict方法含义呢,就是在如果修改中破坏了相应的主键约束,或者说是一定的约束条件,不仅仅是主键约束,需要底层需要如何处理,也就是当发生破坏表字段的约束的时候,需要数据库底层去准备哪些策略,具体策略,可以参考上述代码的注释
ContentValuesd,Android SQLite官方所引入的新的一种数据结构,当我们去修改,或者插入的时候,通过构造着一种数据结构,来整理数据,以便高效的插入。其实它的内部的实现就是一个简单的HashMap<K,V>,那么仔细想一下,为什么不直接使用Map就可以插入,还要多封装一种数据结构呢,其实很简单,只是为了适配相应的SQLite数据库,因为数据库是弱类型的,所以需要这种数据结构,我们看几个它的方法,相信各位看客就明白为什么需要它的原因了,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 public final class ContentValues implements Parcelable { public static final String TAG = "ContentValues" ; private HashMap<String, Object> mValues; public void put (String key, Short value) { mValues.put(key, value); } public void put (String key, Integer value) { mValues.put(key, value); } public void put (String key, Long value) { mValues.put(key, value); } …. public String getAsString (String key) { Object value = mValues.get(key); return value != null ? value.toString() : null ; } public Long getAsLong (String key) { Object value = mValues.get(key); try { return value != null ? ((Number) value).longValue() : null ; } catch (ClassCastException e) { if (value instanceof CharSequence) { try { return Long.valueOf(value.toString()); } catch (NumberFormatException e2) { Log.e(TAG, "Cannot parse Long value for " + value + " at key " + key); return null ; } } else { Log.e(TAG, "Cannot cast value for " + key + " to a Long: " + value, e); return null ; } } } public Integer getAsInteger (String key) { Object value = mValues.get(key); try { return value != null ? ((Number) value).intValue() : null ; } catch (ClassCastException e) { if (value instanceof CharSequence) { try { return Integer.valueOf(value.toString()); } catch (NumberFormatException e2) { Log.e(TAG, "Cannot parse Integer value for " + value + " at key " + key); return null ; } } else { Log.e(TAG, "Cannot cast value for " + key + " to a Integer: " + value, e); return null ; } } } }
4、插入 插入系列的方法个更新系列的方法几乎相同,但是我们需要注意一点比较重要的就是插入方法可以捕获SQLite的异常,通过insertOrThrow这个方法。还有一个方法就是replace这个方法,其实这个方法很危险,为什么说这个方法危险呢,主要就是它的功能导致的,它的功能为插入一条数据转化为使用OR_EREPLACE_ONCONFLICT算法,已解决违反约束的行为,如果插入数据没有造成冲突,replace方法将插入一个新行,如果造成了冲突,现有行将被替代为新的值。下面看看这三个方法在底层如何实现的,相信大家也就懂了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public long insert (String table, String nullColumnHack, ContentValues values) { try { return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); } catch (SQLException e) { Log.e(TAG, "Error inserting " + values, e); return -1 ; } } public long insertOrThrow (String table, String nullColumnHack, ContentValues values) throws SQLException { return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE); } public long replace (String table, String nullColumnHack, ContentValues initialValues) { try { return insertWithOnConflict(table, nullColumnHack, initialValues, CONFLICT_REPLACE); } catch (SQLException e) { Log.e(TAG, "Error inserting " + initialValues, e); return -1 ; } } public long replaceOrThrow (String table, String nullColumnHack, ContentValues initialValues) throws SQLException { return insertWithOnConflict(table, nullColumnHack, initialValues, CONFLICT_REPLACE); } public long insertWithOnConflict (String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) { acquireReference(); try { StringBuilder sql = new StringBuilder(); sql.append("INSERT" ); sql.append(CONFLICT_VALUES[conflictAlgorithm]); sql.append(" INTO " ); sql.append(table); sql.append('(' ); Object[] bindArgs = null ; int size = (initialValues != null && initialValues.size() > 0 ) ? initialValues.size() : 0 ; if (size > 0 ) { bindArgs = new Object[size]; int i = 0 ; for (String colName : initialValues.keySet()) { sql.append((i > 0 ) ? "," : "" ); sql.append(colName); bindArgs[i++] = initialValues.get(colName); } sql.append(')' ); sql.append(" VALUES (" ); for (i = 0 ; i < size; i++) { sql.append((i > 0 ) ? ",?" : "?" ); } } else { sql.append(nullColumnHack + ") VALUES (NULL" ); } sql.append(')' ); SQLiteStatement statement = new SQLiteStatement(this , sql.toString(), bindArgs); try { return statement.executeInsert(); } finally { statement.close(); } } finally { releaseReference(); } }
5、查询
6、事物
7、批处理
这里还有三点,这个我们后面会说到
四、应用程序中的使用 1、数据库在应用程序中所处的地位 整个开发当中,数据无时无刻都起着很重要的作用,说白的了,程序就是操作数据的指令集而已,MVC,MVP等设计模式,也是将展示和数据分离,在Android当中已是如此,其实在很久以前,分层设计能很好的体现出数据源与视图的关系,熟悉J2EE开发人员应该清楚,程序的底层数据设计DAO,数据操作对象是以何种身份呈现说出来的,这种设计其实在Android当中也是适用的。统一数据源,通过特定的接口统一暴露给上层,然后通过相关的Adapter去设置数据。
网络数据的时候,由于是通过网络拿数据,很多开发者遍忽略了DAO,其实可以理解为网络数据,就是数据库在远端的情况,并且在很多的大型商业项目当中,也是通过ContentProvider去维护网络数据的,这个后面部分笔者应该也会提到,当我们去查找,或者删除的时候去触发远端数据的同步过程,
下面详细的讨论一下,数据库在Android应用程序当中所处的位置。
1.1、UI与DB UI和DB就是MVP,MVC,MVVP这些设计模式当中提到的很重要的两个关键点,视图与数据源,Android当中的DB一般都会嵌入到ContentProvider当中使用,而相应的视图则有Activity来代替,而相应的中间交互对象则就是接口层CRUD和cursor,通常称之为REST-like模型,但是由于Android当中特殊的线程规定,主线程不能做耗时操作等,所以在从数据源当中拿去Corsur就有点曲折了。
1.2、Cursor与CursorFactory Cursor就是我们从数据库当中查询到的数据集合,是一种二维形式的数据结构,有点类似于二维数组,包括行和列,还存在一个指向当前行的指针,该指针介于-1到getCount之间,
https://blog.csdn.net/italywj222/article/details/50418577 https://blog.csdn.net/howlaa/article/details/46707159