Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android成長之路(6)——數據持久化處理

Android成長之路(6)——數據持久化處理

編輯:關於Android編程

保存key-value對——SharedPreferences

如果有比較小的數據,這些數據需要儲存,那麼就可以用到ShardPreferences。一個SharedPreferences對象指向一個包含key-value對的文件,它提供一些簡單的方法讀和寫。每個SharedPreferences文件,可以是私有的也可以是共享的。

注意: SharedPreferences 僅僅可以讀和寫key-value對,不要跟Preference搞混了。

對 SharedPreferences 的一些操作

可以通過調用下面的兩個方法中的一個,來創建一個新的SharedPreferences或者獲取一個已經存在的sharedPreferences:

getSharedPreferences() — 如果想讓多個activity可以分享這些SharedPreferences,就可以調用這個方法。需要在第一個參數傳入一個名字來作為唯一標識符(也就是key)。可以在app的任何一個Context 中調用它。

getPreferences() — 如果只是想當前這個activity可以分享到一個sharedpreferences的數據,就調用getPreferences()。因為這樣會獲得一個默認的屬於這個activity的sharedpreferencse,所以就不用傳入一個名字來標識了。

例如,在一個Fragment裡面,通過資源字符串 R.string.preference_file_key來獲得唯一標識的sharedpreference,然後使用private模式,所以只有該app才可以獲得它。

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

當對一個SharedPerference命名的時候,應該用一個可以唯一標識的名字來作為標識符,比如:
“com.example.myapp.PREFERENCE_FILE_KEY”,使用包名做前綴,這樣就不會和其他的app搞混了。

或者,如果只需要在一個activity中分享,就可以使用 getPreferences()方法了。

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

注意 : 如果在創建一個SharedPreference的時候,使用MODE_WORLD_READABLE 或者 MODE_WORLD_WRITEABLE,那麼其他任何一個知道標識符的app也都可以獲取裡面的數據了。

寫入數據到 SharedPreferences中

通過SharedPreferences對象調用edit(),創建一個SharedPreferences.Editor,然後寫入到SharedPreferences中。使用 putInt() 或者 putString(),傳遞想要寫入的keys和values。最後調用commit()方法來提交修改。
例如:

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
int newHighScore = getNewHighScore();  //getNewHighScore()返回一個int類型的值
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();

從SharedPreferences中讀出數據

從SharedPreferences中讀出數據,需要調用 getInt()或者 getString()等。通過傳入的標識名字(也就是key)來獲得想要的數據(value)。 如果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);

如果getString(R.string.saved_high_score)沒有找到值,就選擇defaultValue來代替返回值。比如字符串返回默認值為“ ”,boolean返回一個默認值false等等。

保存數據到文件

Android 使用一個文件系統,有點類似其它平台基於磁盤的文件系統。
一個File對象適用於讀寫較大的數據。比如對於圖片文件或者一些跟網絡交換的數據。

選擇內部或者外部存儲。
所有的Android設備都有兩個文件儲存區域:外部和內部存儲。
內部存儲,大部分數據放置在不容易丟失的內存中
外部存儲,可移動的存儲媒介,比如SD卡
以下是在存儲空間中的兩種存儲總結:

內部存儲:

.它總是可用可訪問的。
.默認情況下只有你的應用可以訪問此處保存的文件。
.當用戶卸載你的應用時,系統會從內部存儲中刪除您的應用的所有文件。
.當您希望確保用戶或其他應用均無法訪問您的文件時,內部存儲是最佳選擇。

外部存儲:

.它並非始終可用,因為用戶可采用 USB 存儲的形式裝載外部存儲,並在某些情況下會從設備中將其刪除。
.它是全局可讀的,因此此處保存的文件可能被其他應用讀取。
.當用戶卸載你的應用時,只有在你通過 getExternalFilesDir() 將你的應用的文件保存在目錄中時,系統才會從此處刪除您的應用的文件。
.對於無需訪問限制以及你希望與其他應用共享或允許用戶使用電腦訪問的文件,外部存儲是最佳位置。

