Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 數據存儲筆記

Android 數據存儲筆記

編輯:關於Android編程

簡介:

Android 提供了多種數據存儲的方式,包括SharedPreference,內部/外部存儲,網絡存儲,數據庫存儲等。本文只記錄自己常用的幾種存儲方式:

SharedPreference 內部/外部存儲 數據庫(SQLite)

使用方法:

SharedPreference:

SharedPreference(以下簡稱sp):對象指向包含鍵值對的文件並提供讀寫這些文件的簡單方法,主要保存相對較小的鍵值集合。


注:SharedPreferences API 僅用於讀寫鍵值對


在使用sp文件進行文件的存取之前首先需要獲取sp的實例對象,在實際代碼中有以下兩種方式可以獲得:

getSharedPreferences() — 如果您需要按照您用第一個參數指定的名稱識別的多個共享首選項文件,請使用此方法。 您可以從您的應用中的任何 Context 調用此方法。

getPreferences() — 如果您只需使用Activity的一個共享首選項,請從 Activity 中使用此方法。 因為此方法會檢索屬於該Activity的默認共享首選項文件,您無需提供名稱。

例如:
Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
getString(R.string.preference_file_key), Context.MODE_PRIVATE);


注:如果你創建了帶 MODE_WORLD_READABLE 或 MODE_WORLD_WRITEABLE 的sp文件,那麼知道文件標識符的任何其他應用都可以訪問您的數據。


在獲得了sp對象之後,你就可以開始操作sp文件了,但是sp對象僅支持讀取數據,寫入數據需要通過Editor對象,因此要想寫入數據你需要通過sp對象調用 edit() 來創建一個 SharedPreferences.Editor,使用Editor傳遞你想要使用諸如 putInt() 和 putString() 方法寫入的鍵和值。然後調用 commit() 以保存更改。

例如:

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();

說完寫入數據,說一下如何從sp文件中讀取數據,要從sp文件中讀取數據,請調用諸如getInt() 和 getString() 等方法,為你想要的值提供鍵,並根據需要提供要在鍵不存在的情況下返回的默認值。

例如:

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
int defaultValue = getResources().getInteger(R.string.saved_high_score_default);
long highScore = sharedPref.getInt(getString(R.string.saved_high_score), defaultValue);

內/外部存儲:

所有 Android 設備都有兩個文件存儲區域:“內部”和“外部”存儲。這些名稱在 Android 早期產生,當時大多數設備都提供內置的非易失性內存(內部存儲),以及移動存儲介質,比如微型 SD 卡(外部存儲)。一些設備將永久性存儲空間劃分為“內部”和“外部”分區,即便沒有移動存儲介質,也始終有兩個存儲空間,並且無論外部存儲設備是否可移動,API 的行為均一致。

 


內部存儲

 

始終可用。
默認情況下只有您的應用可以訪問此處保存的文件。
當用戶卸載您的應用時,系統會從內部存儲中刪除您的應用的所有文件。
所以當你希望確保用戶或其他應用均無法訪問您的文件時,內部存儲是最佳選擇。

 


外部存儲

 

它並非始終可用,因為用戶可采用 USB 存儲的形式裝載外部存儲,並在某些情況下會從設備中將其刪除。
它是全局可讀的,因此此處保存的文件可能不受您控制地被讀取。
當用戶卸載您的應用時,只有在您通過 getExternalFilesDir() 將您的應用的文件保存在目錄中時,系統才會從此處刪除您的應用的文件。
所以對於無需訪問限制以及您希望與其他應用共享或允許用戶使用電腦訪問的文件,外部存儲是最佳位置。

將文件存儲到內部存儲:

在內部存儲保存文件時不需要任何權限,可以通過調用以下兩種方法之一獲取作為 File 的相應目錄:

getFilesDir()
返回表示您的應用的內部目錄的 File, 例如:/data/data/com.kn.codebase/files。 getCacheDir()
返回表示您的應用臨時緩存文件的內部目錄的 File,例如:/data/data/com.kn.codebase/cache 。 務必刪除所有不再需要的文件並對在指定時間您使用的內存量實現合理大小限制,比如,1MB。 如果在系統即將耗盡存儲,它會在不進行警告的情況下刪除您的緩存文件。

注: 您的應用的內部存儲設備目錄由您的應用在Android 文件系統特定位置中的軟件包名稱指定。在技術上,如果您將文件模式設置為可讀,另一個應用可以讀取您的內部文件。 但是,另一個應用也需要知道您的應用的軟件包名稱和文件名。 其他應用無法浏覽您的內部目錄並且沒有讀寫權限,除非您明確將文件設置為可讀或可寫。 只要您為內部存儲上的文件使用 MODE_PRIVATE, 其他應用便從不會訪問它們。


將文件存儲到外部存儲:

在外部存儲中存儲文件需要在清單文件中添加操作SD卡的權限

READ_EXTERNAL_STORAGE 讀取權限 WRITE_EXTERNAL_STORAGE 寫入權限,添加了寫入權限之後會默認存在讀取權限

