Android學習 開發文檔(Training)05 saving data

Saving Data

  • Saving key-value pairs of simple data types in a shared preferences
    file
  • Saving arbitrary files in Android’s file system
  • Using databases managed by SQLite

1. saving key-value sets

如果你需要存儲一小部分的Key-value的數據,你可以使用SharedPreferenceAPI

1.1 get a handle to a sharedpreference

你可以通過調用兩個方法中任何一個來創建一個新的sharedpreference文件或者訪問一個已經存在的。

  • getSharedPreferences() — Use this if you need multiple shared preference files identified by name, which you specify with the first parameter. You can call this from any Context in your app.
  • getPreferences() — Use this from an Activity if you need to use only one shared preference file for the activity. Because this retrieves a default shared preference file that belongs to the activity, you don’t need to supply a name.

這裏是通過第一種方法,name是在string文件中定義的內容,並且通過私有模式打開,這樣這個文件就只能在這個app中使用

Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
        getString(R.string.preference_file_key), Context.MODE_PRIVATE);

When naming your shared preference files, you should use a name that’s uniquely identifiable to your app, such as “com.example.myapp.PREFERENCE_FILE_KEY”

如果只需要一個文件的話就可以使用第二種方法

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);

1.2 write to shared preferences

爲了寫入數據,通過調用edit()方法創建一個 SharedPreferences.Editor。 Pass the keys and values you want to write with methods such as putInt() and putString(). Then call commit() to save the changes. For example:

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();

1.3 Read from Shared Preferences

call methods such as getInt() and getString(), 提供一個key值,並且可以選擇性的提供一個默認的值,防止key並不存在的情況

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

2. saving files

2.1 choose internal or external storage

所有的android設備有兩個存儲區域,內部和外部存儲。
這裏寫圖片描述

結合自己使用手機的情況來總結一下,因爲自己在使用手機的時候也發現,有些app的視頻內容已經下下來了,但是在文件管理器裏面就是找不到。這裏應該是存儲到了它內部的存儲種。而且對於每一個app都有它自己對應的data文件夾且沒有root的手機是看不到的。實際上對於內部存儲和外部存儲來說,他們都是存儲在機身64g的內存中。只不過看不見的就叫內部存儲,看得見的就叫外部存儲。

2.2 obtain permissions for external storage

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

目前,所有的app都能夠在沒有特定的許可下讀取外部的存儲,但是在未來的發佈版本可能改變。所以儘量進行權限說明

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

如果你的程序申請了write權限,那麼就會自動有讀取權限。

2.3 save a file on internal storage

getFilesDir()

Returns a File representing an internal directory for your app.

getCacheDir()

Returns a File representing an internal directory for your app’s temporary cache files. Be sure to delete each file once it is no longer needed and implement a reasonable size limit for the amount of memory you use at any given time, such as 1MB. If the system begins running low on storage, it may delete your cache files without warning.

爲了在這些目錄下創建一個新的文件,你可以使用File()的構造方法,將上面得到的目錄放進去。

File file = new File(context.getFilesDir(), filename);

你也可以調用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()。下面的例子提取了文件的名字,並且在app的隱藏目錄中新建了一個名稱一致的文件。

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

2.4 save file on external storage

爲了確保外部內存的可用性。需要調用getExternalStorageState()如果結果和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;
}

我們需要在外部存儲中存儲兩種類型的文件

  • Public files

    Files that should be freely available to other apps and to the user. When the user uninstalls your app, these files should remain available to the user.
    For example, photos captured by your app or other downloaded files.

  • Private files

    Files that rightfully belong to your app and should be deleted when the user uninstalls your app. Although these files are technically accessible by the user and other apps because they are on the external storage, they are files that realistically don’t provide value to the user outside your app. When the user uninstalls your app, the system deletes all files in your app’s external private directory.
    For example, additional resources downloaded by your app or temporary media files.

如果想要將公共文件保存在外部的存儲中,我們需要使用getExternalStoragePublicDirectory()得到一個File代表了外部存儲的合適目錄。 The method takes an argument specifying the type of file you want to save so that they can be logically organized with other public files, such as DIRECTORY_MUSIC or DIRECTORY_PICTURES. For example:

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

如果你想存儲只對app私有的文件,可以調用 getExternalFilesDir() 來得到合適的目錄,然後把目錄的名字傳遞進去。Each directory created this way is added to a parent directory that encapsulates all your app’s external storage files, which the system deletes when the user uninstalls your app.每個這樣創建的目錄都會被添加到打包所有app外部存儲文件的父目錄,並且會和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私有目錄的根目錄。

無論是哪種方法,使用API提供的目錄常量名例如DIRECTORY_PICTURES是很重要的。這些目錄名確保系統能夠合理的處理這些文件。For instance, files saved in DIRECTORY_RINGTONES are categorized by the system media scanner as ringtones instead of music.

2.5 query free space

如果你通過調用 getFreeSpace() or getTotalSpace()提前知道你存儲數據的大小,你就可以知道是否有足夠的內存而不會出現IOException。這兩個方法分別得到當前可用的和總共的存儲空間大小。
然而系統不能保證 你就能寫入getFreeSpace() 這麼多的數據。如果你剩餘的存儲空間比你的數據多一些mb,或者系統剩餘量比90%多,你纔可以寫入。
在一些情況你不知道數據大小的時候,你可以直接進行寫入,然後捕獲是否出現IOException

2.6 delete a file

myFile.delete();

如果這個文件存儲在內部存儲中,你可以通過context來定位i和刪除。

myContext.deleteFile(fileName);

Note: When the user uninstalls your app, the Android system deletes the following:

