Saving Data筆記
SharedPreferences APIs是用來讀/寫鍵值對的,Preference APIs是用來構建app設置頁面的UI(其使用SharedPreference來保存app設置)。
SharedPreferences
獲取SharedPreferences引用
- 通過Context的getSharedPreference()方法。
Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
getString(R.string.preference_file_key), Context.MODE_PRIVATE);
- 通過Activity的getPreferences()方法,會爲Activity創建專屬SharedPreferences。
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
- 通過PreferenceManager的getDefaultSharedPreferences()方法,會爲app創建專屬SharedPreferences。
注意:如果使用MODE_WORLD_READABLE或MODE_WORLD_WRITEABLE創建SharedPreferences文件,其他app知道文件名即可訪問數據。
向SharedPreferences寫入數據
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();
從SharedPreferences讀取數據
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);
Saving Files
內部存儲和外部存儲
所有Android設備都有兩個文件存儲:”內部”和”外部”存儲。有些設備能夠掛載sd卡作爲外部存儲;有些不能掛載sd卡的設備會把存儲分成內部和外部存儲,這種外部存儲是不能移除的。
有關手機存儲的解釋:
提示:儘管app默認安裝在內部存儲,可以在
<manifest>
標籤通過android:installLocation屬性選擇安裝位置。
獲取外部存儲的權限
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
注意:當前,獲取外部存儲的寫權限,同時也獲得了讀權限。這在之後的發佈版本中可能會修改。最好明確指定 READ_EXTERNAL_STORAGE權限。
在內部存儲上保存文件
獲取內部存儲目錄的兩種方法:
getFilesDir()
返回File代表app內部存儲的目錄。
getCacheDir()
返回File代表app內部存儲的臨時緩存目錄。最好給緩存文件設置個閾值,刪除不用的文件。系統在低存儲的情況下坑你會刪除緩存文件。
openFileOutput()
獲取內部存儲文件的輸出流,文件不存在會創建文件。
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()創建文件。
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;
}
注意:app在內部存儲的文件夾名稱是由包名決定的。如果沒有顯示的指定文件是可讀或可寫的,其他app不能訪問這些文件。
在外部存儲保存文件
因爲外部存儲可能不可用——比如SD卡被移除,掛載到PC,因此,在使用外部存儲前先調用getExternalStorageState()檢查外部存儲的狀態。
/* 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;
}
外部存儲分爲兩種類型:
Public files
所有用戶和app都可以使用。通常用來保存照片和下載文件。
Private files
屬於app的文件,卸載app文件會被刪除。其他用戶或app知道包名的情況下也可使用文件。
不管使用getExternalStoragePublicDirectory()還是getExternalFilesDir(),儘量使用API提供的目錄常量,比如DIRECTORY_PICTURE。這些目錄名能保證系統正確處理文件。比如,存儲在DIRECTORY_RINGTONES目錄會被系統媒體管理認爲是鈴聲而不是音樂。
查詢剩餘空間
如果在存儲數據前知道數據大小,可以調用getFreeSpace()或getTotalSpace查看空間大小。
系統並不保證可以寫入和getFreeSpace()返回值一樣的數據量。
注意:在存儲數據前不需要檢查可用空間,可以捕獲IOException來做存儲數據失敗的操作。
刪除文件
獲取文件引用,調用delete()方法。
myFile.delete();
如果文件存在內部存儲,可以調用Context的deleteFile()方法。
myContext.deleteFile(fileName);
注意:當卸載app,android系統會刪除的文件
- 所有內部存儲的文件。
- 所有用getExternalFilesDir()存在外部存儲文件。
在數據庫保存數據
定義Scheme和Contract
創建定義URIs、tables和columns常量的contract類,可以方便對這些常量進行統一管理。
通過實現BaseColumns接口,內部類可以繼承主見字段_ID,在使用cursor adaptors很有用。
定義table和colum如下:
public final class FeedReaderContract {
// To prevent someone from accidentally instantiating the contract class,
// make the constructor private.
private FeedReaderContract() {}
/* Inner class that defines the table contents */
public static class FeedEntry implements BaseColumns {
public static final String TABLE_NAME = "entry";
public static final String COLUMN_NAME_TITLE = "title";
public static final String COLUMN_NAME_SUBTITLE = "subtitle";
}
}
使用SQL Helper創建數據庫
創建和刪除table的SQL:
private static final String SQL_CREATE_ENTRIES =
"CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
FeedEntry._ID + " INTEGER PRIMARY KEY," +
FeedEntry.COLUMN_NAME_TITLE + " TEXT," +
FeedEntry.COLUMN_NAME_SUBTITLE + " TEXT)";
private static final String SQL_DELETE_ENTRIES =
"DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;
數據庫存在內部存儲,默認情況下其他應用不能使用。
注意:getWritableDatabase()和getReadableDatabase()可能是耗時操作,最好不要在主線程調用。
繼承SQLiteOpenHelper:
public class FeedReaderDbHelper extends SQLiteOpenHelper {
// If you change the database schema, you must increment the database version.
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);
}
}
實例化SQLiteOpenHelper子類:
FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());
向數據庫插入數據
插入一條記錄:
// Gets the data repository in write mode
SQLiteDatabase db = mDbHelper.getWritableDatabase();
// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle);
// Insert the new row, returning the primary key value of the new row
long newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);
insert()的第二個參數表示,當ContentValues爲空(沒有放入values),參數爲空,不會向表插入記錄;否則,插入一條記錄參數指定的列爲null。
從數據庫讀取數據
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
FeedEntry._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_SUBTITLE
};
// Filter results WHERE "title" = 'My Title'
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };
// How you want the results sorted in the resulting Cursor
String sortOrder =
FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";
Cursor cursor = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The columns to return
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);
因爲Cursor的初始位置爲-1,所以在讀取數據之前,要先調用move方法。用cursor讀取數據:
List itemIds = new ArrayList<>();
while(cursor.moveToNext()) {
long itemId = cursor.getLong(
cursor.getColumnIndexOrThrow(FeedEntry._ID));
itemIds.add(itemId);
}
cursor.close();
從數據庫刪除記錄
selection格式分成selection語句和selection參數兩部分,這樣可以防止SQL注入。
// Define 'where' part of query.
String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?";
// Specify arguments in placeholder order.
String[] selectionArgs = { "MyTitle" };
// Issue SQL statement.
db.delete(FeedEntry.TABLE_NAME, selection, selectionArgs);
更新數據庫
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// New value for one column
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
// Which row to update, based on the title
String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?";
String[] selectionArgs = { "MyTitle" };
int count = db.update(
FeedReaderDbHelper.FeedEntry.TABLE_NAME,
values,
selection,
selectionArgs);
保持數據庫連接
由於getWritableDatabase()和getReadableDatabase()在database關閉時調用開銷很高,所以儘可能保持數據庫連接 狀態。在Activity的onDestroy()關閉數據庫是最佳時機。