盡管app默認安裝在內部存儲中,但可以在manifest文件中指定 android:installLocation 的屬性,這樣app便可安裝在在外部存儲中。當 APK 非常大且它們的外部存儲空間大於內部存儲時,用戶更青睐這個選擇

獲得外部存儲的權限

想要寫入到外部存儲,需要在manifest文件中請求WRITE_EXTERNAL_STORAG權限:


    
    ...

如果在app中使用WRITE_EXTERNAL_STORAGE,那麼這意味著也擁有了讀取的權限。注意:如果只需要讀取外部存儲的數據而不需要寫入,要在在manifest文件中請求READ_EXTERNAL_STORAGE權限。


    
    ...

但是,官方說到,這種請求WRITE_EXTERNAL_STORAGE也默認可以讀取的方式以後會改變。
以下官方原文:
Caution: Currently, all apps have the ability to read the external storage without a special permission. However, this will change in a future release. If your app needs to read the external storage (but not write to it), then you will need to declare the READ_EXTERNAL_STORAGE permission. To ensure that your app continues to work as expected, you should declare this permission now, before the change takes effect.

如果想保存文件到內部存儲中,就不需要請求任何的權限了,app可以直接向內部存儲寫入和讀取。

保存文件到內部存儲

當保存文件到內部存儲時,通過調用下面兩個方法中的其中一個,就可以獲得適合的目錄作為文件儲存的地方:

getFilesDir()
返回一個File值,表示內部目錄

getCacheDir()
返回一個File值,表示內部目錄下臨時緩存。
確保一旦這個緩存文件不再需要,記得要及時刪掉它,確保有足夠的內存。如果系統在低內存下運行,系統可能會自動刪除掉緩存文件,並且不會有任何警告提示。

我們可以使用File()構造器來創建一個新的文件,傳入上面兩個方法其中一個的返回值(也就是文件目錄)作為唯一標識內部存儲的路徑。
例如:

//創建一個文件
String filename = "myfile";
File file = new File(context.getFilesDir(), filename);

然後,可以調用 openFileOutput()來獲得一個FileOutputStream,再通過FileOutputStream調用write()寫入數據到內部存儲的目錄中。
例如:

//創建一個文件
String filename = "myfile";
File file = new File(context.getFilesDir(), filename);

String message = "Hello world!";
FileOutputStream outputStream;

//寫入文件
try {
  outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
  outputStream.write(message.getBytes());
  outputStream.close();
} catch (Exception e) {
  e.printStackTrace();
}

app的內部存儲目錄是按照app的包名作為目錄存儲在Android文件系統下。如果設置文件的模式是Readable,其他app就可以讀取你的內部文件,當然,其他app也要知道你的app的包名和文件名才可以。所以,只要在內部存儲中使用MODE_PRIVATE模式,其他app就不可訪問裡面的內容了。

或者,如果需要緩存一些文件,那麼應該用createTempFile()代替File()來創建一個緩存的文件。下面的例子,從URL中提取信息作為文件的名字,存儲在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;
}

保存文件到外部存儲

因為外存儲存並非始終可用,用戶可采用 USB 或者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;
}

盡管外部存儲的數據可以被用戶或者其他app更改,但是有兩類文件要保存在這裡:

公共文件
這類文件對於用戶或者其他app應該是自由可用的,當用戶卸載app時,該文件仍然會被保留。
比如,通過app拍下來的照片或者下載的數據。

私人文件
這類文件屬於app,並且當用戶卸載app時文件也會跟著被刪除。雖然用戶或者其他app實際上還是可以進入這個文件,因為它放在外部存儲下面。這類文件不會給其他app提供數據。
比如,用戶通過app緩存的一些視頻文件。