由於外部存儲可能不可用,比如:當用戶已將存儲裝載到電腦或已移除提供外部存儲的 SD 卡時,因此,在訪問它之前,您應始終確認其容量。 您可以通過調用 getExternalStorageState() 查詢外部存儲狀態。 如果返回的狀態為 MEDIA_MOUNTED,那麼您可以對您的文件進行讀寫。盡管外部存儲可被用戶和其他應用進行修改,但您可在此處保存兩類文件:

公共文件
可供其他應用和用戶自由使用的文件。 當用戶卸載您的應用時,用戶應仍可以使用這些文件。例如,您的應用拍攝的照片或其他已下載的文件。
如果您要使用外部存儲上的公共文件,請使用 getExternalStoragePublicDirectory() 方法獲取表示外部存儲上相應目錄的 File (例如:/storage/sdcard0/Download)。該方法使用指定 您想要保存以便它們可以與其他公共文件在邏輯上組織在一起的文件類型的參數,比如 DIRECTORY_MUSIC 或 DIRECTORY_PICTURES。 例如:
public File getAlbumStorageDir(String albumName) {
// Get the directory for the user's public pictures directory.
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
} 私有文件
如果您要保存您的應用專用文件,您可以通過調用 getExternalFilesDir() 獲取相應的目錄並向其傳遞指示您想要的目錄類型的名稱(例如:/storage/sdcard0/Android/data/com.kn.codebase/cache)。 通過這種方法創建的各個目錄將添加至封裝您的應用的所有外部存儲文件的父目錄,當用戶卸載您的應用時,系統會刪除這些文件。
例如,您可以使用以下方法來創建個人相冊的目錄:
public File getAlbumStorageDir(Context context, String albumName) {
// Get the directory for the app's private pictures directory.
File file = new File(context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}

如果沒有適合您文件的預定義子目錄名稱,您可以改為調用 getExternalFilesDir() 並傳遞 null。這將返回外部存儲上您的應用的專用目錄的根目錄。
切記,getExternalFilesDir() 創建的目錄在用戶卸載應用時會被刪除。如果您希望保存的文件在用戶卸載您的應用後仍然可用(比如,您的應用是照相機並且用戶要保留照片)您應改用 getExternalStoragePublicDirectory()。
無論您對於共享的文件使用 getExternalStoragePublicDirectory() 還是對您的應用專用文件使用 getExternalFilesDir() ,您使用諸如 DIRECTORY_PICTURES 的 API 常數提供的目錄名稱非常重要。 這些目錄名稱可確保系統正確處理文件。 例如,保存在 DIRECTORY_RINGTONES 中的文件由系統介質掃描程序歸類為鈴聲,而不是音樂。

注:當用戶卸載您的應用時,Android 系統會刪除以下各項:

您保存在內部存儲中的所有文件 您使用 getExternalFilesDir() 保存在外部存儲中的所有文件。 但是,您應手動刪除使用 getCacheDir() 定期創建的所有緩存文件並且定期刪除不再需要的其他文件。

SQLite數據庫:

主要用來存儲重復或者結構化數據。Android為SQLite數據庫提供了全面的API支持,你創建的任何數據庫可以通過名稱來訪問應用中的任何類。
我們要使用SQLite數據庫存儲數據,必須先創建一個數據庫,然後才能對數據進行操作,下面逐個介紹創建數據庫,操作數據庫的方法。

創建數據庫:
官方建議創建數據庫的方法是首先創建一個SQLiteOpenHelper的子類並重寫其onCreate()方法,在onCreate()方法中執行sql語句。
例如:
public class DictionaryOpenHelper extends SQLiteOpenHelper {

    //數據庫版本號
    private static final int DATABASE_VERSION = 2;
    //數據庫名稱
    private static final String DATABASE_NAME="test";
    //數據庫表名
    private static final String DICTIONARY_TABLE_NAME = "dictionary";
    //創建數據庫的Sql語句
    private static final String DICTIONARY_TABLE_CREATE =
                "CREATE TABLE " + DICTIONARY_TABLE_NAME + " (" +
                KEY_WORD + " TEXT, " +
                KEY_DEFINITION + " TEXT);";

    DictionaryOpenHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //創建數據庫表
        db.execSQL(DICTIONARY_TABLE_CREATE);
    }

     @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //數據庫版本更新
    }
}

在創建好SQLite輔助類之後,我們就可以利用輔助類的實例來創建數據庫了,具體可以通過 getWritableDatabase() 和 getReadableDatabase()了兩種方式來獲取數據庫的實例,

helper=new DictionaryOpenHelper (context);
        SQLiteDatabase database = helper.getWritableDatabase();

獲取到SQLiteDatabase 的實例之後就可以操作數據了。在對數據庫進行增刪改查操作時我們既可以選擇調用db.execSQL(“SQL語句”)也可以通過API已封裝的方法進行操作。至於第一種本文不再介紹,主要簡單介紹一下調用API實現增刪改查