All files you saved on internal storage
All files you saved on external storage using getExternalFilesDir().

However, you should manually delete all cached files created with getCacheDir() on a regular basis and also regularly delete other files you no longer need.

3. saving data in SQL database

數據庫可以將一些重複的或者結構化的數據進行存儲,比如聊天記錄信息。
The APIs you’ll need to use a database on Android are available in the android.database.sqlite package.

3.1 Define a schema and contract

SQL數據庫的一個主要原則就是schema,這是一個正式的數據庫構建聲明。schema反應在你創建數據庫的SQL語句中。你會發現創建一個名叫contract的夥伴類是很有幫助的。伴侶類用一種系統的,自文檔的方式定義了schena的佈局。

一個contract類是一個常量的容器,定義了URIs,tables和columns,contract類允許您在同一個包中的所有其他類中使用相同的常量。
This lets you change a column name in one place and have it propagate throughout your code.

組織合同類的一個好方法就是將數據庫的全局定義放在類的根level上,然後對每一行創建內部類。

For example, this snippet defines the table name and column names for a single table:

public final class FeedReaderContract {
    // To prevent someone from accidentally instantiating the contract class,
    // give it an empty constructor.
    public FeedReaderContract() {}

    /* Inner class that defines the table contents */
    public static abstract class FeedEntry implements BaseColumns {
        public static final String TABLE_NAME = "entry";
        public static final String COLUMN_NAME_ENTRY_ID = "entryid";
        public static final String COLUMN_NAME_TITLE = "title";
        public static final String COLUMN_NAME_SUBTITLE = "subtitle";
        ...
    }
}

3.2 create a database using a SQL helper

一旦定義了database的外觀,你應該implement方法來創建和維護數據庫。

private static final String TEXT_TYPE = " TEXT";
private static final String COMMA_SEP = ",";
private static final String SQL_CREATE_ENTRIES =
    "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
    FeedEntry._ID + " INTEGER PRIMARY KEY," +
    FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
    FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
    ... // Any other options for the CREATE command 
    " )";

private static final String SQL_DELETE_ENTRIES =
    "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;

就像你存儲在設備內部存儲的文件一樣,android存儲你的數據庫在和app聯繫的私有盤空間。

有用的APIs是SQLIteOpenHelper類,當你使用這個類來獲得數據庫引用的時候,系統只有在需要的時候和非app啓動的時候,纔會執行創建或者更新數據庫的這個長時間操作。你所需要的只是調用getWritableDatabase()或者getReadableDatabase()

爲了使用SQLiteHelper,創建一個子類重寫oncreate(), onupdate(),和onopen()回調方法。

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

爲了訪問數據庫,實例化SQLiteHelper子類

FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());

3.3 put information into a database

通過將ContentValues對象傳遞給insert()函數來插入數據。

// 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_ENTRY_ID, id);
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_CONTENT, content);

// Insert the new row, returning the primary key value of the new row
long newRowId;
newRowId = db.insert(
         FeedEntry.TABLE_NAME,
         FeedEntry.COLUMN_NAME_NULLABLE,
         values);

第一個參數是table名稱, The second argument provides the name of a column in which the framework can insert NULL in the event that the ContentValues is empty (if you instead set this to “null”, then the framework will not insert a row when there are no values).。第二個參數提供了你可以插入null數據的列名,如果value是零的話。如果第二個參數設置爲null,如果value爲null,那麼不會插入row。

3.4 read information from a database

通過query()方法來讀取數據庫的內容,傳入你的選擇標準和需要的列。這個方法結合了insert()update()的元素,但是列的list定義了要獲取的數據,而不是要插入的數據。query的結果返回一個Cursor對象。

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_UPDATED,
    ...
    };

// How you want the results sorted in the resulting Cursor
String sortOrder =
    FeedEntry.COLUMN_NAME_UPDATED + " DESC";

Cursor c = 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中看一行,你必須使用cusor的move方法。首先將cursor.moveToFirst()。對於每行,你可以通過調用Cursor的get方法讀取一列的值,such as getString() or getLong()對於每個get方法,都必須要得到position的index值。可以通過 getColumnIndex() or getColumnIndexOrThrow()來獲得

cursor.moveToFirst();
long itemId = cursor.getLong(
    cursor.getColumnIndexOrThrow(FeedEntry._ID)
)

3.5 delete information from a database

To delete rows from a table, you need to provide selection criteria that identify the rows. The database API provides a mechanism for creating selection criteria that protects against SQL injection. The mechanism divides the selection specification into a selection clause and selection arguments. The clause defines the columns to look at, and also allows you to combine column tests. The arguments are values to test against that are bound into the clause. Because the result isn’t handled the same as a regular SQL statement, it is immune to SQL injection.

要從表中刪除行,需要提供識別行的選擇條件。數據庫API提供了一種創建選擇標準的機制,以防止SQL注入。該機制將選擇規範分爲選擇子句和選擇參數。子句定義要查看的列,還允許您組合列測試。參數是要測試的值,這些值綁定到子句中。由於處理結果與常規SQL語句不同,所以它不受SQL注入的影響。

(筆者這裏不是很理解)

// Define 'where' part of query.
String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
// Specify arguments in placeholder order.
String[] selectionArgs = { String.valueOf(rowId) };
// Issue SQL statement.
db.delete(table_name, selection, selectionArgs);

update a database

當你需要修改數據庫的子集時,使用update方法。更新table包含了插入和刪除的語法。

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 ID
String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
String[] selectionArgs = { String.valueOf(rowId) };

int count = db.update(
    FeedReaderDbHelper.FeedEntry.TABLE_NAME,
    values,
    selection,
    selectionArgs);

至此,結束

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