如果想存儲一些公共文件在外部存儲中,使用 getExternalStoragePublicDirectory() 方法,會得到一個File值,表示一個合適的外部存儲目錄。這個方法可以傳入參數來指定想要保存文件的類型,就可以與其他公共文件邏輯地組織在一起,比如 DIRECTORY_MUSIC 或者 DIRECTORY_PICTURES.

public File getAlbumStorageDir(String albumName) {
    File file = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES), albumName);
    if (!file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

如果想存儲私人文件,則需要調用getExternalFilesDir() 。這樣創建的每個目錄,當用戶卸載app,會被系統刪除

public File getAlbumStorageDir(Context context, String albumName) { 
    File file = new File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName);
    if (!file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}

如果沒有事先已經定義名字的文件,那就調用getExternalFilesDir(),不用傳入參數。這樣就會返回一個外部存儲的根目錄。

File file = new File(context.getExternalFilesDir());

查詢剩余可用空間

如果提前知道了要保存多少數據,想要找足夠的可用空間來存儲不會拋出IO異常,那麼就可以調用getFreeSpace()或者 getTotalSpace()來看看目前可用的空間和總的空間。

如果不知道確切需要多少空間,可以在保存數據之前不需要來檢查可用空間的數量。我們可以嘗試寫入,然後捕捉IO異常。比如把png格式的圖片轉換成jpeg格式,我們事先並不知道所需要存儲的大小。

刪除文件

刪除不再需要的文件。最直接的方法就是讓該文件調用delete()

file.delete();

如果文件保存在內部存儲,也可以通過本地的Context來調用deleteFile(),傳入文件名。

myContext.deleteFile(fileName);

注意: 當用戶卸載app時,Android系統會刪除的文件:
1、所有保存在內部存儲的文件。
2、所有使用getExternalFilesDir()保存在外部存儲的文件。

保存數據到SQL數據庫中

如果數據是重復的有結構的,那麼保存在數據庫裡面最合適了。比如說手機聯系人的信息。
接下來,看看怎麼把數據保持到SQL數據庫中

定義一個Contract類

SQL databases主要的原則之一就是模式(即架構):告訴我們數據庫是怎麼組織的。這個模式就體現在我們創建數據所用到的SQL語句. 對於創建一個Contract類,模式是非常有用的。
那什麼是Contract類呢?Contract類可以看做一個容器,裡面定義了URI、表和其他的方法等。裡面的每一個內部類對應一個table,會枚舉出每一個屬性。

public final class FeedReaderContract {
    // 防止有人意外地實例化這個類,所以給它一個空的構造器
    public FeedReaderContract() {}
    ...
    /* 內部類定義表的內容 */
    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";
        ...
    }
    ...
}

注意: 通過實現 BaseColumn這個接口,內部類就可以繼承一個域 _ID作為主鍵。一些類比如Cursor類會需要用的數據庫中的主鍵。這不是必需的,但是能夠幫助數據庫和Android框架協調地工作。

創建一個數據庫

就像保存在內部存儲的文件一樣,Android存儲數據庫在一個私有的空間裡。數據是安全的,因為這片區域是不允許其他應用訪問的。我們可以使用SQLiteOpenHelper 類,獲得對數據庫的引用。需要使用數據庫的時候,調用getWritableDatabase() 或者 getReadableDatabase()

注意:因為會長時間運行,要確保getWritableDatabase()或者getReadableDatabase()這兩個方法在後台線程中調用,比如AsyncTask或者 IntentService。

創建一個子類繼承SQLiteOpenHelper,覆蓋onCreate(),onUpgrade()onOpen()這些回調方法。還有一個 onDowngrade(),但這不是必需的。

public class FeedReaderDbHelper extends SQLiteOpenHelper {
    // 如果改變了數據庫的模式,那麼必須升級數據庫版本,在版本數上加1
    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) {
        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 +
            ... 
            " )";
        private static final String SQL_DELETE_ENTRIES =
            "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;

        db.execSQL(SQL_CREATE_ENTRIES);
    }

    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 數據庫只對在線數據進行緩存,所以它的升級方法就是簡單地丟棄並重新開始
        db.execSQL(SQL_DELETE_ENTRIES);
        onCreate(db);
    }

    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        onUpgrade(db, oldVersion, newVersion);
    }
}

