編輯:關於Android編程
Android數據持久化存儲共有四種方式,分別是文件存儲、SharedPreferences、Sqlite數據庫和ContentProvider。在本篇幅中只介紹前面三種存儲方式,因為ContentProvider屬於android四大組件之一,所以它的數據存儲方式在介紹四大組件的時候說明。
1、文件存儲
文件存儲不對存儲的內容進行任何的格式化處理,所有數據都是原封不動地保存到文件當中的,因而它比較適合用於存儲一些簡單的文本數據或二進制數據。
文件存儲有兩種方式,一是存儲到手機內存中(memory),一是存儲到sd卡中。
1.1、存儲到手機內存中
1)將數據存儲到文件中
Context類中提供了一個openFileOutput()方法,可以用於將數據存儲到指定的文件中。
方法介紹:
openFileOutput(name,mode):
name:是文件名,注意這裡指定的文件名不可以包含路徑,因為所有的文件都是默認存儲到/data/data/
mode:是文件的操作模式,
MODE_PRIVATE:默認的操作模式,同名文件內容會被覆蓋。
MODE_APPEND則表示如果該文件已存在就往文件裡面追加內容,不存在就創建新文件。
還有另外兩種(android4.2被廢棄),MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,這兩種模式表示允許其他的應用程序對我們程序中的文件進行讀寫操作。
openFileOutput()方法返回的是一個FileOutputStream對象,得到了這個對象之後就可以使用Java流的方式將數據寫入到文件中了。
publicvoidsave(){
Stringdata="Datatosave";
FileOutputStreamout=null;
BufferedWriterwriter=null;
try{
out=openFileOutput("data",Context.MODE_PRIVATE);
writer=newBufferedWriter(newOutputStreamWriter(out));
writer.write(data);
}catch(IOExceptione){
e.printStackTrace();
}finally{
try{
if(writer!=null){
writer.close();
}
}catch(IOExceptione){}
}
}
}
以上代碼就是將將一段文本內容保存到文件中。
完成將數據存儲到文件中,接下來如何從文件中讀取數據呢?
2)從文件中讀取數據
Context類中還提供了一個openFileInput()方法,用於從文件中讀取數據。
這個方法只接收一個參數,即要讀取的文件名,使用openFileInput("filename")讀取存放在/data/data/
publicStringload(){
FileInputStreamin=null;
BufferedReaderreader=null;
StringBuildercontent=newStringBuilder();
try{
in=openFileInput("data");
reader=newBufferedReader(newInputStreamReader(in));
Stringline="";
while((line=reader.readLine())!=null){
content.append(line);
}
}catch(IOExceptione){
e.printStackTrace();
}finally{
if(reader!=null){
try{
reader.close();
}catch(IOExceptione){
e.printStackTrace();
}
}
}
returncontent.toString();
}
1.2、存儲到sd卡中
前面說了file儲存,數據是存儲在應用程序內的,文件大小受到限制;當要存儲大的文件時,就要使用sd卡方式存儲了。使用sd卡存儲主要是使用Environment這個類的功能。
1)將數據存儲到sd卡中
Environment類介紹:
①常用常量:
StringMEDIA_MOUNTED:外部存儲器可讀可寫
MEDIA_MOUNTED_READ_ONLY:外部存儲器只讀
②常用方法:
getExternalStorageDirectory():獲取SDCard的目錄,/mnt/sdcard
getExternalStorageState():獲取外部存儲器的當前狀態
通過簡單價紹Environment類的使用,接下來就是sd卡讀寫數據了。以下是往sd卡讀寫數據的步驟:
1)判斷sd卡的狀態
2)獲取存儲器的目錄,即sd卡的目錄
3)使用IO流對sd卡進行文件的讀寫操作
4)在清單文件中添加權限
寫入數據到sd卡中代碼如下:
publicvoidwrite2Sd(){
if(Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)){
Stringcontent=edtContent.getText().toString();
try{
//獲取SDcard路徑
FilesdCardDir=Environment
.getExternalStorageDirectory();
//SDCard目錄:/mnt/sdcard
StringsdcardPath=sdCardDir.getAbsolutePath();
Filefile=newFile(sdCardDir,FILE_NAME);
//Filefile=newFile(sdcardPath
//+File.separator+FILE_NAME);
//以指定文件創建RandomAccessFile對象
RandomAccessFileraf=newRandomAccessFile(file,"rw");
//將文件記錄指針移動最後
raf.seek(file.length());
//輸出文件內容
raf.write(content.getBytes());
raf.close();
}catch(Exceptione){
//TODO:handleexception
}
}
}
以上代碼可知,往sd卡中寫數據先調用getExternalStorageState()判斷sd卡狀態,接著獲取sd卡的存儲路徑,以路徑很文件名創建文件,最後使用RandomAccessFile類存儲數據,使用該類的好處是可以定位的指定位置進行讀寫數據。
2)從sd卡中讀取數據
publicvoidreadFromSd(){
if(Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)){
//獲取SDcard路徑
StringBuildersb=newStringBuilder();
try{
FilesdCardDir=Environment
.getExternalStorageDirectory();
Filefile=newFile(sdCardDir,FILE_NAME);
InputStreaminputStream=newFileInputStream(file);
intlen=0;
byte[]buffer=newbyte[1024];
while((len=inputStream.read(buffer))!=-1){
sb.append(newString(buffer,0,len));
}
tvResult.setText(sb.toString());
//關閉流
inputStream.close();
}catch(Exceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
}
2.1、將數據存儲到sp中
SharedPreferences類,它是一個輕量級的存儲類,適合用於保存軟件配置參數。SharedPreferences是使用鍵值對的方式來存儲數據的,存儲數據是以xml文件形式存儲,文件存放在/data/data/
步驟:
1)獲取SharedPreferences對象。
要想使用sp來存儲數據,首先需要獲取到sp對象。Android中主要提供了三種方法用於得到sp對象。
1、Context類中的getSharedPreferences(name,mode)方法
Name:用於指定SharedPreferences文件的名稱
mode指定操作模式,和文件存儲模式一樣。
2、Activity類中的getPreferences(mode)方法
使用這個方法時會自動將當前活動的類名作為SharedPreferences的文件名。
3.PreferenceManager類中的getDefaultSharedPreferences(context)方法 這是一個靜態方法,使用當前應用程序的包名作為前綴名來命名文件。
2)調用sp對象的edit()方法來獲取一個SharedPreferences.Editor對象。
3)向SharedPreferences.Editor對象中添加數據。比如添加一個布爾型數據就使用putBoolean方法,添加一個字符串則使用putString()方法,以此類推。
4)調用commit()方法將添加的數據提交,從而完成數據存儲操作
2.2、從sp中讀取數據
步驟:
1)獲取SharedPreferences對象。
2)然後分別調用它的geXxx()方法獲取存儲的值。如getString()、getInt()和getBoolean(),沒有找到相應的值就會使用方法中傳入的默認值來代替
SQLite是一款輕量級的關系型數據庫,它的運算速度非常快,占用資源很少,通常只需要幾百K的內存就足夠了。SQLite不僅支持標准的SQL語法,還遵循了數據庫的ACID事務。
Android提供了一個SQLiteOpenHelper幫助類,借助這個類就可以非常簡單地對數據庫進行創建和升級。
SQLiteOpenHelper是一個抽象類,有兩個抽象方法,分別是onCreate()和onUpgrade(),分別在這兩個方法中去實現創建、升級數據庫的邏輯。SQLiteOpenHelper中還有兩個實例方法,getReadableDatabase()和getWritableDatabase()。這兩個方法都可以創建或打開一個現有的數據庫(如果數據庫已存在則直接打開,否則創建一個新的數據庫),並返回一個可對數據庫進行讀寫操作的對象。
3.1、創建數據庫
因為SQLiteOpenHelper是一個抽象類,創建數據庫的時候可以創建一個類繼承SQLiteOpenHelper,並重寫其兩個抽象方法。新建MyDatabaseHelper類繼承自SQLiteOpenHelper,代碼如下所示:
publicclassMyDatabaseHelperextendsSQLiteOpenHelper{
publicstaticfinalStringCREATE_BOOK="createtablebook("
+"idintegerprimarykeyautoincrement,"
+"authortext,"
+"pricereal,"
+"pagesinteger,nametext)";
privateContextmContext;
publicMyDatabaseHelper(Contextcontext,Stringname,
CursorFactoryfactory,intversion){
super(context,name,factory,version);
mContext=context;
}
@Override
publicvoidonCreate(SQLiteDatabasedb){
db.execSQL(CREATE_BOOK);
Toast.makeText(mContext,"Createsucceeded",Toast.LENGTH_SHORT).show();
}
@Override
publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion){
}
}
如上所示,先把建表語句寫成字符串常量,在onCreate()方法中調用SQLiteDatabase的execSQL()方法去執行這條建表語句,以保證數據庫創建的同時也成功的創建Book表。
然後再布局文件加入了一個id是.create_database的Button按鈕,用於創建數據庫。最後修改MainActivity中的代碼,如下所示:
publicclassMainActivityextendsActivity{
privateMyDatabaseHelperdbHelper;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper=newMyDatabaseHelper(this,"BookStore.db",null,1);
ButtoncreateDatabase=(Button)findViewById(R.id.create_database);
createDatabase.setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(Viewv){
dbHelper.getWritableDatabase();
}
});
}
}
如上所示,在onCreate()方法中構建了一個MyDatabaseHelper對象,並且通過構造函數的參數將數據庫名指定為BookStore.db,版本號指定為1,然後在Createdatabase按鈕的點擊事件裡調用了getWritableDatabase()方法。這樣當第一次點擊按鈕時,就會檢測到當前程序中並沒有BookStore.db這個數據庫,於是會創建該數據庫並調用MyDatabaseHelper中的onCreate()方法同時也創建Book表。
3.2、升級數據庫
MyDatabaseHelper中的onUpgrade()方法是用於對數據庫進行升級的。
接著我們再添加一張Category表用於記錄書籍的分類,修改MyDatabaseHelper代碼如下:
publicclassMyDatabaseHelperextendsSQLiteOpenHelper{
publicstaticfinalStringCREATE_CATEGORY="createtableCategory("
+"idintegerprimarykeyautoincrement,"
+"category_nametext,"
+"category_codeinteger)";
.....
@Override
publicvoidonCreate(SQLiteDatabasedb){
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion){
switch(oldVersion){
case1:
db.execSQL(CREATE_CATEGORY);
case2:
db.execSQL("altertableBookaddcolumncategory_idinteger");
default:
}
}
}
可以看到,這裡在onUpgrade()方法中加入了一個switch判斷,如果oldVersion等於1,就再創建一個CATEGORY表。接著只需要在創建MyDatabaseHelper時構造函數第四個參數改成大於1的整數即可。
緊接著,發現CATEGORY表少添加了category_id列,為了保留原有數據庫的數據,只需在添加一個case語句,使用alter..add添加該列就行,注意case之間沒有break。使用這種方式升級數據庫就不會導致表中的數據丟失問題。
3.3、數據庫的CRUD操作
數據庫的crud其中C代表添加(Create),R代表查詢(Retrieve),U代表更新(Update),D代表刪除(Delete)。每一種操作又各自對應了一種SQL命令。
調用SQLiteOpenHelper的getReadableDatabase()或getWritableDatabase()
方法是可以用於創建和升級數據庫的,這兩個方法還都會返回一個SQLiteDatabase對象,借助這個對象就可以對數據進行CRUD操作。
1)C數據庫的add操作
SQLiteDatabase中的insert(table,nullColumnHack,values)方法
Table:表名
nullColumnHack:通常用不到,直接傳null。
Value:ContentValue,是map集合,表示待添加的數據。
publicvoidadd(){
SQLiteDatabasedb=dbHelper.getWritableDatabase();
ContentValuesvalues=newContentValues();//開始組裝第一條數據
values.put("name","TheDaVinciCode");
values.put("author","DanBrown");
values.put("pages",454);
values.put("price",16.96);
db.insert("Book",null,values);
}
2)R數據庫的query操作
SQLiteDatabase中的query(table,columns,selection,selectionArgs,groupBy,having,orderBy)方法:
table,表名
columns,指定去查詢哪幾列
selection,selectionArgs,指定約束條件和占位符,表示查詢哪幾行
groupBy,對查詢結果進行groupby操作
having,對groupBy後的數據進行進一步過濾
orderBy,對查詢的結果進行排序
publicvoidquery(){
SQLiteDatabasedb=dbHelper.getWritableDatabase();
//查詢Book表中所有的數據
Cursorcursor=db.query("Book",null,null,null,null,null,null);
if(cursor.moveToFirst()){
do{
//遍歷Cursor對象
Stringname=cursor.getString(cursor.getColumnIndex("name"));
Stringauthor=cursor.getString(cursor
.getColumnIndex("author"));
intpages=cursor.getInt(cursor.getColumnIndex("pages"));
doubleprice=cursor.getDouble(cursor.getColumnIndex("price"));
}while(cursor.moveToNext());
}
cursor.close();
}
3)U數據庫的update操作
SQLiteDatabase中的update(table,values,whereClause,whereArgs)方法
Table:表名
Value:ContentValue,是map集合。
whereClause、whereArgs:指定第三和第四個參數用於指定修改哪些行
publicvoidupdate(){
SQLiteDatabasedb=dbHelper.getWritableDatabase();
ContentValuesvalues=newContentValues();
values.put("price",10.99);
db.update("Book",values,"name=?",
newString[]{"xiaoming"});
}
4)D數據庫的delete操作
SQLiteDatabase中的delete(table,whereClause,whereArgs)
table,表名
whereClause,whereArgs:指定要刪除哪些行,對應sql語句的where部分。
publicvoiddelete(){
SQLiteDatabasedb=dbHelper.getWritableDatabase();
db.delete("Book","pages>?",newString[]{"500"});
}
3.4、使用SQL操作數據庫
Android中支持使用原生的sql語句執行數據庫的增刪改查操作。具體如下:
添加數據的方法如下:
db.execSQL("insertintoBook(name,author,pages,price)values(?,?,?,?)",newString[]{"TheDaVinciCode","DanBrown","454","16.96"});
更新數據的方法如下:
db.execSQL("updateBooksetprice=?wherename=?",newString[]{"10.99","TheDaVinciCode"});
刪除數據的方法如下:
db.execSQL("deletefromBookwherepages>?",newString[]{"500"});
查詢數據的方法如下:
db.rawQuery("select*fromBook",null);
使用SQLiteOpenHelper有一個弊端,就是如果數據庫中某一列已經沒有用了,我想把這一列刪除,使用SQLite是無法實現刪除某一列的(開發中多是忽視它,反正以後也用不到,留著也占用不了多少空間),另外就是如果表之間存在關聯關系,建表語句就會變得很復雜,因此使用LitePal來自動建立表關聯又是一個非常不錯的選擇,我們不需要關心什麼外鍵、中間表等實現的細節,只需要在對象中聲明好它們相互之間的引用關系,LitePal就會自動在數據庫表之間建立好相應的關聯關系了。
注:雖然使用Litepal框架可以輕松實習表的關聯,但表的關聯知識了解下還是有必要的。多表設計數據庫口訣:
一對一關聯的實現方式是用外鍵,多對一關聯的實現方式也是用外鍵(外鍵必須加載多方),多對多關聯的實現方式是用中間表。
LitePal開源項目地址:https://github.com/LitePalFramework/LitePal
1、對話框通知(Dialog Notification)當你的應用需要顯示一個進度條或需要用戶對信息進行確認時,可以使用對話框來完成。下面代碼將打開一個如圖所示的對話框:
最近在開發中,涉及到用戶的意見反饋功能這一方面的開發,需要用戶輸入的文字或者提交的圖片,效果大概類似於微信朋友圈那樣的圖片選擇器,一開始自己找了個用universal-i
復制代碼 代碼如下:filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + /A
在開始講述touch事件流程之前,還簡單介紹下TouchEvent,View和ViewGroup。1. MotionEvent 整個事件分發流程中,會