Android資料儲存相關
本文來自對Android Training的總結。
SharedPreferences物件
SharedPreferences通過鍵值集合的方式儲存資料,通常用來儲存應用程式的偏好設定(設定資訊)。SharedPreferences 物件指向包含鍵值對的檔案並提供讀寫這些檔案的簡單方法。每個 SharedPreferences 檔案由框架進行管理並且可以專用或共享。
注:SharedPreferences API 僅用於讀寫鍵值對,注意不能將其與 Preference API 混淆,後者用於應用設定構建使用者介面(儘管它們使用 SharedPreferences 作為其實現以儲存應用設定)。
讀取
getSharedPreferences()
: 如果有多個首選項檔案,第一個引數需傳入指定的檔名稱,第二個引數為檔案識別符號。Context.MODE_PRIVATE
、MODE_WORLD_READABLE
或MODE_WORLD_WRITEABLE
。getPreferences()
:如只需使用 Activity 的一個共享首選項,那用該方法。 因為此方法會檢索屬於該 Activity 的預設共享首選項檔案,無需提供名稱。- 呼叫Context物件的getSharedPreferences()方法獲得的SharedPreferences物件可以被同一應用程式下的其他元件共享.
呼叫Activity物件的getPreferences()方法獲得的SharedPreferences物件只能在該Activity中使用.
//首先獲取到SharedPreferences的例項
Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
getString(R.string.preference_file_key), Context.MODE_PRIVATE);
/*命名共享首選項檔案時,應使用對於應用而言唯一可識別的名稱,比如 "com.example.myapp.PREFERENCE_FILE_KEY"
如果只需 Activity 的一個共享首選項檔案,可以使用 getPreferences() 方法:SharedPreferences sharedPref =getActivity().getPreferences(Context.MODE_PRIVATE);*/
//讀取值
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);
寫入
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();
檔案儲存
所有 Android 裝置都有兩個檔案儲存區域:“內部”和“外部”儲存。這些名稱在 Android 早期產生,當時大多數裝置都提供內建的非易失性記憶體(內部儲存),以及移動儲存介質,比如微型 SD 卡(外部儲存)。一些裝置將永久性儲存空間劃分為“內部”和“外部”分割槽,即便沒有移動儲存介質,也始終有兩個儲存空間,並且無論外部儲存裝置是否可移動,API 的行為均一致。以下列表彙總了關於各個儲存空間的實際資訊。
內部儲存 | 外部儲存 |
---|---|
它始終可用。 | 它並非始終可用,因為使用者可採用 USB 儲存裝置的形式裝載外部儲存,並在某些情況下會從裝置中將其移除。 |
只有你的應用可以訪問此處儲存的檔案。 | 它是全域性可讀的,因此此處儲存的檔案可能不受控制地被讀取。 |
當用戶解除安裝應用時,系統會從內部儲存中移除應用的所有檔案。 | 當用戶解除安裝您的應用時,只有在您通過 getExternalFilesDir() 將你的應用的檔案儲存在目錄中時,系統才會從此處移除您的應用的檔案。 |
當你希望確保使用者或其他應用均無法訪問你的檔案時,內部儲存是最佳選擇。 | 對於無需訪問限制以及你希望與其他應用共享或允許使用者使用計算機訪問的檔案,外部儲存是最佳位置。 |
儘管應用預設安裝在內部儲存中,但你可在你的清單檔案中指定 android:installLocation
屬性,這樣你的應用便可安裝在在外部儲存中。當 APK
非常大且它們的外部儲存空間大於內部儲存時,使用者更青睞這個選擇。
關於許可權
要向外部儲存寫入資訊,必須在清單檔案中請求 WRITE_EXTERNAL_STORAGE 許可權。
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
...
</manifest>
注意:
- 目前,所有應用都可以讀取外部儲存,而無需特別的許可權。 但這在將來版本中會進行更改。如果應用需要讀取外部儲存(但不向其寫入資訊),那麼需要宣告 READ_EXTERNAL_STORAGE 許可權。要確保應用繼續正常工作,應在更改生效前宣告此許可權。
- 如果你的應用使用 WRITE_EXTERNAL_STORAGE 許可權,那麼它也隱含讀取外部儲存的許可權。
- 無需任何許可權,即可在內部儲存中儲存檔案。 你的應用始終具有在其內部儲存目錄中進行讀寫的許可權。
內部儲存
在內部儲存中儲存檔案時,您可以通過呼叫以下兩種方法之一獲取作為 File 的相應目錄:
1. getFilesDir()
返回表示您的應用的內部目錄的 File 。
2. getCacheDir()
返回表示您的應用臨時快取檔案的內部目錄的 File。 務必刪除所有不再需要的檔案並對在指定時間您使用的記憶體量實現合理大小限制,比如,1MB。 如果在系統即將耗盡儲存,它會在不進行警告的情況下刪除您的快取檔案。
然後通過呼叫標準的JavaIO的API進行操作。如:
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 提取檔名並在的應用的內部快取目錄中以該名稱建立檔案:
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;
}
注:應用的內部儲存目錄由應用在 Android 檔案系統特定位置中的軟體包名稱指定。從技術上講,如果您將檔案模
式設定為可讀,那麼,另一應用也可以讀取您的內部檔案。
但是,此應用也需要知道您的應用的軟體包名稱和檔名。
其他應用無法瀏覽您的內部目錄並且沒有讀寫許可權,除非您明確將檔案設定為可讀或可寫。
只要您為內部儲存上的檔案使用 MODE_PRIVATE,其他應用便從不會訪問它們。
外部儲存
由於外部儲存可能不可用—比如,當用戶已將儲存裝載到電腦或已移除提供外部儲存的 SD 卡時。因此,在訪問它之前,應該進行狀態判斷。 可以通過呼叫 getExternalStorageState()
查詢外部儲存狀態。 如果返回的狀態為 MEDIA_MOUNTED
,那麼您可以對您的檔案進行讀寫。 例如,以下方法對於確定儲存可用性非常有用:
//檢查外部儲存是否可讀寫
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
//檢查外部儲存是否可讀
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
儘管外部儲存可被使用者和其他應用進行修改,但在此處可儲存兩類檔案:
公共檔案:應供其他應用和使用者自由使用的檔案。 當用戶解除安裝您的應用時,使用者應仍可以使用這些檔案。
例如,您的應用拍攝的照片或其他已下載的檔案。如Music、Pictures等目錄(通常位於根目錄)。File Environment.getExternalStoragePublicDirectory (String type)
獲取外部儲存的共有檔案。得到一個頂級共享/外部儲存目錄將特定型別的檔案。這就是使用者通常會放置和管理自己的檔案,所以在這裡進行操作需要小心。- 與多個使用者裝置上(如被UserManager),每個使用者有自己的獨立的共享儲存。應用程式只能訪問正在執行的使用者共享儲存。
- type有:
DIRECTORY_MUSIC
,DIRECTORY_PICTURES
,DIRECTORY_MOVIES
,DIRECTORY_DOWNLOADS
,DIRECTORY_DCIM
等。
私有檔案:屬於您的應用且在使用者解除安裝您的應用時應予刪除的檔案。 儘管這些檔案在技術上可被使用者和其他應用訪問(因為它們儲存在外部儲存中), 但它們實際上不向您的應用之外的使用者提供任何輸出值。 當用戶解除安裝您的應用時,系統會刪除應用外部私有目錄中的所有檔案。
getExternalFilesDir(String type)
返回程式在外部儲存中的私有目錄。通常路徑為:SDCard/Android/data/packageName/files。在私有目錄中依然可以指定特定的型別目錄,type
值同上。getExternalCacheDir()
返回程式在外部儲存中的私有快取目錄。通常路徑為:SDCard/Android/data/packageName/cache。
關於各API獲取的路徑,除錯資訊如下(7.1版本):
查詢可用空間
long getFreeSpace ()
返回分割槽中未分配的位元組數(剩餘容量大小)。
long getTotalSpace ()
返回分割槽的總位元組數(容量大小)
通常在保持檔案之前,無需檢查可用空間量。 可以嘗試立刻寫入檔案,然後在 IOException 出現時將其捕獲。
如果您不知道所需的確切空間量,你可能需要這樣做。
刪除檔案
- 呼叫File的方法。
myFile.delete();
- 呼叫Context的方法。
myContext.deleteFile(fileName);
注:當用戶解除安裝應用時,Android 系統會刪除以下各項:
- 儲存在內部儲存中的所有檔案
- 外部儲存中的私有檔案。(使用 getExternalFilesDir() 儲存在外部儲存中的私有檔案)
而且,應該定期刪除使用 getCacheDir() 建立的所有快取檔案並且和不再需要的其他檔案。
使用SQLite資料庫
SQLite是Android系統內建的輕量級關係型資料庫,運算速度快、佔用記憶體小。SQLite支援標準的SQL語法,還遵循了資料庫的ACID(原子性Atomicity、一致性Consistency、隔離性Isolation、永續性Durability)事務。
資料檔案儲存在內建儲存中(data/data/<packagename>databases下),因此資料是安全的,因為在預設情況下,其他應用無法訪問此區域。
BaseColumns介面
這個介面提供了自動增長的ID
和COUNT
這兩個欄位,不是必須的操作。用於規範格式。
建立資料庫
為了更方便地建立、管理、升級資料庫,Android提供了一個幫助類SQLiteOpenHelper
。使用這個類可以很方便地建立和升級資料庫。
建立一個繼承SQLiteOpenHelper的類
public class FeedReaderDbHelper extends SQLiteOpenHelper { public static final int DATABASE_VERSION = 1; public static final String DATABASE_NAME = "FeedReader.db"; public FeedReaderDbHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } public void onCreate(SQLiteDatabase db) { db.execSQL(SQL_CREATE_ENTRIES); } public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // This database is only a cache for online data, so its upgrade policy is // to simply to discard the data and start over db.execSQL(SQL_DELETE_ENTRIES); onCreate(db); } public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { onUpgrade(db, oldVersion, newVersion); } }
建立資料庫
獲取Helper的例項,呼叫
getReadableDatabase()
或getWritableDatabase()
便可以建立或開啟一個數據庫。(資料庫已存在就開啟,否則建立)當資料庫不可寫入的時候(如磁碟空間已滿),
getWritableDatabase()
方法返回的物件將以只讀的方式開啟資料庫,而getReadableDatabase()
將會產生異常。FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext()); // 以可寫模式讀取資料庫 SQLiteDatabase db = mDbHelper.getWritableDatabase();
插入資料
// 建立一個ContentValue物件將插入的資料作為鍵值對存入,該物件有系列過載的put方法 ContentValues values = new ContentValues(); //第一個引數為key(欄位名),第二個為插入的值 values.put("name", "liu"); // 插入一條記錄,並返回該記錄id,第一個引數為表名,第二個為nullColumnHacki,第三個為ContentValues物件。 long newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);
查詢資料
使用 query() 方法進行查詢,以遊標的方式(Cursor)返回。
query()方法引數 對應SQL部分 描述 table from table_name 指定查詢的表名 columns select column1,column2 指定查詢的列名 selection where column=value 指定where的約束條件 selectionArgs - 為where中的佔位符提供具體的值 groupBy group by column 指定需要group by的列 having having column=value 對group by後的結果進一步約束 orderBy order by column1, column2 指定查詢結果的排序方式 query()方法:
Cursor query (boolean distinct,
String table,
String[] columns,
String selection,
String[] selectionArgs,
String groupBy,
String having,
String orderBy,
String limit)刪除資料
int delete (String table, String whereClause, String[] whereArgs)
- table:表名
- whereClause:條件,為null時將刪除所有記錄
- 為whereClause中的佔位符提供具體的值
例:
db.delete("book", "page > ?", new String[]{"500"})
刪除page欄位大於500的所有記錄。更新資料
int update (String table,
ContentValues values,
String whereClause,
String[] whereArgs)
此處不再累述。使用標準SQL語句操作資料庫
上面的方法都是通過使用Android提供的API對資料庫進行操作,我們也可以使用標準的SQL語句操作資料庫。
例:
//同樣值需要使用佔位符 db.execSQL("insert into Book(name, author, page, price) values(?, ?, ?, ?)", new String[]{"Thinking in Java", "Bruce", "500", "213"});
使用事務
事務的特性可以保證讓一系列操作要麼全部完成,要麼一個都不能完成。
同時,利用事務批量提交語句,可以提高效能和效率。例:
myDB.beginTransaction(); //需要批量執行的操作 for (int i = 0; i< 2000;i++){ myDB.execSQL("insert into meetings (id , mainid) values ( '1','1')"); } myDB.setTransactionSuccessful(); myDB.endTransaction();