官术网_书友最值得收藏!

4.2 數據庫SQLite

本節介紹Android的數據庫存儲方式—— SQLite的使用方法,包括如何建表和刪表、變更表結構以及對表數據進行增加、刪除、修改、查詢等操作,然后通過SQLite結合“登錄App”項目改進記住密碼功能。

4.2.1 SQLite的基本用法

SQLite是一個小巧的嵌入式數據庫,使用方便、開發簡單,手機上最早由iOS運用,后來Android也采用了SQLite。SQLite的多數sql語法與Oracle一樣,下面只列出不同的地方:

(1)建表時為避免重復操作,應加上IF NOT EXISTS關鍵詞,例如CREATE TABLE IF NOT EXISTS table_name。

(2)刪表時為避免重復操作,應加上IF EXISTS關鍵詞,例如DROP TABLE IF EXISTS table_name。

(3)添加新列時使用ALTER TABLE table_name ADD COLUMN ...,注意比Oracle多了一個COLUMN關鍵字。

(4)在SQLite中,ALTER語句每次只能添加一列,如果要添加多列,就只能分多次添加。

(5)SQLite支持整型INTEGER、字符串VARCHAR、浮點數FLOAT,但不支持布爾類型。布爾類型數要使用整型保存,如果直接保存布爾數據,在入庫時SQLite就會自動將其轉為0或1,0表示false,1表示true。

(6)SQLite建表時需要一個唯一標識字段,字段名為_id。每建一張新表都要例行公事加上該字段定義,具體屬性定義為_idINTEGER PRIMARY KEYAUTOINCREMENT NOT NULL。

(7)條件語句等號后面的字符串值要用單引號括起來,如果沒用使用單引號括起來,在運行時就會報錯。

SQLiteDatabase是SQLite的數據庫管理類,我們可以在活動頁面代碼或任何能取到Context的地方獲取數據庫實例,參考代碼如下:

            //創建數據庫,如果已存在就打開
            SQLiteDatabase db = getApplicationContext().openOrCreateDatabase("test.db",
    Context.MODE_PRIVATE, null);
            //刪除數據庫
            getApplicationContext().deleteDatabase("test.db");

SQLiteDatabase提供了若干操作數據表的API,常用的方法有3類,列舉如下:

1.管理類,用于數據庫層面的操作。

● openDatabase:打開指定路徑的數據庫。

● isOpen:判斷數據庫是否已打開。

● close:關閉數據庫。

● getVersion:獲取數據庫的版本號。

● setVersion:設置數據庫的版本號。

2.事務類,用于事務層面的操作。

● beginTransaction:開始事務。

● setTransactionSuccessful:設置事務的成功標志。

● endTransaction:結束事務。執行本方法時,系統會判斷是否已執行setTransactionSuccessful,如果之前已設置就提交,如果沒有設置就回滾。

3.數據處理類,用于數據表層面的操作。

● execSQL:執行拼接好的SQL控制語句。一般用于建表、刪表、變更表結構。

● delete:刪除符合條件的記錄。

● update:更新符合條件的記錄。

● insert:插入一條記錄。

● query:執行查詢操作,返回結果集的游標。

● rawQuery:執行拼接好的SQL查詢語句,返回結果集的游標。

4.2.2 SQLiteOpenHelper

SQLiteDatabase存在局限性,例如必須小心、不能重復地打開數據庫,處理數據庫的升級很不方便。Android提供了一個輔助工具—— SQLiteOpenHelper,用于指導我們進行SQLite的合理使用。

SQLiteOpenHelper的具體使用步驟如下:

步驟01 新建一個繼承自SQLiteOpenHelper的數據庫操作類,提示重寫onCreate和onUpgrade兩個方法。其中,onCreate方法只在第一次打開數據庫時執行,在此可進行表結構創建的操作;onUpgrade方法在數據庫版本升高時執行,因此我們可以在onUpgrade函數內部根據新舊版本號進行表結構變更處理。

步驟02 封裝保證數據庫安全的必要方法,包括獲取單例對象、打開數據庫連接、關閉數據庫連接。

● 獲取單例對象:確保App運行時數據庫只被打開一次,避免重復打開引起錯誤。

● 打開數據庫連接:SQLite有鎖機制,即讀鎖和寫鎖的處理;故而數據庫連接也分兩種,讀連接可調用SQLiteOpenHelper的getReadableDatabase方法獲得,寫連接可調用getWritableDatabase獲得。

● 關閉數據庫連接:數據庫操作完畢后,應當調用SQLiteDatabase對象的close方法關閉連接。

