Android:資料存儲之SharedPreference & 檔案
存儲方式
Android主要的資料存儲方式包括下列5種, 下文将介紹前2種
1. SharePreference
2. 檔案
3. SQlite
4. ContentProvider
5. 網絡
儲存到Preference
當有一個相對較小的key-value集合需要儲存時,可以使用SharedPreferences APIs。
Note: SharedPreferences APIs 僅僅提供了讀寫key-value對的功能,請不要與Preference APIs相混淆。後者可以幫助我們建立一個設定使用者配置的頁面(盡管它實際上是使用SharedPreferences 來實作儲存使用者配置的)。
1. 建立preference檔案
一般有兩種方法:
context.getSharedPreferences(name, mode): context的方法, 需要指定 preference檔案的名字(preference檔案其實就是一個存放key-value的xml檔案). 同App的任何activity都可以通過 name來使用這個 preference檔案.
Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
getString(R.string.preference_file_key), Context.MODE_PRIVATE);
context.getPreferences(mode): context的方法, 無需指定preference檔案名, 系統會自動建立以activity命名的preference檔案. 隻能擷取供單個activity使用.
如果建立了一個MODE_WORLD_READABLE或者MODE_WORLD_WRITEABLE 模式的shared preference檔案,則其他任何app均可通過檔案名通路該檔案。
2.寫入檔案
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();
3.讀取檔案
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), default);
檔案
檔案主要分為兩部分: internal 和 external
- Internal storage:
1. 總是可用的
2. 這裡的檔案預設隻能被我們的app所通路。
3. 當使用者解除安裝app的時候,系統會把internal内該app相關的檔案都清除幹淨。
4. Internal是我們在想確定不被使用者與其他app所通路的最佳存儲區域。
- External storage:
1. 并不總是可用的,因為使用者有時會通過USB存儲模式挂載外部存儲器,當取下挂載的這部分後,就無法對其進行通路了。
2. 是大家都可以通路的,是以儲存在這裡的檔案可能被其他程式通路。
3. 當使用者解除安裝我們的app時,系統僅僅會删除external根目錄(getExternalFilesDir())下的相關檔案。
4. External是在不需要嚴格的通路權限并且希望這些檔案能夠被其他app所共享或者是允許使用者通過電腦通路時的最佳存儲區域。
Tip: 盡管app是預設被安裝到internal storage的,我們還是可以通過在程式的manifest檔案中聲明android:installLocation 屬性來指定程式安裝到external storage。當某個程式的安裝檔案很大且使用者的external storage空間大于internal storage時,使用者會傾向于将該程式安裝到external storage。更多安裝資訊見App Install Location。
聲名外部存儲讀寫權限
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
...
</manifest>
儲存檔案到Internal Storage
當儲存檔案到internal storage時,可以通過執行下面兩個方法之一來擷取合适的目錄作為 FILE 的對象:
- getFilesDir() : 傳回一個File,代表了我們app的internal目錄。
- getCacheDir() : 傳回一個File,代表了我們app的internal緩存目錄。請確定這個目錄下的檔案能夠在一旦不再需要的時候馬上被删除,并對其大小進行合理限制,例如1MB 。系統的内部存儲空間不夠時,會自行選擇删除緩存檔案。
可以使用File() 構造器在那些目錄下建立一個新的檔案,如下:
File file = new File(context.getFilesDir(), filename);
同樣,也可以執行openFileOutput() 擷取一個 FileOutputStream 用于寫檔案到internal目錄。如下:
String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(string.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
如果需要緩存一些檔案,可以使用createTempFile()。例如:下面的方法從URL中抽取了一個檔案名,然後再在程式的internal緩存目錄下建立了一個以這個檔案名命名的檔案。
public File getTempFile(Context context, String url) {
File file;
try {
String fileName = Uri.parse(url).getLastPathSegment();
file = File.createTempFile(fileName, null, context.getCacheDir());
catch (IOException e) {
// Error while creating file
}
return file;
}
儲存檔案到External Storage
因為external storage可能是不可用的,比如遇到SD卡被拔出等情況時。是以在通路之前應對其可用性進行檢查。我們可以通過執行 getExternalStorageState()來查詢external storage的狀态。若傳回狀态為MEDIA_MOUNTED, 則可以讀寫。示例如下:
/* 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;
}
盡管external storage對于使用者與其他app是可修改的,我們可能會儲存下面兩種類型的檔案。
- Public files :這些檔案對與使用者與其他app來說是public的,當使用者解除安裝我們的app時,這些檔案應該保留。例如,那些被我們的app拍攝的圖檔或者下載下傳的檔案。
- Private files: 這些檔案完全被我們的app所私有,它們應該在app被解除安裝時删除。盡管由于存儲在external storage,那些檔案從技術上而言可以被使用者與其他app所通路,但實際上那些檔案對于其他app沒有任何意義。是以,當使用者解除安裝我們的app時,系統會删除其下的private目錄。例如,那些被我們的app下載下傳的緩存檔案。
想要将檔案以public形式儲存在external storage中,請使用getExternalStoragePublicDirectory()方法來擷取一個 File 對象,該對象表示存儲在external storage的目錄。這個方法會需要帶有一個特定的參數來指定這些public的檔案類型,以便于與其他public檔案進行分類。參數類型包括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;
}
想要将檔案以private形式儲存在external storage中,可以通過執行getExternalFilesDir() 來擷取相應的目錄,并且傳遞一個訓示檔案類型的參數。每一個以這種方式建立的目錄都會被添加到external storage封裝我們app目錄下的參數檔案夾下(如下則是albumName)。這下面的檔案會在使用者解除安裝我們的app時被系統删除。如下示例:
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. 它會傳回app在external storage下的private的根目錄。
請記住,getExternalFilesDir() 方法會建立的目錄會在app被解除安裝時被系統删除。如果我們的檔案想在app被删除時仍然保留,請使用getExternalStoragePublicDirectory().
無論是使用 getExternalStoragePublicDirectory() 來存儲可以共享的檔案,還是使用 getExternalFilesDir() 來儲存那些對于我們的app來說是私有的檔案,有一點很重要,那就是要使用那些類似DIRECTORY_PICTURES 的API的常量。那些目錄類型參數可以確定那些檔案被系統正确的對待。例如,那些以DIRECTORY_RINGTONES 類型儲存的檔案就會被系統的media scanner認為是ringtone而不是音樂。
查詢剩餘空間
如果事先知道想要儲存的檔案大小,可以通過執行getFreeSpace() or getTotalSpace() 來判斷是否有足夠的空間來儲存檔案,進而避免發生IOException。那些方法提供了目前可用的空間還有存儲系統的總容量。
然而,系統并不能保證可以寫入通過getFreeSpace()查詢到的容量檔案, 如果查詢的剩餘容量比我們的檔案大小多幾MB,或者說檔案系統使用率還不足90%,這樣則可以繼續進行寫的操作,否則最好不要寫進去。
Note:并沒有強制要求在寫檔案之前去檢查剩餘容量。我們可以嘗試先做寫的動作,然後通過捕獲 IOException 。這種做法僅适合于事先并不知道想要寫的檔案的确切大小。例如,如果在把PNG圖檔轉換成JPEG之前,我們并不知道最終生成的圖檔大小是多少。
删除檔案
在不需要使用某些檔案的時候應删除它。删除檔案最直接的方法是直接執行檔案的delete()方法。
myFile.delete();
如果檔案是儲存在internal storage,我們可以通過Context來通路并通過執行deleteFile()進行删除
myContext.deleteFile(fileName);
Note: 當使用者解除安裝我們的app時,android系統會删除以下檔案:
所有儲存到internal storage的檔案。
所有使用getExternalFilesDir()方式儲存在external storage的檔案。
然而,通常來說,我們應該手動删除所有通過 getCacheDir() 方式建立的緩存檔案,以及那些不會再用到的檔案。