<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                在MediaProvider中觸發數據庫的是attach函數,其代碼如下: **MediaProvider::attach** ~~~ private Uri attachVolume(String volume) { Contextcontext = getContext(); DatabaseHelper db; if(INTERNAL_VOLUME.equals(volume)) { ......//針對內部存儲空間的數據庫 } elseif (EXTERNAL_VOLUME.equals(volume)) { ...... String dbName = "external-" +Integer.toHexString(volumeID) + ".db"; //①構造一個DatabaseHelper對象 db =new DatabaseHelper(context, dbName, false, false, mObjectRemovedCallback); ......//省略不相關的內容 }...... if(!db.mInternal) { //②調用DatabaseHelper的getWritableDatabase函數,該函數返回值的類型為 //SQLiteDatabase,即代表SQLite數據庫的對象 createDefaultFolders(db.getWritableDatabase()); ....... } ...... } ~~~ 以上代碼中列出了兩個關鍵點,分別是: - 構造一個DatabaseHelper對象。 - 調用DatabaseHelper對象的getWritableDatabase函數得到一個代表SQLite數據庫的SQLiteDatabase對象。 1. DatabaseHelper分析 DatabaseHelper是MediaProvider的內部類,它從SQLiteOpenHelper派生,其構造函數的代碼如下: (1) DatabaseHelper構造函數分析 **MediaProvider.java::DatabaseHelper** ~~~ public DatabaseHelper(Context context, Stringname, boolean internal, boolean earlyUpgrade, SQLiteDatabase.CustomFunction objectRemovedCallback) { //重點關注其基類的構造函數 super(context, name, null,DATABASE_VERSION); mContext = context; mName= name; mInternal = internal; mEarlyUpgrade = earlyUpgrade; mObjectRemovedCallback = objectRemovedCallback; } ~~~ SQLiteOpenHelper作為DatabaseHelper的基類,其構造函數的代碼如下: **SQLiteOpenHelper.java::SQLiteOpenHelper** ~~~ public SQLiteOpenHelper(Context context, Stringname, CursorFactory factory, int version) { //調用另外一個構造函數,注意它新建了一個默認的錯誤處理對象 this(context, name, factory, version, newDefaultDatabaseErrorHandler()); } public SQLiteOpenHelper(Context context, Stringname, CursorFactory factory, int version, DatabaseErrorHandlererrorHandler) { ...... mContext= context; mName =name; //看到”factory“一詞,讀者要能想到設計模式中的工廠模式,在本例中該變量為null mFactory= factory; mNewVersion = version; mErrorHandler= errorHandler; } ~~~ 上面這些函數都比較簡單,其中卻蘊含一個較為深刻的設計理念,具體如下: 從SQLiteOpenHelper的構造函數中可知,MediaProvider對應的數據庫對象(即SQLiteDatabase實例)并不在該函數中創建。那么,代表數據庫的SQLiteDatabase實例是何時創建呢? 此處使用了所謂的延遲創建(lazy creation)的方法,即SQLiteDatabase實例真正創建的時機是在第一次使用它的時候,也就是本例中第二個關鍵點函數getWritableDatabase。 在分析getWritableDatabase函數之前,先介紹一些和延遲創建相關的知識。 延遲創建或延遲初始化(lazy intializtion)所謂的“重型”資源(如占內存較大或創建時間比較長的資源),是系統開發和設計中常用的一種策略[^①]。在使用這種策略時,開發人員不僅在資源創建時“斤斤計較”,在資源釋放的問題上也是“慎之又。資源釋放的控制一般會采用引用計數技術。 結合前面對SQLiteDatabase的介紹會發現,SQLiteDatabase這個框架,在設計時不是簡單地將SQLite API映射到Java層,而是有大量更為細致的考慮。例如,在這個框架中,資源創建采用了lazy creation方法,資源釋放又利用SQLiteClosable來控制生命周期。 * * * * * **建議**:此框架雖更完善、更具擴展性,但是使用它比直接使用SQLite API要復雜得多,因此在開發過程中,應當根據實際情況綜合考慮是否使用該框架。例如,筆者在開發公司的DLNA解決方案時,就直接使用了SQLiteAPI,而沒使用這個框架。 * * * * * (2) getWritableDatabase函數分析 現在來看getWritableDatabase的代碼。 **SQLiteDatabase.java::getWritableDatabase** ~~~ public synchronized SQLiteDatabasegetWritableDatabase() { if(mDatabase != null) { //第一次調用該函數時mDatabase還未創建。以后的調用將直接返回已經創建好的mDatabase } booleansuccess = false; SQLiteDatabase db = null; if (mDatabase != null) mDatabase.lock(); try { mIsInitializing = true; if(mName == null) { db = SQLiteDatabase.create(null); }else { //①調用Context的openOrCreateDatabase創建數據庫 db = mContext.openOrCreateDatabase(mName, 0, mFactory, mErrorHandler); } intversion = db.getVersion(); if(version != mNewVersion) { db.beginTransaction(); try { if (version == 0) { /* 如果初次創建該數據庫(即對應的數據庫文件不存在),則調用子類實現的 onCreate函數。子類實現的onCreate函數將完成數據庫建表等操作。讀者不妨 查看MediaProviderDatabaseHelper實現的onCreate函數 */ onCreate(db); } else { //如果從數據庫文件中讀出來的版本號與MediaProvider設置的版本號不一致, //則調用子類實現的onDowngrade或onUpgrade做相應處理 if (version > mNewVersion) onDowngrade(db, version,mNewVersion); else onUpgrade(db, version,mNewVersion); } db.setVersion(mNewVersion); db.setTransactionSuccessful(); }finally { db.endTransaction(); } }// if (version != mNewVersion)判斷結束 onOpen(db);//調用子類實現的onOpen函數 success =true; return db; }...... ~~~ 由以上代碼可知,代表數據庫的SQLiteDatabase對象是由context openOrCreateDatabase創建的。下面單起一節具體分析此函數。 2. ContextImpl openOrCreateDatabase分析 (1) openOrCreateDatabase函數分析 相信讀者已能準確定位openOrCreateDatabase函數的真正實現了,它就在ContextImpl.java中,其代碼如下: **ContextImpl.java::openOrCreateDatabase** ~~~ public SQLiteDatabase openOrCreateDatabase(Stringname, int mode, CursorFactory factory,DatabaseErrorHandler errorHandler) { File f =validateFilePath(name, true); //調用SQLiteDatabase的靜態函數openOrCreateDatabase創建數據庫 SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f.getPath(), factory, errorHandler); setFilePermissionsFromMode(f.getPath(), mode, 0); return db; } ~~~ **SQLiteDatabase.java::openDatabase** ~~~ public static SQLiteDatabase openDatabase(Stringpath, CursorFactory factory, int flags,DatabaseErrorHandlererrorHandler) { //又調用openDatabase創建SQLiteDatabase實例,真的是層層轉包啊 SQLiteDatabase sqliteDatabase = openDatabase(path, factory, flags, errorHandler,(short) 0); if(sBlockSize == 0) sBlockSize = newStatFs("/data").getBlockSize(); //為該SQLiteDatabase實例設置一些參數。這些內容和SQLite本身的特性有關,本書不 //擬深入討論這方面的內容,感興趣的讀者不妨參考SQLite官網提供的資料 sqliteDatabase.setPageSize(sBlockSize); sqliteDatabase.setJournalMode(path, "TRUNCATE"); synchronized(mActiveDatabases) { mActiveDatabases.add( newWeakReference<SQLiteDatabase>(sqliteDatabase)); } returnsqliteDatabase; } ~~~ openDatabase將真正創建一個SQLiteDatabase實例,其相關代碼是: **SqliteDatabase.java::openDatabase** ~~~ private static SQLiteDatabase openDatabase(Stringpath, CursorFactory factory, int flags,DatabaseErrorHandler errorHandler, shortconnectionNum) { //構造一個SQLiteDatabase實例 SQLiteDatabase db = new SQLiteDatabase(path, factory, flags,errorHandler, connectionNum); try { db.dbopen(path, flags);//打開數據庫,dbopen是一個native函數 db.setLocale(Locale.getDefault());//設置Locale ...... returndb; }...... } ~~~ 其實openDatabase主要就干了兩件事情,即創建一個SQLiteDatabase實例,然后調用該實例的dbopen函數。 (2) SQLiteDatabase的構造函數及dbopen函數分析 先看SQLitedDatabase的構造函數,代碼如下: **SQLitedDatabase.java::SQLiteDatabas**e ~~~ private SQLiteDatabase(String path, CursorFactoryfactory, int flags, DatabaseErrorHandler errorHandler, short connectionNum) { setMaxSqlCacheSize(DEFAULT_SQL_CACHE_SIZE); mFlags =flags; mPath = path; mFactory= factory; mPrograms= new WeakHashMap<SQLiteClosable,Object>(); // config_cursorWindowSize值為2048,所以下面得到的limit值應該為8MB int limit= Resources.getSystem().getInteger( com.android.internal.R.integer.config_cursorWindowSize) * 1024 * 4; native_setSqliteSoftHeapLimit(limit); } ~~~ 前面說過,Java層的SQLiteDatabase對象會和一個Native層sqlite3實例綁定,從以上代碼中可發現,綁定的工作并未在構造函數中開展。實際上,該工作是由dbopen函數完成的,其相關代碼如下: **android_database_SQLiteDatabase.cpp::dbopen** ~~~ static void dbopen(JNIEnv* env, jobject object,jstring pathString, jint flags) { int err; sqlite3* handle = NULL; sqlite3_stmt * statement = NULL; charconst * path8 = env->GetStringUTFChars(pathString, NULL); intsqliteFlags; registerLoggingFunc(path8); if(flags & CREATE_IF_NECESSARY) { sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; } ...... //調用sqlite3_open_v2函數創建數據庫,sqlite3_open_v2和示例中的sqlite3_open類似 //handle用于存儲新創建的sqlite3*類型的實例 err =sqlite3_open_v2(path8, &handle, sqliteFlags, NULL); ...... sqlite3_soft_heap_limit(sSqliteSoftHeapLimit); err =sqlite3_busy_timeout(handle, 1000 /* ms */); ...... //Android在原生SQLite之上還做了一些特殊的定制,相關內容留待本節最后分析 err =register_android_functions(handle, UTF16_STORAGE); //將handle保存到Java層的SQLiteDatabase對象中,這樣Java層SQLiteDatabase實例 //就和一個Native層的sqlite3實例綁定到一起了 env->SetIntField(object, offset_db_handle, (int) handle); handle =NULL; // The caller owns the handle now. done: if(path8 != NULL) env->ReleaseStringUTFChars(pathString, path8); if(statement != NULL) sqlite3_finalize(statement); if(handle != NULL) sqlite3_close(handle); } ~~~ 從上述代碼可知,使用dbopen函數其實就是為了得到Native層的一個sqlite3實例。另外,Android對SQLite還設置了一些平臺相關的函數,這部分內容將在后文進行分析。 3. SQLiteCompiledSql介紹 前文曾提到,Native層sqlite3_stmt實例的封裝是由未對開發者公開的類SQLiteCompileSql完成的。由于它的隱秘性,沒有在圖7-4中把它列出來。現在我們就來揭開它神秘的面紗,其代碼如下: **SQLiteCompliteSql.java::SQLiteCompiledSql** ~~~ SQLiteCompiledSql(SQLiteDatabase db, String sql) { db.verifyDbIsOpen(); db.verifyLockOwner(); mDatabase = db; mSqlStmt= sql; ...... nHandle= db.mNativeHandle; native_compile(sql);//調用native_compile函數,代碼如下 } ~~~ **android_database_SQLiteCompliteSql.cpp::native_compile** ~~~ static void native_compile(JNIEnv* env, jobjectobject, jstring sqlString) { compile(env, object, GET_HANDLE(env, object), sqlString); } //來看compile的實現 sqlite3_stmt * compile(JNIEnv* env, jobjectobject, sqlite3 * handle,jstring sqlString) { int err; jcharconst * sql; jsizesqlLen; sqlite3_stmt * statement = GET_STATEMENT(env, object); if(statement != NULL) ....//釋放之前的sqlite3_stmt實例 sql = env->GetStringChars(sqlString,NULL); sqlLen =env->GetStringLength(sqlString); //調用sqlite3_prepare16_v2得到一個sqlite3_stmt實例 err =sqlite3_prepare16_v2(handle, sql, sqlLen * 2, &statement, NULL); env->ReleaseStringChars(sqlString, sql); if (err== SQLITE_OK) { //保存到Java層的SQLiteCompliteSql對象中 env->SetIntField(object, gStatementField, (int)statement); return statement; } ...... } ~~~ 當compile函數執行完后,一個綁定了SQL語句的sqlite3_stmt實例就和Java層的SQLiteCompileSql對象綁定到一起了。 4. Android SQLite自定義函數介紹 本節將介紹Android在SQLite上自定義的一些函數。一切還得從SQL的觸發器說起。 (1) 觸發器介紹 觸發器(Trigger)是數據庫開發技術中一個常見的術語。其本質非常簡單,就是在指定表上發生特定事情時,數據庫需要執行的某些操作。還是有點模糊吧?再來看MediaProvider設置的一個觸發器: ~~~ db.execSQL("CREATE TRIGGER IF NOT EXISTSimages_cleanup DELETE ON images " + "BEGIN " + "DELETE FROM thumbnails WHERE image_id = old._id;" + "SELECT _DELETE_FILE(old._data);" + "END"); ~~~ 上面這條SQL語句是什么意思呢? - CREATE TRIGGER IF NOT EXITSimages_cleanup:如果沒有定義名為images_cleanup的觸發器,就創建一個名為images_cleanup的觸發器。 - DELETE ON images:設置該觸發器的觸發條件。顯然,當我們對images表執行delete操作時,該觸發器將被觸發。 BEGIN和END之間則定義了該觸發器要執行的動作。從前面的代碼可知,它將執行兩項操作: - 刪除thumbnails(縮略圖)表中對應的信息。為什么要刪除縮略圖呢?因為原圖的信息已經不存在了,留著它沒用。 - 執行_DELETE_FILE函數,其參數是old.data。從名字上來看,這個函數的功能應為刪除文件。為什么要刪除此文件?原因也很簡單,數據庫都沒有該項信息了,還留著圖片干什么!另外,如不刪除文件,下一次媒體掃描時就又會把它們找到。 * * * * * **提示**:_DELETE_FILE這個操作曾給筆者及同仁帶來極大困擾,因為最開始并不知道有這個觸發器。結果好不容易下載的測試文件全部被刪除了。另外,由于MediaProvider本身的設計缺陷,頻繁掛/卸載SD卡時也會錯誤刪除數據庫信息(這個缺陷只能盡量避免,無法徹底根除),結果實體文件也被刪除掉了。 * * * * * 有人可能會感到奇怪,這個_DELETE_FILE函數是誰設置的呢?答案就在前面提到的register_android_functions中。 (2) register_android_functions介紹 register_android_functions在dbopen中被調用,其代碼如下: **sqlite3_android.cpp::register_android_functions** ~~~ //dbopen調用它時,第二個參數設置為0 extern "C" intregister_android_functions(sqlite3 * handle, int utf16Storage) { int err; UErrorCode status = U_ZERO_ERROR; UCollator * collator = ucol_open(NULL, &status); ...... if(utf16Storage) { err =sqlite3_exec(handle, "PRAGMA encoding = 'UTF-16'", 0, 0, 0); ...... } else { //sqlite3_create_collation_xx定義一個用于排序的文本比較函數,讀者可自行閱讀 //SQLite官方文檔以獲得更詳細的說明 err = sqlite3_create_collation_v2(handle,"UNICODE", SQLITE_UTF8, collator, collate8, (void(*)(void*))localized_collator_dtor); } /* 調用sqlite3_create_function創建一個名為"PHONE_NUMBERS_EQUAL"的函數, 第三個參數2表示該函數有兩個參數,SQLITE_UTF8表示字符串編碼為UTF8, phone_numbers_equal為該函數對應的函數指針,也就是真正會執行的函數。注意 "PHONE_NUMBERS_EQUAL"是SQL語句中使用的函數名,phone_numbers_equal是Native 層對應的函數 */ err =sqlite3_create_function( handle, "PHONE_NUMBERS_EQUAL", 2, SQLITE_UTF8, NULL, phone_numbers_equal, NULL, NULL); ...... //注冊_DELETE_FILE對應的函數為delete_file err =sqlite3_create_function(handle, "_DELETE_FILE", 1, SQLITE_UTF8, NULL, delete_file, NULL, NULL); if (err!= SQLITE_OK) { return err; } #if ENABLE_ANDROID_LOG err =sqlite3_create_function(handle, "_LOG", 1, SQLITE_UTF8, NULL, android_log,NULL, NULL); ...... #endif ......//和PHONE相關的一些函數 returnSQLITE_OK; } ~~~ register_android_functions注冊了Android平臺上定制的一些函數。來看和_DELETE_FILE有關的delete_file函數,其代碼為: **Sqlite3_android.cpp::delete_file** ~~~ static void delete_file(sqlite3_context * context,int argc, sqlite3_value** argv) { if (argc!= 1) { sqlite3_result_int(context, 0); return; } //從argv中取出第一個參數,這個參數是觸發器調用_DELETE_FILE時傳遞的 charconst * path = (char const *)sqlite3_value_text(argv[0]); ...... /* Android4.0之后,系統支持多個存儲空間(很多平板都有一塊很大的內部存儲空間)。 為了保持兼容性,環境變量EXTERNAL_STORAGE還是指向sd卡的掛載目錄,而其他存儲設備的 掛載目錄由SECCONDARY_STORAGE表示,各個掛載目錄由冒號分隔。 下面這段代碼用于判斷_DELETE_FILE函數所傳遞的文件路徑是不是正確的 */ boolgood_path = false; charconst * external_storage = getenv("EXTERNAL_STORAGE"); if(external_storage && strncmp(external_storage, path,strlen(external_storage)) == 0) { good_path = true; } else { charconst * secondary_paths = getenv("SECONDARY_STORAGE"); while (secondary_paths && secondary_paths[0]) { const char* colon = strchr(secondary_paths, ':'); int length = (colon ? colon - secondary_paths : strlen(secondary_paths)); if (strncmp(secondary_paths, path, length) == 0) { good_path = true; } secondary_paths += length; while (*secondary_paths == ':')secondary_paths++; } } if(!good_path) { sqlite3_result_null(context); return; } //調用unlink刪除文件 int err= unlink(path); if (err!= -1) { sqlite3_result_int(context, 1);//設置返回值 } else { sqlite3_result_int(context, 0); } } ~~~ [^①]:其實這是一種廣義的設計模式,讀者可參考《Pattern-Oriented Software Architecture Volume 3: Patterns forResource Management》一書以加深理解。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看