步驟03 提供對表記錄進行增加、刪除、修改、查詢的操作方法。

可被SQLite直接使用的數據結構是ContentValues類,類似于映射Map,提供put和get方法用來存取鍵值對。區別之處在于ContentValues的鍵只能是字符串,查看ContentValues的源碼會發現其內部保存鍵值對的數據結構就是HashMap“privateHashMap<String, Object>mValues; ”。ContentValues主要用于記錄增加和更新操作,即SQLiteDatabase的insert和update方法。

對于查詢操作來說,使用的是另一個游標類Cursor。調用SQLiteDatabase的query和rawQuery方法時,返回的都是Cursor對象,因此獲取查詢結果要根據游標的指示一條一條遍歷結果集合。Cursor的常用方法可分為3類,說明如下:

1.游標控制類方法,用于指定游標的狀態。

● close:關閉游標。

● isClosed:判斷游標是否關閉。

● isFirst:判斷游標是否在開頭。

● isLast:判斷游標是否在末尾。

2.游標移動類方法,把游標移動到指定位置。

● moveToFirst:移動游標到開頭。

● moveToLast:移動游標到末尾。

● moveToNext:移動游標到下一條記錄。

● moveToPrevious:移動游標到上一條記錄。

● move:往后移動游標若干條記錄。

● moveToPosition:移動游標到指定位置的記錄。

3.獲取記錄類方法,可獲取記錄的數量、類型以及取值。

● getCount:獲取結果記錄的數量。

● getInt:獲取指定字段的整型值。

● getFloat:獲取指定字段的浮點數值。

● getString:獲取指定字段的字符串值。

● getType:獲取指定字段的字段類型。

鑒于數據庫操作的特殊性,不方便單獨演示某個功能,接下來從創建數據庫開始介紹,完整演示一下數據庫的讀寫操作。如圖4-5和圖4-6所示,在頁面上分別錄入兩個用戶的注冊信息并保存到SQLite。從SQLite讀取用戶注冊信息并展示在頁面上,如圖4-7所示。

圖4-5 第一條注冊信息保存到數據庫

圖4-6 第二條注冊信息保存到數據庫

圖4-7 從SQLite中讀取兩條注冊記錄

