再讀Android sqlite

再讀Android sqlite

Android原生支持sqlite數據庫操做,sqlite時輕量級關係型數據庫,支持標準sql語句。Android對sqlite進行良好的接口封裝來避免sql注入等安全問題。android

本文解決的問題:
一、Android App內如何建立數據庫
二、Android App內建立數據庫如何自定義文件路徑
三、Android App內獲取數據庫流程解析
四、無Context模式使用數據庫,可在uiautomator1.0測試框架和其餘app_process啓動的進程內使用數據庫。sql

App內常規使用數據庫

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);
    }

二、獲取數據庫實例:真正建立數據庫文件安全

  • 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();
            }
        }
    }

三、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;
    }

自定義本地數據庫文件路徑

咱們在使用數據庫時有時候須要自定義數據庫存儲路徑,下面介紹三種改變數據庫文件路徑的方法。

  • 1、更改context中getDatabasesDir()的返回值
    一、自定義Application
    二、覆寫Application的getDatabasePath(String name)方法,在方法內指定自定義路徑。
    三、在AndroidManifest.xml中指定自定義Application
一、自定義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);
  • 2、構建數據庫時傳入絕對路徑
//獲取DatabaseHelper,數據庫文件爲/sdcard/test.db
DatabaseHelper dataBaseHelper =  new DatabaseHelper(context, "/sdcard/test.db", 0);
  • 3、在SQLiteOpenHelper子類中覆寫getWritableDatabase()和getReadableDatabase()
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。

使用數據庫的一些建議

  • 調用getWritableDatabase()獲取數據庫時會從新建立數據庫實例,通常在程序中複用該數據庫實例便可,若是保存多份數據庫實例會致使OOM異常。
  • 數據庫插入數據時使用SQLiteStatement對數據進行封裝,避免sql注入相關問題。
  • 數據庫查詢中使用方法Cursor rawQuery(String sql, String[] selectionArgs)指定動態參數,防止sql注入及異常字符問題。
相關文章
相關標籤/搜索