Android原生支持sqlite數據庫操做,sqlite時輕量級關係型數據庫,支持標準sql語句。Android對sqlite進行良好的接口封裝來避免sql注入等安全問題。android
本文解決的問題:
一、Android App內如何建立數據庫
二、Android App內建立數據庫如何自定義文件路徑
三、Android App內獲取數據庫流程解析
四、無Context模式使用數據庫,可在uiautomator1.0測試框架和其餘app_process啓動的進程內使用數據庫。sql
Android應用內使用數據庫須要藉助於SQLiteOpenHelper類實現對數據庫的操做,使用數據庫經過如下幾步:
一、建立私有類集成自SQLiteOpenHelper方法,並覆寫onCreate、onUpdate方法實現對數據庫升級降級操做。
二、獲取SQLiteOpenHelper對象實例。
三、獲取只讀、讀寫類型數據庫SQLiteDatabase對象實例(getReadableDatabase()/getWritableDatabase()),當數據庫升級或建立時纔會調用onCreate、onUpdate方法。
四、使用SQLiteDatabase接口實現數據庫操做(增刪改查)數據庫
//一、建立私有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) { //數據庫升級時調用 } } //二、獲取SQLiteOpenHelper對象實例。 int dbVersion=0; DatabaseHelper databaseHelper=new (context,"test.db",dbVersion); //三、獲取只讀、讀寫類型數據庫SQLiteDatabase對象實例 SQLiteDatabase db = databaseHelper.getWritableDatabase(); //四、使用SQLiteDatabase接口實現數據庫操做(增刪改查) String sql = "select * from testTable where id=?" Cursor cursor = db.rawQuery(sql, new String[]{"1111"});
一、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); }
二、獲取數據庫實例:真正建立數據庫文件安全
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(); } } }
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(); } } }
三、ContextImpl獲取數據庫路徑app
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; }
經過上面源碼分析發現獲取數據庫對象的具體調用邏輯:框架
一、經過getWritableDatabase()方法獲取數據庫對象,實際是調用getDatabaseLocked(true)方法。ide
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; }
咱們在使用數據庫時有時候須要自定義數據庫存儲路徑,下面介紹三種改變數據庫文件路徑的方法。
一、自定義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/"); } } 二、在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> 四、獲取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);
class DatabaseHelper extends SQLiteOpenHelper { //一、指定數據庫文件名 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(); } }
注意
方法1、方法二須要傳遞context對象,若是Context爲null,獲取數據庫實例時會致使空指針異常。
方法三的實現不依賴Context對象,能夠將Context指定爲null。