把信息存進數據庫裡面

為了可以進入數據庫,要先實例化一個SQLiteOpenHelper子類——剛才創建的FeedReaderDbHelper。調用getWritableDatabase()可以向數據庫寫入數據。用一個ContentValues對象按照key-value的方式裝入數據,然後通過傳遞一個ContentValues對象到insert()方法中,來插入數據:

FeedReaderDbHelper mDbHelper = new FeedWritableDbHelper(getContext());

SQLiteDatabase db = mDbHelper.getWritableDatabase();

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

long newRowId;
newRowId = db.insert(
         FeedEntry.TABLE_NAME,
         null,
         values);

這裡insert()有三個參數:
第一個參數是表名。
第二個參數是當ContentValues為空情況下,給某些可為空的屬性自動賦值 為NULL,一般用不到這
個功能,直接傳入 null 。
第三個參數是一個 ContentValues 對象。

從數據庫中讀出數據

調用 query() 方法從數據庫中讀取信息。查詢的結果會返回一個Cursor對象。

SQLiteDatabase db = mDbHelper.getReadableDatabase();

String[] projection = {
    FeedEntry._ID,
    FeedEntry.COLUMN_NAME_TITLE,
    FeedEntry.COLUMN_NAME_UPDATED,
    ...
    };

String selection = "where "+FeedEntry.COLUMN_NAME_TITLE+" = ?";

String selectionArgs =  new String[]{"title_1"};

String sortOrder =
    FeedEntry.COLUMN_NAME_UPDATED + " DESC";

Cursor cursor = db.query(
    FeedEntry.TABLE_NAME,                     
    projection,                              
    selection,                               
    selectionArgs,                           
    null,                                    
    null,                                    
    sortOrder                               
    );


cursor.moveToFirst()

long itemId = cursor.getLong(
           cursor.getColumnIndex(FeedEntry._ID));

String title = cursor.getString(
           cursor.getColumnIndex(FeedEntry.COLUMN_NAME_TITLE);

...

query這裡第一個參數當然是表名了。
第二個參數,返回的每一列
第三參數是where語句
第四個參數是where語句指定的值
第五個參數是Group by語句
第六個參數是對Group by語句之後的過濾
第七個就是數據的排序了。

查詢完返回一個Cursor對象,使用對象先調用 moveToFirst(),它將讀取第一項的結果。對於每一行,通過調用getString()或者getLong()來讀取每一列的值。每個方法再調用getColumnIndex() 或者 getColumnIndexOrThrow()來傳遞那一列的位置。想讀取下一項的數據,就調用moveToNext()

刪除數據庫

delete()傳入三個參數
第一個參數是表名
第二個參數是where語句
第三個參數是where語句指定的值

String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";

String[] selectionArgs = { String.valueOf(rowId) };

db.delete(table_name, selection, selectionArgs);

更新數據庫

update()結合了insert()和delete()的一些參數,分別是tableName,value,selection,selectionArgs

SQLiteDatabase db = mDbHelper.getReadableDatabase();

// 新的值
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);

String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";

String[] selectionArgs = { String.valueOf(rowId) };

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

不過也有人更加青睐於直接使用 SQL 來操作數據庫
比如:

查詢:

db.execSQL("insert into ComputerBook (name, author, pages, price) values(?, ?, ?, ?)",
     new String[] { "Java Programing", "Dan Brown", "310", "49" });

更新

db.execSQL("update ComputerBook set price = ? where name = ?", new String[] { "50","Java Programing" });

刪除

db.execSQL("delete from ComputerBook where pages > ?", new String[] { "300" });

查詢

db.rawQuery("select * from ComputerBook", null);
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved