Android 數據存儲 I-存儲選項

概述:

Android爲我們提供了多種選擇來保存持久化應用數據.選擇哪種方案取決於我們的需求, 比如數據是否應該對自己的應用是私有的, 對其他應用(和用戶)是否可訪問; 還有需要多大空間來保存數據. 我們的數據存儲選項如下:

Shard Preferences: 用鍵值對模式保存私有原始數據. (卸載刪除)

Internal Storage: 在設備的內部存儲中保存私有數據. (卸載刪除)

External Storage: 在設備的外部共享存儲中保存公共數據. (公共/私有/緩存(私有的卸載刪除))

SQLite Databases: 在私有數據庫中保存結構化數據.

Network Connection: 在自己的網絡服務器上保存數據.

Android爲我們提供了一種可以暴露私有數據給其它APP的方法, 就是contentprovider. content provider是一種可選的組件, 讓我們的APP可以爲其它APP提供讀寫的接口,

使用SharedPreferences:

SharedPreferences類提供了一個通用的框架讓我們可以以鍵值對的模式保存和讀取原始類型的持久化數據. 我們可以使用SharedPreferences來保存任何原始數據: Boolean, float, int, long和string. 這些數據將會持久化並貫穿用戶會話(甚至我們的APP被殺死了).

注意, shared preferences並不是嚴格的用來保存用戶偏好的, 比如用戶選擇了什麼鈴聲. 如果打算保存用戶偏好, 那麼應該考慮使用PreferenceActivity, 它提供了一個Activity框架給我們來創建用戶偏好, 並自動持久化(使用shared preferences).

想要獲取一個SharedPreferences對象, 使用這兩種方法:

l  getSharedPreferences(): 如果我們需要多個preferences文件, 並用名字區別它們的話, 那麼就使用這個方法. 我們可以使用第一個參數指定名字.

l  getPreferences(): 如果只需要一個preferences文件, 就用這個方法. 因爲這是唯一的一個preferences文件, 所以不需要指定名字.

想要寫入值的話:

1.      調用edit()方法來獲取一個SharedPreferences.Editor.

2.      使用putBoolean和putString()這樣的方法來添加值.

3.      用commit()方法提交新的值.

要讀取值的話, 則需要使用SharedPreferences方法比如getBoolean()和getString().

下面是一個栗子, 它是用來保存計算器的無聲按鍵模式的:

publicclass Calc extends Activity {
    public staticfinal String PREFS_NAME= "MyPrefsFile";

    @Override
    protected void onCreate(Bundle state){
       super.onCreate(state);
       . . .

       // Restore preferences
       SharedPreferences settings = getSharedPreferences(PREFS_NAME,0);
       boolean silent = settings.getBoolean("silentMode",false);
       setSilent(silent);
    }

    @Override
    protected void onStop(){
       super.onStop();

      // We need an Editor object to make preference changes.
      // All objects are from android.context.Context
      SharedPreferences settings = getSharedPreferences(PREFS_NAME,0);
      SharedPreferences.Editor editor= settings.edit();
      editor.putBoolean("silentMode", mSilentMode);

      // Commit the edits!
      editor.commit();
    }
}

使用內部存儲:

我們可以直接將文件保存在設備的內部存儲器中. 默認情況下, 文件在內部存儲器中是私有的, 其它的APP無法訪問這些文件(用戶也不行). 當用戶卸載APP的時候, 這些文件將會被移除. 要創建和寫入一個私有文件到內部存儲器:

1.      調用openFileOutput(), 並傳入文件名和打開模式. 這將會返回一個FileOutputStream.

2.      用write()方法寫入.

3.      寫完之後用close()方法關閉. 慄如:

String FILENAME = "hello_file";
String string = "hello world!";

FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
fos.write(string.getBytes());
fos.close();

MODE_PROVATE將會創建一個文件(或者用同樣的名字覆蓋一個文件)並使它對APP是私有的. 其它可用的模式還有MODE_APPEND, MODE_WORLD_READABLE和MODE_WRITEABLE.

想要從內部存儲讀取一個文件的話:

1.      調用openFileInput()並傳入文件名. 它會返回一個FileInputStream.

2.      使用read()方法讀取bytes.

3.      寫完之後用close()方法關閉它.

提醒: 如果我們想要在編譯的時候保存一個靜態文件, 那麼請保存該文件在res/raw/目錄下. 我們可以用openRawResource()方法打開它, 傳入R.raw.<filename>資源ID. 該方法會返回一個InputStream讓我們可以讀取文件(但是我們不能寫入到一個原始文件中).

保存緩存文件:

如果我們希望緩存一些數據, 而不是持久化保存, 我們應該使用getCacheDir()方法來打開一個File, 它表示一個我們的APP應該用來保存臨時緩存文件的內部目錄. 當設備處於內部存儲不足的情況, Android可能會刪除這些緩存文件來恢復空間. 但是我們不應該一來系統來清理這些文件. 而是自己維護這些緩存文件, 保持在一個合理的大小, 比如1M. 當用戶卸載APP的時候, 這些文件會被刪除.