下面是用戶注冊信息數據庫的SQLiteOpenHelper操作類的完整代碼:

        public class UserDBHelper extends SQLiteOpenHelper {
            private static final String TAG = "UserDBHelper";
            private static final String DB_NAME = "user.db";
            private static final int DB_VERSION=1;
            private static UserDBHelper mHelper=null;
            private SQLiteDatabase mDB=null;
            private static final String TABLE_NAME="user_info";


            private UserDBHelper(Context context){
                super(context, DB_NAME, null, DB_VERSION);
            }


            private UserDBHelper(Context context, int version){
                super(context, DB_NAME, null, version);
            }


            public static UserDBHelper getInstance(Context context, int version){
                if(version>0&&mHelper==null){
                    mHelper=new UserDBHelper(context, version);
                }else if(mHelper==null){
                    mHelper=new UserDBHelper(context);
                }
                return mHelper;
            }


            public SQLiteDatabase openReadLink(){
                if(mDB==null||mDB.isOpen()! =true){
                    mDB=mHelper.getReadableDatabase();
                }
                return mDB;
            }


            public SQLiteDatabase openWriteLink(){
                if(mDB==null||mDB.isOpen()! =true){
                    mDB=mHelper.getWritableDatabase();
                }
                return mDB;
            }


            public void closeLink(){
                if(mDB! =null&&mDB.isOpen()==true){
                    mDB.close();
                    mDB=null;
                }
            }


            public String getDBName(){
                if(mHelper! =null){
                    return mHelper.getDatabaseName();
                }else{
                    return DB_NAME;
                }
            }


            @Override
            public void onCreate(SQLiteDatabase db){
                Log.d(TAG, "onCreate");
                String drop_sql="DROP TABLE IF EXISTS"+TABLE_NAME+"; ";
                db.execSQL(drop_sql);
                String create_sql="CREATE TABLE IF NOT EXISTS"+TABLE_NAME+"("
                        +"_id INTEGER PRIMARY KEY  AUTOINCREMENT NOT NULL, "
                        +"name VARCHAR NOT NULL, "+"age INTEGER NOT NULL, "
                        +"height LONG NOT NULL, "+"weight FLOAT NOT NULL, "
                        +"married INTEGER NOT NULL, "+"update_time VARCHAR NOT NULL"
                        //演示數據庫升級時要先注釋下面這行代碼
                        +", phone VARCHAR"+", password VARCHAR"+"); ";
                Log.d(TAG, "create_sql:"+create_sql);
                db.execSQL(create_sql);
            }


            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
                Log.d(TAG, "onUpgrade oldVersion="+oldVersion+", newVersion="+newVersion);
                if(newVersion>1){
                    //Android的ALTER命令不支持一次添加多列,只能分多次添加
                    String alter_sql="ALTER TABLE"+TABLE_NAME+"ADD COLUMN"+"phone
    VARCHAR; ";
                    Log.d(TAG, "alter_sql:"+alter_sql);
                    db.execSQL(alter_sql);
                    alter_sql="ALTER TABLE"+TABLE_NAME+"ADD COLUMN"+"password
    VARCHAR; ";
                    Log.d(TAG, "alter_sql:"+alter_sql);
                    db.execSQL(alter_sql);
                }
            }


            public int delete(String condition){
                int count=mDB.delete(TABLE_NAME, condition, null);
                return count;
            }


            public int deleteAll(){
                int count=mDB.delete(TABLE_NAME, "1=1", null);
                return count;
            }


            public long insert(UserInfo info){
                ArrayList<UserInfo>infoArray=new ArrayList<UserInfo>();
                infoArray.add(info);
                return insert(infoArray);
            }


            public long insert(ArrayList<UserInfo>infoArray){
                long result=-1;
                for(int i=0; i<infoArray.size(); i++){
                    UserInfo info=infoArray.get(i);
                    ArrayList<UserInfo>tempArray=new ArrayList<UserInfo>();
                    // 如果存在同名記錄,就更新記錄。注意條件語句的等號后面要用單引號括起來
                    if(info.name! =null&&info.name.length()>0){
                        String condition=String.format("name='%s'", info.name);
                        tempArray=query(condition);
                        if(tempArray.size()>0){
                            update(info, condition);
                            result=tempArray.get(0).rowid;
                            continue;
                        }
                    }
                    // 如果存在同樣的手機號碼,就更新記錄
                    if(info.phone! =null&&info.phone.length()>0){
                        String condition=String.format("phone='%s'", info.phone);
                        tempArray=query(condition);
                        if(tempArray.size()>0){
                            update(info, condition);
                            result=tempArray.get(0).rowid;
                            continue;
                        }
                    }
                    // 如果不存在唯一性重復的記錄,就插入新記錄
                    ContentValues cv=new ContentValues();
                    cv.put("name", info.name);
                    cv.put("age", info.age);
                    cv.put("height", info.height);
                    cv.put("weight", info.weight);
                    cv.put("married", info.married);
                    cv.put("update_time", info.update_time);
                    cv.put("phone", info.phone);
                    cv.put("password", info.password);
                    result=mDB.insert(TABLE_NAME, "", cv);
                    // 添加成功后返回行號,失敗則返回-1
                    if(result==-1){
                        return result;
                    }
                }
                return result;
            }


            public int update(UserInfo info, String condition){
                ContentValues cv=new ContentValues();
                cv.put("name", info.name);
                cv.put("age", info.age);
                cv.put("height", info.height);
                cv.put("weight", info.weight);
                cv.put("married", info.married);
                cv.put("update_time", info.update_time);
                cv.put("phone", info.phone);
                cv.put("password", info.password);
                int count=mDB.update(TABLE_NAME, cv, condition, null);
                return count;
            }


            public int update(UserInfo info){
                return update(info, "rowid="+info.rowid);
            }


            public ArrayList<UserInfo>query(String condition){
                String sql=String.format("select rowid, _id, name, age, height, weight, married, update_time, "+
                        "phone, password from %s where %s; ", TABLE_NAME, condition);
                Log.d(TAG, "query sql:"+sql);
                ArrayList<UserInfo>infoArray=new ArrayList<UserInfo>();
                Cursor cursor=mDB.rawQuery(sql, null);
                if(cursor.moveToFirst()){
                    for(; ; cursor.moveToNext()){
                        UserInfo info=new UserInfo();
                        info.rowid=cursor.getLong(0);
                        info.xuhao=cursor.getInt(1);
                        info.name = cursor.getString(2);
                        info.age = cursor.getInt(3);
                        info.height = cursor.getLong(4);
                        info.weight = cursor.getFloat(5);
                        //SQLite沒有布爾型,用0表示false,用1表示true
                        info.married = (cursor.getInt(6)==0)? false:true;
                        info.update_time = cursor.getString(7);
                        info.phone = cursor.getString(8);
                        info.password = cursor.getString(9);
                        infoArray.add(info);
                        if (cursor.isLast() == true) {
                            break;
                        }
                    }
                }
                cursor.close();
                return infoArray;
            }


            public UserInfo queryByPhone(String phone) {
                UserInfo info = null;
                ArrayList<UserInfo> infoArray = query(String.format("phone='%s'", phone));
                if (infoArray.size() > 0) {
                    info = infoArray.get(0);
                }
                return info;
            }
        }

