標籤:des android blog http java os 使用 io strong
廢話不多說了,緊接著來講資料庫的操作吧。Come On!提到資料存放區問題,資料庫是不得不提的。資料庫是用來儲存關係型資料的不二利器。Android為開發人員提供了強大的資料庫支援,可以用來輕鬆地構造基於資料庫的應用。Android的資料庫應用,依託於當下最流行的開源嵌入式資料庫SQLite。在Android中,應用的資料庫檔案是該應用私人的,儲存在應用資料目錄下的databases子目錄內。從代碼結構來看,Android的資料庫實現可以分成兩個層次,在底層通過C++調用SQLite的介面來執行SQL語句,並通過JNI向上暴露Java可訪問的介面。一、Android資料庫使用
Android中使用android.database.sqlite.SQLiteDatabase來表示一個資料庫物件,它提供了兩種模式來協助開發人員進行增刪改查等基本資料庫操作。
- 利用SQL語句描述操作
利用SQL語句調用SQLiteDatabase.execSql或SQLiteDatabase.rawQuery來執行操作。
//利用sql查詢資料Cursor data = db.rawQuery("select id,name from table");//利用sql插入資料db.execSql("insert into contacts (id,name) values (2,‘cpacm‘)");稍微學過sql語句的人應該都看的懂上面的代碼(其實看語句的意思也能知道個大概~)
在這裡我來解釋一下Cursor(遊標)的作用吧,遊標不能顧名思義(up主當時學習資料庫時一度將遊標當做與C語言裡面的指標變數一樣,雖然有點對,但意思還是理解錯了),Cursor它是系統為使用者開設的一個資料緩衝區,是的,它是一塊資料區域,存放SQL語句的執行結果。但是它也提供了能從包括多條資料記錄的結果集中每次提取一條記錄的機制,這一點也跟指標很像。遊標總是與一條SQL選擇語句相關聯因為遊標由結果集(可以是零條、一條或由相關的選擇語句檢索出的多條記錄)和結果集中指向特定記錄的遊標位置群組成。當決定對結果集進行處理 時,必須聲明一個指向該結果集的遊標。用C語言作比較的話,如果寫過對檔案進行處理的程式,那麼遊標就像您開啟檔案所得到的檔案控制代碼一樣,只要檔案開啟成功,該檔案控制代碼就可代表該檔案。總之記住,遊標是一塊有著特有記號的一塊資料區域,能夠讓使用者逐條從中讀取出資料。
- 結構化的方式描述資料庫的操作
這樣即使我們不熟悉SQL語句,也能使用最熟悉的物件導向的方式進行資料庫操作。
//結構化的方式查詢資料Cursor data = db.query("contacts",new String[]{"id","name"},null,null,null,null,null);//結構化方式插入資料ContentValue values = new ContentValues();values.put("id",2);values.put("name","cpacm");db.insert("table",null,values);/*** 參數說明* table:資料表名,columns:需要顯示的列名,如果為null則相當與** selection:相當於sql語句的where條件;selectionArgs數組放的是where條件要替換的?號* groupBy:SQL語句的Group, orderBy: 排序,預設asc**/public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy){
}比如說我要查詢的SQL語句為
SELECT CustomerName, SUM(OrderPrice) FROM Orders WHERE Country=? GROUP BY CustomerName HAVING SUM(OrderPrice)>500 ORDER BY CustomerName
那麼我寫的代碼如下
//資料表名String table = "Orders" ; //要顯示的列名String[] columns = new String[] { "CustomerName" , "SUM(OrderPrice)" }; //選擇條件String selection = "Country=?" ; //裡面的變數對應條件中的問號,多個的時候請一一入座。String[] selectionArgs = new String[]{ "China" }; //分組名String groupBy = "CustomerName" ; //分組的條件String having = "SUM(OrderPrice)>500" ; //按欄位排序String orderBy = "CustomerName" ; Cursor c = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy); 這樣就能實現資料庫的查詢了。其它的語句參數都是差不多的,這裡就不一一介紹了。
public long insert (String table, String nullColumnHack, ContentValues values)
public int delete(String table, String whereClause, String[] whereArgs)
public int update(String table, ContentValues values, String whereClause, String[] whereArgs)
課外小知識:關於GroupBy和Having的使用
group by 顧名思義就是按照xxx進行分組,它必須有“彙總函式”來配合才能使用,使用時至少需要一個分組識別欄位。彙總函式有:sum()、count()、avg()等,使用group by目的就是要將資料分組進行匯總操作。比如上面sql語句的CustomerName,如果它有四個行{“張三”,“李四”,“張三”,“李四”},那麼此時就會分成兩組,分別為張三組和李四組,然後統計出他們使用的orderprice總和。
HAVING作用就是為每一個組指定條件,像where指定條件一樣,也就是說,可以根據你指定的條件來選擇行。如果你要使用HAVING子句的話,它必須處在GROUP BY子句之後。還是上面的SQL語句,如果張三的SUM(OrderPrice)沒有超過500,那麼張三組就不會顯示。
- SQL語句的先行編譯
在實踐中,有的SQL語句需要被反覆使用,為了避免反覆解析SQL語句產生的開銷,可以對需要複用的SQL語句進行先行編譯,來提高資料庫操作的執行效率。
//編譯複雜的SQL語句SQLiteStatement compiledSql = db.compileStatement(aSQL);//執行SQLcompiledSql.execute();
除此以外,Android還提供了豐富的進階資料庫功能,比如支援觸發器、支援複合索引以及支援對資料庫事務的處理。
try{ db.beginTransaction(); //執行相關的資料庫操作,如有異常,直接進入finally部分。 }finally{ //不論成功都要調用endTransaction來結束事務 db.endTransaction(); }課外小知識:所謂事務是使用者定義的一個資料庫操作序列,這些操作要麼全做要麼全不做,是一個不可分割的工作單位。例如,在關聯式資料庫中,一個事務可以是一條SQL語句、一組SQL語句或整個程式。 簡單舉個例子就是你要同時修改資料庫中兩個不同表的時候,如果它們不是一個事務的話,當第一個表修改完,可是第二表改修出現了異常而沒能修改的情況下,就只有第二個表回到未修改之前的狀態,而第一個表已經被修改完畢。 而當你把它們設定為一個事務的時候,當第一個表修改完,可是第二表改修出現了異常而沒能修改的情況下,第一個表和第二個表都要回到未修改的狀態!這就是所謂的交易回復。
- SQLiteOpenHelper
在SQLiteOpenHelper中,封裝了一個SqliteDatabase對象,使用著可以通過使用此類來進行資料庫的操作。
package com.example.notebook;import android.content.Context;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteOpenHelper;import android.database.sqlite.SQLiteDatabase.CursorFactory;public class DBHelper extends SQLiteOpenHelper{ private static final int VERSION=1; /** * 在SQLiteOpenHelper的子類當中,必須有該建構函式 * @param context 內容物件 * @param name 資料庫名稱 * @param factory * @param version 當前資料庫的版本,值必須是整數並且是遞增的狀態 */ public DBHelper(Context context,String name,CursorFactory factory,int version){ super(context,name,factory,version); } public DBHelper(Context context, String name, int version){ this(context,name,null,version); } public DBHelper(Context context, String name){ this(context,name,VERSION); } @Override public void onCreate(SQLiteDatabase db) { // 資料庫首次構造時,會調用該函數,可以在這裡構造表、索引,等等 System.out.println("create a database"); //execSQL用於執行SQL語句 db.execSQL("create table notebook(_id integer primary key autoincrement,pic varchar(50),title varchar(20),content text,time varchar)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 如果給定的當前資料庫版本高於已有資料庫版本,調用該函數 System.out.println("upgrade a database"); } }
- SQLiteOpenHelper的應用
建立一個資料庫管理類DBManager
package com.example.notebook;import android.content.ContentValues;import android.content.Context;import android.database.Cursor;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteException;import android.util.Log;public class DBManager { private Context mContext = null; private SQLiteDatabase mSQLiteDatabase = null;//用於操作資料庫的對象 private DBHelper dh = null;//用於建立資料庫的對象 private String dbName = "note.db";//資料庫的名稱 private int dbVersion = 1;//資料庫的版本 public DBManager(Context context){ mContext = context; } public void open(){ try{ dh = new DBHelper(mContext, dbName, null, dbVersion);//建立資料庫 if(dh == null){ Log.v("msg", "is null"); return ; } mSQLiteDatabase = dh.getWritableDatabase();//以可寫方式開啟資料庫 //dh.onOpen(mSQLiteDatabase); }catch(SQLiteException se){ se.printStackTrace(); } }public void close(){ mSQLiteDatabase.close();//關閉資料庫 dh.close(); }public Cursor selectAll(){ Cursor cursor = null; try{ //sql語句操作 String sql = "select * from notebook"; cursor = mSQLiteDatabase.rawQuery(sql, null); }catch(Exception ex){ ex.printStackTrace(); cursor = null; } return cursor;}public Cursor selectById(int id){ //String result[] = {}; Cursor cursor = null; try{ //sql語句操作 String sql = "select * from notebook where _id=‘" + id +"‘"; cursor = mSQLiteDatabase.rawQuery(sql, null); }catch(Exception ex){ ex.printStackTrace(); cursor = null; } return cursor;}public long insert(String title, String content,String pic){ long datetime = System.currentTimeMillis(); long l = -1; try{ //結構化方式操作 ContentValues cv = new ContentValues(); cv.put("title", title); cv.put("content", content); cv.put("time", datetime); cv.put("pic", pic); l = mSQLiteDatabase.insert("notebook", null, cv); // Log.v("datetime", datetime+""+l); }catch(Exception ex){ ex.printStackTrace(); l = -1; } return l; }public int delete(int id){ int affect = 0; try{ //結構化方式操作 affect = mSQLiteDatabase.delete("notebook", "_id=?", new String[]{String.valueOf(id)}); }catch(Exception ex){ ex.printStackTrace(); affect = -1; } return affect;}public int update(int id, String title, String content,String pic){ int affect = 0; try{ //結構化方式操作 ContentValues cv = new ContentValues(); cv.put("title", title); cv.put("content", content); cv.put("pic", pic); String w[] = {String.valueOf(id)}; affect = mSQLiteDatabase.update("notebook", cv, "_id=?", w); }catch(Exception ex){ ex.printStackTrace(); affect = -1; } return affect;}}擷取資料樣本
private DBManager dm = null;// 資料庫管理對象
private Cursor cursor = null;
dm = new DBManager(this);//資料庫操作對象 dm.open();//開啟資料庫操作對象 cursor = dm.selectAll();//擷取所有資料 cursor.moveToFirst();//將遊標移動到第一條資料,使用前必須調用 int count = cursor.getCount();//個數 ArrayList<String> contents = new ArrayList<String>();//圖片的所有集合 ArrayList<String> imgs = new ArrayList<String>();//圖片的所有集合 ArrayList<String> items = new ArrayList<String>();//標題的所有集合 ArrayList<String> times = new ArrayList<String>();//時間的所有集合 for(int i= 0; i < count; i++){ contents.add(cursor.getString(cursor.getColumnIndex("content"))); imgs.add(cursor.getString(cursor.getColumnIndex("pic"))); items.add(cursor.getString(cursor.getColumnIndex("title"))); times.add(cursor.getString(cursor.getColumnIndex("time")));
//cursor.getInt(cursor.getColumnIndex("_id")) cursor.moveToNext();//將遊標指向下一個 } dm.close();//關閉資料操作對象
- 資料庫的並發問題
並發問題是使用資料庫過程中最容易碰到的問題,如果在開發中碰到了android.database.SQLException異常,並提示"database is locked",那很有可能是出現了資料庫的死結導致無法訪問。原因是Sqlite會對檔案的讀寫進行加鎖,防止資料被破壞。而在Android架構層SqliteDatabase會對所有資料庫物件進行加鎖保護,一旦出現了指向同一個資料庫的多個SqliteDatabase對象同時在多個線程中被使用,那就跳脫了SqliteDatabase鎖保護,就會導致資料庫出現被鎖的異常。因此在實踐中,需要保證同時訪問資料庫的SqliteDatabase對象僅有一個。(可以使用全域變數來儲存資料庫物件,在整個資料來源對象中使用同一個串連)
課外小知識:在Android SDK中提供了工具Sqlite3,在shell模式下,可以對資料庫進行增刪改查。
cmd->adb shell ->sqlite3 <路徑>/<資料庫名> ->sqlite > select * from sqmple;
二、Android資料的雲端服務
本質上而言,雲端儲存就是通過網路將行動裝置上的資料存放區到遠端伺服器上。在Android中,增加了一些協助工具功能,使得整個流程的實現變得更為簡單。首先是通過Google帳號來標識使用者身份。在android中,預設支援使用Google帳號作為使用者身份的標識,系統上各個應用都可以通過帳號系統獲得使用者的登入資訊。其次,有了Google帳號,使得開發人員不需要自行構建後台服務系統。
Android的雲端資料存取由系統服務BackupManagerService來統一管理。當應用提交備份資料請求時,BackupManagerService會將該請求放入備份隊列中,該隊列會按照一定的控制邏輯定時提交到雲端。當有新應用安裝到系統時,會觸發資料恢複事件,BackupManagerService會憑藉應用程式套件名和使用者帳號從雲端取出相應的備份資料,嘗試恢複。
在實踐中,Android會構造一個派生自BackupAgent類的子類android.app.backup.BackupAgentHelper的對象,來更方便地構建雲端儲存群組件。
import java.io.File;import java.io.IOException;import android.app.backup.BackupAgentHelper;import android.app.backup.BackupDataInput;import android.app.backup.BackupDataOutput;import android.app.backup.FileBackupHelper;import android.os.ParcelFileDescriptor;public class MyBackupAgent extends BackupAgentHelper { private static final String KEY = "my_backup"; @Override public void onCreate() { //構造檔案讀寫對象,聲明需要備份的檔案 FileBackupHelper helper = new FileBackupHelper(this,"backup_file"); addHelper(KEY,helper); super.onCreate(); } @Override public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) throws IOException { //調用父類方法,提交整個檔案到雲端 super.onBackup(oldState, data, newState); } @Override public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) throws IOException { // 調用父類方法,將從雲端擷取的檔案覆蓋本地檔案 super.onRestore(data, appVersionCode, newState); } @Override public void onRestoreFile(ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime) throws IOException { // TODO Auto-generated method stub super.onRestoreFile(data, size, destination, type, mode, mtime); }
Android不會自行將資料提交到雲端,開發人員需要顯性調用android.app.backup.BackupManager的dataChanged函數來觸發。
和所有組件一樣,雲端儲存群組件是由系統進行託管的。這就需要把組件的相關資訊放入設定檔中。
<application android:backupAgent = "MyBackupAgent" ...>
【Android的從零單排開發日記】——Android資料存放區(下)