其它有用的方法:

getFilesDir(): 取得保存內部文件的目錄的絕對路徑.

getDir(): 在內部存儲空間中創建(或者打開一個已經存在的)目錄.

deleteFile(): 在內部存儲中刪除一個文件.

fileList(): 返回APP保存的當前文件列表.

使用外部存儲:

每個兼容Android的設備都支持一個共享的”外部存儲”讓我們可以存儲文件. 它可以是一個可移除的存儲媒介(比如SD卡)或者一個內部的存儲(不可移除). 保存在外部存儲中的文件是全局可讀的, 並可以被用戶修改. 比如他們連接一臺電腦時.

注意, 外部存儲可能會在用戶在電腦上綁定外部存儲或者移除了媒介後變成不可讀的, 並且在外部存儲的文件並沒有強制加密措施. 所有的APP都可以讀寫和移除這些文件.

獲取外部存儲的訪問:

爲了在外部存儲區讀寫文件, 我們的APP必須取得READ_EXTERNAL_STORAGE或者WRITE_EXTERNAL_STORAGE系統權限, 栗子:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>

如果我們既需要讀取有需要寫文件, 那麼只需要WRITE_EXTERNAL_STORAGE權限就可以了, 因爲它隱含了讀訪問權限. 注意, 從Android 4.4開始, 如果只讀寫APP的私有文件, 那麼就不用再需要這些權限了.

檢查媒介可用性:

在對外部存儲做任何操作之前, 我們應該總是調用getExternalStorageState()來檢查媒介是否可用. 它可能被綁定在電腦上或者不見了,只讀或者其它什麼狀態. 栗子:

/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return true;
    }
    return false;
}

/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
    String state = Environment.getExternalStorageState();
    if (Environment.MEDIA_MOUNTED.equals(state) ||
        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return true;
    }
    return false;
}

getExternalStorageState()方法會返回其它我們可能有興趣的狀態, 比如是媒介否在共享狀態(跟電腦連接ing), 是否完全不見了, 已經被暴力移除了等. 我們可以使用這些來提醒用戶更多的信息.

可以共享給其它APP的文件:

通常, 公共的文件應該保存在設備的公共目錄下, 這樣其他的app就可以訪問它們, 用戶也可以簡單的從設備copy它們. 這種情況下我們應該選擇公共目錄比如Music/, Pictures/, 和Ringtones/. 想要獲取一個代表合適公共目錄的File, 應該調用getExternalStoragePublicDirectory(), 並傳入一個想要的目錄類型, 比如DIRECTORY_MUSIC,DIRECTORY_PICTURES, DIRECTORY_RINGTONES或者別的什麼. 通過保存這些文件到相應的目錄, 系統的媒體掃描器就可以合適的將我們的文件分類了. (比如鈴聲會出現在系統設置裏, 而不像音樂類).

比如, 這是一個方法, 它可以創建一個目錄來保存新的照片專輯:

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

我們還可以對媒體掃描器隱藏我們的文件. 只需要在文件夾中包含一個.nomedia文件即可(請留意文件名中的點), 這會阻止文件掃描器讀取我們的媒體文件和通過MediaStore content provider將它們提供給其它的APP. 如果想要完全的私有該文件,那麼:

保存文件爲APP私有:

如果我們創建的文件不打算讓其它的APP使用(比如只給自己APP用的圖片和聲音資源), 我們應該使用一個外部存儲的私人的存儲目錄, 可以調用getExternalFilesDir()方法. 該方法需要一個類型參數來指定子目錄的類型(比如DIRECTORY_MOVIES). 如果我們不需要指定的媒體目錄, 那麼傳入null來獲取私人目錄的根目錄.

從Android4.4開始, 讀寫私人目錄中的文件不需要READ_EXTERNAL_STORAGE或者WRITE_EXTERNAL_STORAGE權限. 所以我們聲明的權限只有在低版本中才有必要, 可以通過maxSdkVersion來實現, 栗子:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                     android:maxSdkVersion="18" />
    ...
</manifest>

注意: 當用戶卸載我們的APP的時候, 這個目錄和其中所有的內容將會被刪除. 同樣, 系統媒體掃描器不會讀取這些目錄的文件, 所以它們從MediaStore content provider是不可訪問的. 因此我們不應該使用這些目錄來存放最終屬於用戶的媒體, 比如相冊或者用戶從APP中購買的音樂等– 這些文件應該保存在公共目錄中.