4.2.3 優化記住密碼功能

在“4.1.2實現記住密碼功能”中,我們利用共享參數實現了記住密碼的功能,不過這個方法有局限,只能記住一個用戶的登錄信息,并且手機號碼跟密碼不存在從屬關系,如果換個手機號碼登錄,前一個用戶的登錄信息就被覆蓋了。真正意義上的記住密碼功能是先輸入手機號碼,然后根據手機號匹配保存的密碼,一個密碼對應一個手機號碼,從而實現具體手機號碼的密碼記憶功能。

現在我們運用SQLite技術分條存儲不同用戶的登錄信息,并提供根據手機號碼查找登錄信息的方法,這樣可以同時記住多個手機號碼的密碼。具體的改造主要有以下3點:

(1)聲明一個UserDBHelper對象,然后在活動頁面的onResume方法中打開數據庫連接,在onPasue方法中關閉數據庫連接,示例代碼如下:

            @Override
            protected void onResume() {
                super.onResume();
                mHelper = UserDBHelper.getInstance(this, 2);
                mHelper.openWriteLink();
            }


            @Override
            protected void onPause() {
                super.onPause();
                mHelper.closeLink();
            }

(2)登錄成功時,如果用戶勾選了“記住密碼”,就使用數據庫保存手機號碼與密碼在內的登錄信息。在loginSuccess函數中增加如下代碼:

                if (bRemember) {
                    UserInfo info = new UserInfo();
                    info.phone = et_phone.getText().toString();
                    info.password = et_password.getText().toString();
                    info.update_time = DateUtil.getNowDateTime("yyyy-MM-dd HH:mm:ss");
                    mHelper.insert(info);
                }

(3)再次打開登錄頁面,用戶輸入手機號完畢后點擊密碼輸入框時,App到數據庫中根據手機號查找登錄記錄,并將記錄結果中的密碼填入密碼框。

看到這里,讀者也許已經想到給密碼框注冊點擊事件,然后在onClick方法中補充數據庫讀取操作。可是EditText比較特殊,點擊后只是讓其獲得焦點,再次點擊才會觸發點擊事件。也就是說,要連續點擊兩次EditText才會處理點擊事件。Android有時就是這么調皮搗蛋,你讓它往東,它偏偏往西。難不成叫用戶將就一下點擊兩次?用戶肯定覺得這個App古怪、難用,還是卸載好了……這里提供一個解決辦法,先給密碼框注冊一個焦點變更監聽器,比如下面這行代碼:

                et_password.setOnFocusChangeListener(this);

這個焦點變更監聽器要實現接口OnFocusChangeListener,對應的事件處理方法是onFocusChange,將數據庫查詢操作放在該方法中,詳細代碼如下:

            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                String phone = et_phone.getText().toString();
                if (v.getId() == R.id.et_password) {
                    if (phone.length() > 0 && hasFocus == true) {
                        UserInfo info = mHelper.queryByPhone(phone);
                        if (info ! = null) {
                            et_password.setText(info.password);
                        }
                    }
                }
            }

這樣,就不再需要點擊兩次才處理點擊事件了。

代碼寫完后,再來看登錄頁面的效果圖,用戶上次登錄成功時已勾選“記住密碼”,現在再次進入登錄頁面,用戶輸入手機號后光標還停留在手機框,如圖4-8所示。接著點擊密碼框,光標隨之跳到密碼框,這時密碼框自動填入了該手機號對應的密碼串,如圖4-9所示。如此便真正實現了記住密碼功能。

圖4-8 光標在手機號碼框

圖4-9 光標在密碼輸入框

主站蜘蛛池模板: 云南省| 潞城市| 安徽省| 龙门县| 赤水市| 玉门市| 桑日县| 马关县| 天全县| 大渡口区| 江达县| 惠水县| 攀枝花市| 惠水县| 定陶县| 永吉县| 太原市| 宝鸡市| 英德市| 扬中市| 西藏| 威远县| 龙海市| 武鸣县| 廊坊市| 广南县| 新巴尔虎左旗| 获嘉县| 邯郸县| 景泰县| 桓台县| 松潘县| 万山特区| 边坝县| 镇康县| 余庆县| 汉寿县| 清涧县| 鄂托克旗| 新安县| 墨竹工卡县|