Saving Data筆記

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()關閉數據庫是最佳時機。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章