- 持久化技術(shù)
指將用戶產(chǎn)生的數(shù)據(jù),存儲到手機(jī)中,即使手機(jī)關(guān)機(jī)數(shù)據(jù)也不會丟失。Android有文件存儲、ShardPreference存儲、數(shù)據(jù)庫存儲、或者SD卡存儲這幾種方式來實(shí)現(xiàn)數(shù)據(jù)持久化。但相對來說前三種方式會相對比較簡單和安全。
1. 文件存儲
Android最基本的數(shù)據(jù)存儲方式,不對存儲內(nèi)容進(jìn)行任何處理,所有數(shù)據(jù)原封不動的保存到文件當(dāng)中,比較適合用于存儲一些簡單的文本或二進(jìn)制數(shù)據(jù)。
1.1 將數(shù)據(jù)存儲到文件中
使用Context類提供的openFileOutput()方法可以將數(shù)據(jù)存儲到指定文件中。默認(rèn)存儲到/data/data/<packagename>/files/目錄下
openFileOutput()參數(shù)說明:
- 第一個:文件名,文件創(chuàng)建時使用的名稱,無需包含路徑。
- 第二個:文件的操作模式,MODE_PRIVATE: 默認(rèn)操作模式,當(dāng)指定的文件名已存在的時候,覆蓋已存在的內(nèi)容。MODE_APPEND: 當(dāng)指定文件已存在的時候就忘里邊追加內(nèi)容。
- 該方法返回一個FileOutputStream對象,得到該對象之后就可以對文件進(jìn)行保存了。
-
以下展示了將一段文本內(nèi)容保存到文件中
public void save() { String data = "我是要保存的文本"; FileOutputStream out = null; BufferdWriter writer = null; try { out = openFileOutput("data", Context.MODE_PRIVATE); writer = new BufferedWriter(new OutputStreamWriter(out)); writer.writer(data); } catch (IOException e) { e.printStackTrace(); } finally { try { if (writer != null) { writer.close(); } } catch (IOException e) { e.printStackTrace(); } } }
1.2 從文件中讀取數(shù)據(jù)
使用Context類提供的OpenFileInput()方法,用于從文件中讀取數(shù)據(jù)。
這個方法的使用只需傳入要讀取的文件名,然后就會從/data/data/<package name>/files目錄下去加載這個文件,返回一個FileInputStream()對象。
- 示例代碼如下:
public String load() {
FileInputStream in = null;
BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try {
in = openFieInput("data");
reader = new BufferedReader(new InputStreamReader(in));
String line = "";
while ((line = reader.readLine()) != null) {
content.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e){
e.printStackTrace();
}
}
}
return content.toString();
}
2.SharedPreferences存儲
SharedPreferences是使用鍵值對的方式來存儲數(shù)據(jù)的,如果存儲的數(shù)據(jù)是整型,讀出來的數(shù)據(jù)也是整形,所以使用SharedPreferences來進(jìn)行數(shù)據(jù)存儲要比文件方便很多。
2.1 將數(shù)據(jù)存儲到SharedPreferences中
- 獲取SharedPreferences對象(Android有三種方式可以得到SharedPreferences對象)
- Activity類中的getPreferences()方法
- PreferenceManager類中的getDefaultSharedPreferences()方法
- Context類中的getSharedPreferences()方法
pref = PreferenceManager.getDefaultSharedPreferences(this);
pref = getPreferences(MODE_PRIVATE);
pref = getSharedPreferences("", MODE_PRIVATE);
- 調(diào)用SharedPreferences對象的edit()方法。
- 向SharedPreferences.Editor對象中添加數(shù)據(jù)。
- 調(diào)用apply()方法將添加的數(shù)據(jù)提交。
Button button = (Button) findViewById(R.id.save_data);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//獲得SharedPreferences對象
SharedPreferences.Editor editor = getSharedPreferences("data", MODE_PRIVATE).edit();
//寫如要保存的數(shù)據(jù)
editor.putString("name", "Tom");
editor.putInt("age", 28);
editor.putBoolean("married", false);
//調(diào)用apply()方法
editor.apply();
}
});
2.2 從SharedPrefrences中讀取數(shù)據(jù)
步驟跟存數(shù)據(jù)類似
需要注意的是,讀取數(shù)據(jù)不需要調(diào)用edit()方法。
Button button1 = (Button)findViewById(R.id.restore_data);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences pref = getSharedPreferences("data", MODE_PRIVATE);
String name = pref.getString("name", "");
int age = pref.getInt("age", 0);
boolean married = pref.getBoolean("married", false);
Log.d("MainActivity","name is " + name);
Log.d("MainActivity", "age is" + age);
Log.d("MainActivity", "married is" + married);
}
});
3.SQLite數(shù)據(jù)庫存儲
使用SQLiteOpenHelper類可以非常簡單的對數(shù)據(jù)庫進(jìn)行創(chuàng)建和升級。
3.1 SQLiteOpenHelper類介紹
- SQLiteOpenHelper是一個抽象類,如果要使用它,需要創(chuàng)建一個幫助類去繼承它,然后重寫onCreate()和onUpgrade()這兩個抽象方法。
- getReadableDatabase()和getWritableDatabase()是兩個非常重要的實(shí)例方法,可以創(chuàng)建或打開一個現(xiàn)有的數(shù)據(jù)庫(如果數(shù)據(jù)庫已存在直接打開,否則創(chuàng)建一個新的數(shù)據(jù)庫),并返回一個可對數(shù)據(jù)庫進(jìn)行讀寫操作的對象。注意:當(dāng)磁盤空間已滿時,使用getReadableDatabase()方法返回的為只讀數(shù)據(jù)庫,而getWritableDatabase()則將出現(xiàn)異常
3.2創(chuàng)建數(shù)據(jù)庫
SQLiteOpenHelper中有兩個構(gòu)造方法可供重寫,一般使用參數(shù)少一點(diǎn)方法即可
第一個參數(shù):Context
第二個參數(shù):數(shù)據(jù)庫名
第三個參數(shù):允許我們在查詢數(shù)據(jù)的時候返回一個自定義的Cursor,一般傳null
第四個參數(shù):當(dāng)前數(shù)據(jù)庫的版本號??捎糜趯?shù)據(jù)庫進(jìn)行操作
class MyDatabaseHelper extends SQLiteOpenHelper {
//定義SQL的建表語句
public static final String CREATE_BOOK2 = "create table Book("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
private Context mContext;
//重寫構(gòu)造方法
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
Toast.makeText(context, "創(chuàng)建", Toast.LENGTH_SHORT).show();
mContext = context;
}
//重寫onCreate方法
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK2);
Toast.makeText(mContext, "創(chuàng)建成功", Toast.LENGTH_SHORT).show();
}
//重寫onUpgrade()方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
3.3 升級數(shù)據(jù)庫
升級數(shù)據(jù)庫:在原有的數(shù)據(jù)庫基礎(chǔ)上添加新表。
主要通過SQLiteOpenHelper的構(gòu)造方法的第四個參數(shù)(當(dāng)前版本號)來實(shí)現(xiàn)。當(dāng)出入的版本好為新版本號時會執(zhí)行onUpgrade方法,在這個方法里邊實(shí)現(xiàn)升級數(shù)據(jù)庫的操作。
//重寫onUpgrade()方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//刪除已存在的表
db.execSQL("drop table if exists Book");
db.execSQL("drop table if exists Category");
//調(diào)用onCreate()方法,創(chuàng)建要創(chuàng)建的表
onCreate(db);
}
3.4 添加數(shù)據(jù)
SQLiteDatabase中提供了insert()方法,專門用于添加數(shù)據(jù),它接受三個參數(shù)
第一個參數(shù):表名
第二個參數(shù):用于在未指定添加數(shù)據(jù)的情況下給某些可為空的列自動賦值NULL,一般這個參數(shù)傳null即可
第三個參數(shù):一個ContentValues對象,它提供了一系列的put()方法重載,用于向ContentValues中添加數(shù)據(jù)。只需要將每個列名以及相應(yīng)的待添加數(shù)據(jù)傳入即可。
//添加數(shù)據(jù)
Button addData = (Button) findViewById(R.id.add_data);
addData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
//組裝第一條數(shù)據(jù)
values.put("name", "The Da Vinci Code");
values.put("author", "Dan Brown");
values.put("pages", 454);
values.put("price", 16.69);
db.insert("Book", null, values); //插入第一條數(shù)據(jù)
values.clear();
//組裝第二條數(shù)據(jù)
values.put("name", "The Lost Symbol");
values.put("author", "Dan Brown");
values.put("pages", 510);
values.put("price", 19.95);
db.insert("Book", null, values);//插入第二條數(shù)據(jù)
}
});
3.5 更新數(shù)據(jù)
SQLiteDatabase中提供了update()方法用于對數(shù)據(jù)進(jìn)行更新,這個方法接受四個參數(shù)
第一個參數(shù):表名,和insert()方法一樣
第二個參數(shù):ContentValues對象,把要更新的數(shù)據(jù)在這里組裝進(jìn)去
第三個參數(shù)第四個參數(shù);用于約束更新某一行或某幾行數(shù)據(jù),不指定的話默認(rèn)更新所有行
//更新數(shù)據(jù)
Button updateData = (Button) findViewById(R.id.update_data);
updateData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 1.99);
//將名字是The Da Vinci Code的書價格改為1.99
db.update("Book", values, "name = ?",new String[] {"The da Vinci Code"});
}
});
3.6 刪除數(shù)據(jù)
SQLiteDatabase提供了delete()方法用于刪除數(shù)據(jù),該方法接收三個參數(shù)
第一個參數(shù):表名,和其他方法一樣
第二個第三個參數(shù)是用來約束刪除某一行或某幾行的,跟update()方法的第三第四個參數(shù)類似。
//刪除數(shù)據(jù)
Button deleteData = (Button) findViewById(R.id.delete_data);
deleteData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
//刪除Book表中page大于500的數(shù)據(jù)
db.delete("Book", "page > ?0", new String[] {"500"});
}
});
3.7 查詢數(shù)據(jù)
使用SQLiteDatabase提供querry()方法除數(shù)據(jù)逆行查詢。該方法較為復(fù)雜,最短的一個方法重載也要傳入七個參數(shù),并返回一個Cursor對象
- 第一個參數(shù):表名
- 第二個參數(shù):用于指定查詢哪幾列,如果不指定默認(rèn)查詢所有列
- 第三第四個參數(shù):用于約束查詢某一行或者某幾行的數(shù)據(jù),不指定則默認(rèn)查詢所有行
- 第五個參數(shù):用于指定需要去group by的列,不指定則表示不進(jìn)行g(shù)roup by操作。
- 第六個參數(shù):用于對group by之后的數(shù)據(jù)進(jìn)行進(jìn)一步的過濾,不指定則表示不進(jìn)行過濾。
- 第七個參數(shù):用于指定查詢結(jié)果的排序方式,不指定則表示使用默認(rèn)的排序方式。
- 注:querry()方法會返回一個Cursor對象,查詢的所有數(shù)據(jù)都是從這個對象取出

