【Android開發日記】之入門篇(八)——Android數據存儲(下)

廢話很少說了,緊接着來說數據庫的操做吧。Come On!
提到數據存儲問題,數據庫是不得不提的。數據庫是用來存儲關係型數據的不二利器。Android爲開發者提供了強大的數據庫支持,能夠用來輕鬆地構造基於數據庫的應用。Android的數據庫應用,依託於當下最流行的開源嵌入式數據庫SQLite。在Android中,應用的數據庫文件是該應用私有的,存儲在應用數據目錄下的databases子目錄內。從代碼結構來看,Android的數據庫實現能夠分紅兩個層次,在底層經過C++調用SQLite的接口來執行SQL語句,並經過JNI向上暴露Java可訪問的接口。

1、Android數據庫使用

Android中使用android.database.sqlite.SQLiteDatabase來表示一個數據庫對象,它提供了兩種模式來幫助開發者進行增刪改查等基本數據庫操做。java

  1. 利用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語言做比較的話,若是寫過對文件進行處理的程序,那麼遊標就像您打開文件所獲得的文件句柄同樣,只要文件打開成功,該文件句柄就可表明該文件。總之記住,遊標是一塊有着特有記號的一塊數據區域,可以讓用戶逐條從中讀取出數據。
  2. 結構化的方式描述數據庫的操做
    這樣即便咱們不熟悉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語句爲
    android

    SELECT CustomerName, SUM(OrderPrice) FROM Orders WHERE Country=?   
        GROUP BY CustomerName   
        HAVING SUM(OrderPrice)>500  
        ORDER BY CustomerName  

    那麼我寫的代碼以下sql

    //數據表名
    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);  

    這樣就能實現數據庫的查詢了。其它的語句參數都是差很少的,這裏就不一一介紹了。
    shell

    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,那麼張三組就不會顯示。
    數據庫

  3. SQL語句的預編譯
    在實踐中,有的SQL語句須要被反覆使用,爲了不反覆解析SQL語句產生的開銷,能夠對須要複用的SQL語句進行預編譯,來提升數據庫操做的執行效率。
    //編譯複雜的SQL語句
    SQLiteStatement compiledSql = db.compileStatement(aSQL);
    //執行SQL
    compiledSql.execute();

    除此之外,Android還提供了豐富的高級數據庫功能,好比支持觸發器、支持複合索引以及支持對數據庫事務的處理。
    數組

         try{
                db.beginTransaction();
                //執行相關的數據庫操做,若有異常,直接進入finally部分。
            }finally{
                //不論成功都要調用endTransaction來結束事務
                db.endTransaction();
            }

    課外小知識:所謂事務是用戶定義的一個數據庫操做序列,這些操做要麼全作要麼全不作,是一個不可分割的工做單位。例如,在關係數據庫中,一個事務能夠是一條SQL語句、一組SQL語句或整個程序。 簡單舉個例子就是你要同時修改數據庫中兩個不一樣表的時候,若是它們不是一個事務的話,當第一個表修改完,但是第二表改修出現了異常而沒能修改的狀況下,就只有第二個表回到未修改以前的狀態,而第一個表已經被修改完畢。 而當你把它們設定爲一個事務的時候,當第一個表修改完,但是第二表改修出現了異常而沒能修改的狀況下,第一個表和第二個表都要回到未修改的狀態!這就是所謂的事務回滾。服務器

  4. 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");
        }  
    
    }

  5. 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();//關閉數據操做對象
  6. 數據庫的併發問題
    併發問題是使用數據庫過程當中最容易碰到的問題,若是在開發中碰到了android.database.SQLException異常,並提示"database is locked",那頗有多是出現了數據庫的死鎖致使沒法訪問。緣由是Sqlite會對文件的讀寫進行加鎖,防止數據被破壞。而在Android框架層SqliteDatabase會對全部數據庫對象進行加鎖保護,一旦出現了指向同一個數據庫的多個SqliteDatabase對象同時在多個線程中被使用,那就跳脫了SqliteDatabase鎖保護,就會致使數據庫出現被鎖的異常。所以在實踐中,須要保證同時訪問數據庫的SqliteDatabase對象僅有一個。(可使用全局變量來保存數據庫對象,在整個數據源對象中使用同一個鏈接)
    課外小知識:在Android SDK中提供了工具Sqlite3,在shell模式下,能夠對數據庫進行增刪改查。   
    cmd->adb shell ->sqlite3 <路徑>/<數據庫名> ->sqlite > select * from sqmple; 

2、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" 
...>

 

到這裏,數據的部分就講解的差很少了,剩下的就要回到咱們的數據源組件ContentProvider了。
數據這一塊對於開發者來講相當重要,怎樣使用,如何作最有效率,這些問題也是咱們技術員要一直研究的重點。
 
參考文章:Android 的Backup服務管理機制與架構分析     http://blog.csdn.net/goohong/article/details/8026045
資源下載:(數據庫+文件)demo
 
 ========================================
做者:cpacm
出處:(http://www.cpacm.net/2015/03/22/Android開發日記(六)——Android數據存儲(下)/
相關文章
相關標籤/搜索