插入數據(增):數據庫的插入操作提供了3中方法,分別是:
database.insert(“表名”,null,values);
參數:1.表名 2.空值字段名稱(當values不為空時可置為null) 3.插入的鍵值對
database.insertOrThrow(“表名”,null,values);
該方法與第一種唯一的區別就是會直接把異常拋出。
database.insertWithOnConflict(“表名”,null,values,1);
該方法增加了處理插入沖突的算法,其實這三種方法最終都通過第三種方式實現,只是前兩種第四個參數給了默認值。
對於這四個參數,第一個和第三個都很好理解,第四個是沖突解決方案,顧名思義很容易理解,而第二個參數就有點疑惑了,這個參數到底有什麼作用呢?當values參數為空或者裡面沒有內容的時候,我們insert是會失敗的(底層數據庫不允許插入一個空行),為了防止這種情況,我們要在這裡指定一個列名,到時候如果發現將要插入的行為空行時,就會將你指定的這個列名的值設為null,然後再向數據庫中插入。通過查看源碼我們就明白了:
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();
        }
    }

上面的源碼中當我們的ContentValues類型的數據initialValues為null,或者size<=0時,就會再sql語句中添加nullColumnHack的設置。我們可以想象一下,如果我們不添加nullColumnHack的話,那麼我們的sql語句最終的結果將會類似insert into tableName()values();這顯然是不允許的。而如果我們添加上nullColumnHack呢,sql將會變成這樣,insert into tableName (nullColumnHack)values(null);這樣很顯然就是可以的。


注:上面提到了插入時的沖突解決方案,在API中提供了0~5這幾種:

CONFLICT_NONE = 0
Use the following when no conflict action is specified.
規定沒有沖突的動作時使用。 CONFLICT_ROLLBACK = 1
When a constraint violation occurs, an immediate ROLLBACK occurs, thus ending the current transaction, and the command aborts with a return code of SQLITE_CONSTRAINT. If no transaction is active (other than the implied transaction that is created on every command) then this algorithm works the same as ABORT.
當限制沖突發生時,立即發生回滾,從而結束當前的事務,並命令SQLITE_CONSTRAINT的返回代碼中止。如果沒有事務是有效的(除了創建在每個命令的必需事務),那麼這算法做法和ABORT一樣。 CONFLICT_ABORT = 2
When a constraint violation occurs,no ROLLBACK is executed so changes from prior commands within the same transaction are preserved. This is the default behavior.
當限制沖突發生時,不執行ROLLBACK所以在同一事務中從先前的命令更改將保留。這是默認的行為。 CONFLICT_FAIL = 3
When a constraint violation occurs, the command aborts with a return code SQLITE_CONSTRAINT. But any changes to the database that the command made prior to encountering the constraint violation are preserved and are not backed out.
當限制沖突發生時,命令返回代碼SQLITE_CONSTRAINT中止。但該命令之前遇到限制沖突得以保存任何更改數據庫不備份出來。 CONFLICT_IGNORE = 4
When a constraint violation occurs, the one row that contains the constraint violation is not inserted or changed. But the command continues executing normally. Other rows before and after the row that contained the constraint violation continue to be inserted or updated normally. No error is returned.
當限制沖突發生時,包含限制沖突的一行不會插入或更改。但命令繼續正常執行。前後包含約束違反的行後,其他行繼續正常插入或更新。不返回任何錯誤。 CONFLICT_REPLACE = 5
如果發生違反NOT NULL約束,NULL值被該列的默認值取代。如果列沒有默認值,那麼使用ABORT算法。如果則會發生CHECK約束違反IGNORE算法

刪除數據(刪):
database.delete(String table, String whereClause, String[] whereArgs);
參數:1.表名 2.刪除時的where語句,為null時刪除整行 。例如:“_id=?”3.刪除條件的數組,用來代替問號的。例如:new String[]{1,2}; 修改數據(改):修改數據時API中提供了兩種方法:
database.update(String table, ContentValues values, String whereClause, String[] whereArgs)
參數:1.表名 2.鍵值對 3.需要修改的where條件 4.修改條件的數組,用來代替前面的問號
database.updateWithOnConflict(String table, ContentValues values,
String whereClause, String[] whereArgs, int conflictAlgorithm)
該方法相比較於第一種方法多了解決沖突方案這個參數,這個參數的含義一插入數據的含義相同。第一種方法最終調用的是第二個方法,第四個參數默認為CONFLICT_NONE 查詢數據(查):查詢方法提供的比較多,此處只介紹其中1個
query(boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit, CancellationSignal cancellationSignal)
參數:
1 distinct:是否合並,是:每行均唯一;否:….
2 table:表名
3 columns:要查詢的列的數組
4 selection:查詢的where語句
5 selectionArgs:where語句的查詢條件,代替問號
6 groupBy:分組列
7 having:分組條件
8 orderBy:排序列
9 limit:分頁查詢限制
10 cancellationSignal:信號取消操作正在進行中,如果沒有則為null。如果操作被取消,那麼{@link OperationCanceledException}將執行查詢時拋出。(沒用過)
(CursorFactory cursorFactory,
boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit, CancellationSignal cancellationSignal)
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved