1. 程式人生 > >解決:Android使用自帶sqlite開發時,apk中建立的資料庫外部的程序是沒有許可權去讀/寫的,而且無法如何讀取指定目錄下的db檔案

解決:Android使用自帶sqlite開發時,apk中建立的資料庫外部的程序是沒有許可權去讀/寫的,而且無法如何讀取指定目錄下的db檔案

SQLiteOpenHelper是Android框架為我們提供的一個非常好的資料庫開啟、升級與關閉的工具類。但是這個工具類會自動把db檔案建立到“ /data/data/com.*.*(package name)/” 目錄下,這麼做可能是與Android檔案系統的設計思路有關。

但是在實戰過程中,我們可能有各種原因需要自定義db檔案路徑(例如db檔案較大放到sd卡更安全等等),相信很多人都遇到了這個需求,網上也有很多解決方法,這些方法大多是拋棄Android框架為我們提供的SQLiteOpenHelper類,自己重頭寫一個DbHelper類完成自定義路徑的資料庫開啟關閉等。這麼做雖然可以解決問題,但並不是一個最好的方法,因為自己寫的DbHelper可靠性和功能自然難和google巨匠相。


本文提出一種方法,通過繼承和新增程式碼,並複用SQLiteOpenHelper的程式碼,來解決自定義db路徑的問題。

首先我們來分析一下SQLiteOpenHelper的原始碼。getReadableDatabase()和getWritableDatabase()在內部都是呼叫getDatabaseLocked()。getDatabaseLocked()的原始碼很容易理解,分析得知:

  • 如果以只讀方式開啟,是通過mContext.getDatabasePath(mName)來獲取db檔案的路徑並使用SQLiteDatabase.openDatabase()直接開啟資料庫;
  • 如果以讀寫方式開啟,是通過mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ? Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0, mFactory, mErrorHandler)開啟或建立資料庫。

所以我們需要改變mContext的行為。Android框架提供了一個ContextWrapper類,是Context的一個代理,可以通過繼承的方式拉改變Context的行為,所以我們繼承ContextWrapper,程式碼如下:


class CustomPathDatabaseContext extends ContextWrapper{

                private String mDirPath;
                
                public CustomPathDatabaseContext(Context base, String dirPath) {
                        super(base);
                        this.mDirPath = dirPath;
                }
                
                @Override
                public File getDatabasePath(String name) 
                {
                    File result = new File(mDirPath + File.separator + name);

                    if (!result.getParentFile().exists())
                    {
                        result.getParentFile().mkdirs();
                    }

                    return result;
                }
                
                @Override 
                public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory)
                {
                        return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), factory);
                }
                @Override 
                public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler){
                       return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name).getAbsolutePath(), factory, errorHandler);
                }


        }




上述程式碼很簡單了,就不用多說明了吧,然後我們在繼承SQLiteOpenHelper時這麼寫就可以了:
class YourDbHelper extends SQLiteOpenHelper{

        public YourDbHelper(Context context, String name, CursorFactory factory,
                        int version) {
                super(new CustomPathDatabaseContext(context, getDirPath()), name, factory, version);
        }

        /**
         * 獲取db檔案在sd卡的路徑
         * @return
         */
        private static String getDirPath(){
                //TODO 這裡返回存放db的資料夾的絕對路徑
                return "";
        }
        
        @Override
        public void onCreate(SQLiteDatabase db) {
                
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                
        }
        
}



如此一來,我們既可以自定義db檔案路徑,又可以複用SQLiteOpenHelper十分好用的功能了~


另外需要注意的是,有些應用可能是有一個已建好表的db檔案放在assets中,應用執行時先判斷db檔案是否存在,如果不存在則從assets中複製到自定義路徑。這種情況通常都是在PC端使用SQLiteSpy諸如此類的工具寫sql建表,使用這種方法的小夥伴們別忘了在建表時執行  PRAGMA schema_version = 1   這句sql(當然了版本號取決於您的需求) , 否則SQLiteOpenHelper還是會觸發onCreate的~看了SQLiteOpenHelper什麼時候觸發onCreate的原始碼就明白怎麼回事了~




        另補充: 之前的程式碼有個小問題,CustomPathDatabaseContext 在重寫方法時,不但要重寫openOrCreateDatabase(String name, int mode, CursorFactory factory),這個是android2.x呼叫的方法,還需要重寫openOrCreateDatabase(String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler),這個是android4.x呼叫的方法~~