有時, 一個設備分配其內部存儲的一部分用來作爲外部存儲, 也可能提供一個SD卡插槽. 當這樣的一個設備運行在Android 4.3及更低版本中, getExternalFilesDir()方法將只提供對內部存儲部分的訪問, 而APP不能讀寫SD卡的部分. 從Android 4.4開始, 我們可以通過getExternalFilesDirs()方法訪問這兩個區域, 它會返回一個包含各部分路徑的文件列表. 列表的第一個部分是基本的外部存儲, 除非它滿了或者不可用, 否則我們應該使用該路徑. 如果我們希望在Android 4.3及更低版本中訪問這兩種可用路徑, 則需要使用support library的靜態方法ContextComopat.getExternalFilesDirs(). 該方法也會返回一個File的列表, 但是在Android4.3及更低版本中總是隻包含一個條目.

注意: 雖然getExternalFilesDir()和getExternalFilesDirs()方法提供的目錄不能用MediaStore content provider直接訪問, 其它擁有READ_EXTERNAL_STORAGE權限的APP可以訪問所有的外部存儲的文件. 如果我們需要完全限制對文件的訪問, 那麼應該使用內部存儲.

保存緩存文件:

想要打開一個存放在外部存儲目錄的緩存文件, 調用getExternalCacheDir(). 如果用戶卸載了APP, 這些文件將會被自動刪除. 跟ContextCompat.getExternalFilesDirs()類似, 我們我們還可以在備用外部存儲通過ContextCompat.getExternalCacheDirs()方法訪問一個緩存目錄.

提醒: 想要保留文件空間並維護自己的APP的性能, 細心的管理緩存文件是很重要的, 當不需要的時候應該將它們刪除.

使用數據庫:

Android提供了對SQLite數據庫的完整支持. 我們創建的任何數據庫都可以通過名稱被我們APP下的任何類訪問, 但是不能被APP以外訪問. 推薦的創建SQLite數據庫的方法是創建一個SQLiteOpenHelper的子類, 並重寫onCreate()方法, 在這裏我們可以執行SQLite命令來創建數據庫表哥, 栗子:

publicclass DictionaryOpenHelperextends SQLiteOpenHelper{

    private staticfinal int DATABASE_VERSION= 2;
    private staticfinal String DICTIONARY_TABLE_NAME= "dictionary";
    private staticfinal 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);
    }
}

然後可以通過構造方法獲取一個SQLiteOpenHelper的實例. 想要從數據庫寫數據和讀數據數據, 分別調用getWritableDatabase()和getReadableDatabase()方法. 這倆方法都會返回一個SQLiteDatabase對象, 它表示一個數據庫並提供SQLite操作方法. 我們可以使用SQLiteDatabase query()方法執行SQLite查詢, 它接受各種查詢參數, 比如要查詢的表, selection, 列, 分組和別的. 對於更加複雜的查詢, 比如那些請求列別名, 我們應該使用SQLiteQueryBuilder, 它提供了一些方便的方法來構建查詢.

每個SQLite查詢將會返回一個Cursor, 它指向所有查詢得到的行. Cursor可以用來瀏覽數據庫查詢得到的結果.

Android沒有引入任何超出標準SQLite概念的限制. 官方建議使用一個自動遞增的關鍵值來作爲ID, 這樣可以快速索引某個記錄. 這對於私有數據並非必須的, 但是如果實現了一個content provider, 則必須包含一個唯一的ID, 並使用BaseColumns._ID常量.

數據庫調試:

Android SDK包含了一個sqlite3數據庫工具, 這讓我們可以瀏覽表內容, 運行SQL命令和執行其他有用的功能.詳情可以查看這裏:Examiningsqlite3 databases from a remote shell.

使用網絡連接:

我們可以使用網絡(如果可用的話)在自己的服務器上存取數據. 想要執行這些操作, 這兩個類將會有所幫助:java.net.*,android.net.*.

 

總結:

我們可以使用這些方法來保存自己的數據, 應該根據自己的需求來選擇使用哪種:

Shard Preferences: 用鍵值對模式保存私有原始數據. (卸載刪除); 通常我們用它來保存一些簡單的原始數據, 但是不用來保存設置項(設置項通常用封裝好的PreferenceActivity來實現), 比如當前登陸用戶的私有信息等.

Internal Storage: 在設備的內部存儲中保存私有數據. (卸載刪除); 用來保存文件, 通常是一些比較重要的APP私有信息, 可以保存緩存信息, 但是通常情況下內部存儲的空間比外部的小, 所以在保存一些比較大的文件類型的時候(比如視頻緩存)應該考慮保存在外部存儲中.

External Storage: 在設備的外部共享存儲中保存公共數據. (公共/私有/緩存(私有的卸載刪除)), 可以共有可以私有, 而且是全局唯一的共有文件保存位置. 所以公共文件一定要保存在這裏.

SQLite Databases: 在私有數據庫中保存結構化數據. 這個比較牛了, 用途廣泛特別是在保存聊天記錄這種量大又要求按順序存取的時候, 數據庫最合適不過了.

Network Connection: 在自己的網絡服務器上保存數據. 幾乎所有的網絡相關的APP都會用到.

 

參考: https://developer.android.com/guide/topics/data/data-storage.html

 

發佈了81 篇原創文章 · 獲贊 4 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章