//查詢數(shù)據(jù)
Button querryData = (Button) findViewById(R.id.querry_data);
querryData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
//查詢Book表中的所有數(shù)據(jù)
Cursor cursor = db.query("Book", null, null, null, null, null, null);
//調(diào)用moveToFirst()方法將數(shù)據(jù)指針移動到第一行位置
if (cursor.moveToFirst()) {
do {
//遍歷Cursor對象,取出數(shù)據(jù)并打印
String name = cursor.getString(cursor.getColumnIndex("name")); //getColumnIndex()方法可以獲得某一列在表中對應(yīng)的位置索引
String author = cursor.getString(cursor.getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
double price = cursor.getDouble(cursor.getColumnIndex("price"));
Log.d("MainActivity", "book name is + " + name);
Log.d("MainActivity", "book author is + " + author);
Log.d("MainActivity", "book page is + " + pages);
Log.d("MainActivity", "book price is + " + price);
} while (cursor.moveToNext());
}
cursor.close();
}
});
}
3.8 使用SQL語句操作數(shù)據(jù)庫
Android還可以不借助SQLiteDatabase的情況下進(jìn)行數(shù)據(jù)庫的增刪查改功能,具體使用方法如下
//添加數(shù)據(jù)的方法如下:
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", new String[] { "The Da Vinci Code", "Dan Brown", "454", "16.96" }); db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", new String[] { "The Lost Symbol", "Dan Brown", "510", "19.95" });
//更新數(shù)據(jù)的方法如下:
db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99", "The Da Vinci Code" });
//刪除數(shù)據(jù)的方法如下:
db.execSQL("delete from Book where pages > ?", new String[] { "500" });
//查詢數(shù)據(jù)的方法如下:
db.rawQuery("select * from Book", null);
3.9 SQLite使用事務(wù)
SQLite的特性是支持事務(wù)的,事務(wù)的特性可以保證讓某一系列的操作要么全部一起完成,要么一個都不完成。
比如有這么一個需求,需要將數(shù)據(jù)庫中的舊書替換成新書,這時候就需要把舊書刪除,然后把新書添加進(jìn)去,但是要保證舊書刪除之后新書必須添加成功,這個時候就可以使用事務(wù)來實(shí)現(xiàn)了。
//替換數(shù)據(jù)
Button replaceData = (Button) findViewById(R.id.replace_data);
replaceData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
//開啟事務(wù)
db.beginTransaction();
try {
db.delete("Book", null, null);
if (true) {
//在這里手動跑出一個異常,演示當(dāng)添加不成功時,事務(wù)失敗
throw new NullPointerException();
}
ContentValues values = new ContentValues();
values.put("name", "Game of Thrones");
values.put("author", "me");
values.put("pages", 750);
values.put("price", 20.22);
db.insert("Book", null, values);
db.setTransactionSuccessful();//事務(wù)執(zhí)行成功
} catch (Exception e) {
e.printStackTrace();
} finally {
//結(jié)束事務(wù)
db.endTransaction();
}
}
});
3.10 升級數(shù)據(jù)庫的最佳寫法
在3.3的時候已經(jīng)有演示了升級數(shù)據(jù)庫的寫法,但是那種方法是通過把原有的數(shù)據(jù)庫刪除,然后重新創(chuàng)建一個的方式來進(jìn)行升級的,這樣的話原有數(shù)據(jù)庫的信息也會隨著升級而丟失,所以有時候這種方法是不可取的。
如果想要進(jìn)行數(shù)據(jù)庫升級,并保留原有的數(shù)據(jù),需要為每個數(shù)據(jù)庫版本號賦予它各自改變的內(nèi)容,然后在onUpgrade()方法中去執(zhí)行更新操作。
//重寫onUpgrade()方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//刪除已存在的表
// db.execSQL("drop table if exists Book");
// db.execSQL("drop table if exists Category");
// //調(diào)用onCreate()方法,創(chuàng)建要創(chuàng)建的表
// onCreate(db);
switch (oldVersion) {
//對舊版本進(jìn)行判斷,執(zhí)行新版本要更新的內(nèi)容。這里需要注意,case后面不加break,以保證用戶直接從第一版升級至第三版時case1和case2都會執(zhí)行。
case 1:
db.execSQL(CREATE_GATEGORY);
case 2:
db.execSQL("alter table Book add colum category_id integer");
default:
}
}