- OpenGL ES 3.x游戲開發(上卷)
- 吳亞峰
- 4963字
- 2019-01-05 00:53:38
2.3 手機自帶數據庫——SQLite
上一節介紹了如何使用Preferences存儲簡單數據,而復雜的數據就需要存儲到文件或數據庫中了。Android自帶了一款輕量級的關系數據庫—SQLite,其具有體積小,功能強大等特點,成為嵌入式設備首選的數據庫系統。本節將帶領讀者走進SQLite的世界,學習如何應用SQLite數據庫進行數據的增、刪、改、查等基本操作。
2.3.1 初識SQLite
SQLite是一款滿足ACID特性的具有完備功能的關系數據庫系統,由于其設計目標為輕量級、開源、支持嵌入式使用,因此,目前已經在嵌入式設備領域被廣泛采用。其運行需要的系統資源非常少,在嵌入式設備中可能只需要幾百KB的內存就夠了。
SQLite對主流編程語言的支持也非常全面,如C#、PHP、Java等,同時還支持ODBC接口。另外,SQLite的性能也是一流的,在一般應用情況下,其處理速度比MySQL、PostgreSQL這兩款著名的開源數據庫管理系統都快。
提示
SQLite的最新版本為3.8.11.1,發布時間是2015年7月29日。其官方網站為:http://www.sqlite.org或者http://www.sqlite.com.cn,讀者可以在該網站上獲取SQLite的源代碼和相關文檔。
雖然SQLite占用的資源非常少,但是其功能、特性與服務器級數據庫相比卻絲毫不差,這也是SQLite能夠受到Android系統青睞的主要原因,其部分特性如下所列。
? 最大可以支持2TB的數據庫文件。
? 占用資源少,一般占用250KB左右。
? API非常簡單,易于使用。
? 沒有任何額外的依賴,是獨立的。
? 源代碼完全開放,可以用于任何用途。
Android系統中很多的用戶數據都存儲在SQLite數據庫中,如聯系人信息、通話記錄、短信等,由此可見SQLite對于Android的重要性。
提示
讀者要想很好地使用SQLite數據庫,必須熟練掌握SQL語言。這是由于SQL已經事實上成為關系數據庫操作的標準語言,市面上的關系數據庫幾乎無一例外都支持SQL。因此,在數據庫領域,有這樣一句話“學好SQL,走遍天下都不怕”。
2.3.2 SQLite數據庫的基本操作
一般學習數據庫相關課程的時候,首先介紹的就是數據庫的一些基本操作,如數據的增、刪、改、查等。按照慣例,本書也首先簡單介紹SQLite數據庫的創建、關閉及數據的增加、刪除、修改、查詢等基本操作,具體如下所列。
提示
想在Android下通過Java編程對SQLite數據庫進行操作,就必須要用到android.database.sqlite包下的SQLiteDatabase類,該類提供了對SQLite數據庫進行基本操作的所有重要方法。
? 創建數據庫。
創建數據庫需要用到的是openDatabase方法,此方法簽名為“public static SQLiteDatabase openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags)”。其中path為數據庫所在的路徑;factory為游標工廠;flags為標志,可以用來控制數據庫的訪問模式。
? 關閉數據庫。
關閉數據庫需要用到的是close方法,此方法簽名為“public void close()”。在實際開發中數據庫使用完畢后,一定不要忘記使用該方法關閉數據庫,以防止資源的浪費。
? 插入數據。
插入數據可以使用insert方法,此方法簽名為“public long insert(String table, String nullColumnHack, ContentValues values)”。其中table為待插入的表名,nullColumnHack通常設置為null, values為待插入的數據。
? 更新數據。
更新數據可以使用update方法,其簽名為“public int update(String table, ContentValues values, String whereClause, String[] whereArgs)”。其中table為待更新的表名;values為待更新內容;whereClause為where子句的內容,用來進行記錄篩選;whereArgs為where子句的參數。
? 刪除數據。
刪除數據可以使用delete方法,其簽名為“public int delete(String table, String whereClause, String[] whereArgs)”。其中table為要操作的表名;whereClause為where子句的內容,用來進行記錄篩選;whereArgs為where子句的參數。
? 查詢數據。
查詢數據可以使用query方法,其方法簽名為“public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)”。其中table為要查詢的表,columns為要查詢的列,selection為過濾記錄的子句,selectionArgs為過濾的參數值,groupBy為分組子句,having為過濾分組的子句,orderBy為記錄排序子句。
提示
Android中被重載了的query方法有多個變體。這里由于篇幅所限,不再贅述,有需要的讀者可以自行查閱API或其他相關資料。
? 執行非查詢SQL。
對于不太熟悉SQL語言的初學者而言,插入、更新、刪除數據可以用前面介紹的insert、update、delete方法。但對于熟練掌握SQL的開發人員而言,使用execSQL方法直接執行相應的SQL語句十分方便。
此方法簽名為“public void execSQL(String sql)”或“public void execSQL(String sql, Object[] bindArgs)”。其中sql為需要執行的SQL語句,bindArgs為帶參數SQL語句的參數值數組。
提示
需要注意的是,此方法僅支持執行非查詢的SQL語句,如CREATE TABLE、DELETE、 INSERT、UPDATE等,不能用于執行SELECT語句。
? 執行查詢SQL。
對于熟練掌握SQL的開發人員而言,會覺得前面介紹的query方法使用過于繁瑣。Android的設計人員也考慮到了這個問題,提供了支持執行SQL查詢語句的rawQuery方法,其方法簽名為“public Cursor rawQuery(String sql, String[] selectionArgs)”。其中sql為要執行的SQL查詢語句(可以帶參數), selectionArgs為查詢參數的值。
提示
SQLiteDatabase類中用于數據庫操作的方法還有很多,本書只是介紹了其中一些常用的,若讀者有需要可以查閱API或其他相關資料進一步學習。
2.3.3 SQLite數據庫的簡單案例
上一小節介紹了SQLite數據庫的基本操作方法,本小節將詳細介紹一個使用SQLite數據庫的簡單案例,以使讀者可以更加快速地掌握SQLite數據庫的使用方法,從而在開發中進行合理地使用。本案例運行效果分別如圖2-10、圖2-11和圖2-12所示。

▲圖2-10 創建數據庫

▲圖2-11 插入記錄

▲圖2-12 查詢記錄
介紹完本案例的運行效果后,接下來將開發本案例中唯一的一個類—Sample2_4_Activity,其代碼如下。
代碼位置:見隨書中源代碼/第2章/Sample2_4/src/com/bn/pp4目錄下的Sample2_4_Activity.java。
1 package com.bn.pp4; 2 ……//此處省略了部分類的引入代碼,讀者可自行查看隨書的源代碼 3 public class Sample2_4_Activity extends Activity { 4 SQLiteDatabase sld; // 聲明SQLiteDatabase引用 5 @Override 6 public void onCreate(Bundle savedInstanceState) { // onCreate方法 7 super.onCreate(savedInstanceState); 8 setContentView(R.layout.main); //跳轉到主界面 9 Button b = (Button) this.findViewById(R.id.Button01); //獲取打開/創建數據庫按鈕的引用 10 b.setOnClickListener( //為打開/創建按鈕添加監聽器 11 new OnClickListener() { 12 @Override 13 public void onClick(View v) { 14 createOrOpenDatabase(); //調用方法打開或創建數據庫 15 }}); 16 b = (Button) this.findViewById(R.id.Button02); //獲取關閉數據庫按鈕的引用 17 b.setOnClickListener( //為關閉按鈕添加監聽器 18 new OnClickListener() { 19 @Override 20 public void onClick(View v) { 21 closeDatabase(); //調用方法關閉數據庫 22 }}); 23 b = (Button) this.findViewById(R.id.Button03); //獲取添加記錄按鈕的引用 24 b.setOnClickListener( //為添加按鈕添加監聽器 25 new OnClickListener() { 26 @Override 27 public void onClick(View v) { 28 insert(); //調用方法插入記錄 29 }}); 30 b = (Button) this.findViewById(R.id.Button04); //獲取刪除記錄按鈕的引用 31 b.setOnClickListener( //為刪除按鈕添加監聽器 32 new OnClickListener() { 33 @Override 34 public void onClick(View v) { 35 delete(); //調用方法刪除記錄 36 }}); 37 b = (Button) this.findViewById(R.id.Button05); //獲取查詢記錄按鈕的引用 38 b.setOnClickListener( //為查詢按鈕添加監聽器 39 new OnClickListener() { 40 @Override 41 public void onClick(View v) { 42 query(); //調用方法查詢記錄 43 }}); } 44 public void createOrOpenDatabase() { //創建或打開數據庫的方法 45 try { 46 sld = SQLiteDatabase.openDatabase( 47 "/data/data/com.bn.pp4/mydb", //數據庫所在路徑 48 null, //游標工廠,默認為null 49 SQLiteDatabase.OPEN_READWRITE | 50 SQLiteDatabase.CREATE_IF_NECESSARY //模式為讀寫,若不存在則創建 51 ); //生成創建數據庫的SQL語句 52 String sql = "create table if not exists student" + 53 "(sno char(5), stuname varchar(20), " + 54 "sage integer, sclass char(5))"; 55 sld.execSQL(sql); //執行SQL語句 56 Toast.makeText(getBaseContext(), "成功創建數據庫。", 57 Toast.LENGTH_LONG).show(); 58 } catch (Exception e) { 59 e.printStackTrace(); 60 }} 61 public void closeDatabase() { //關閉數據庫的方法 62 try { 63 sld.close(); //關閉數據庫 64 Toast.makeText(getBaseContext(), "成功關閉數據庫。", 65 Toast.LENGTH_LONG).show(); 66 } catch (Exception e) { 67 e.printStackTrace(); 68 }} 69 public void insert() { //插入記錄的方法 70 try { //生成插入記錄的SQL語句 71 String sql = "insert into student values" + 72 "('001', 'Android',22, '283')"; 73 sld.execSQL(sql); //執行SQL語句 74 Toast.makeText(getBaseContext(), "成功插入一條記錄。", 75 Toast.LENGTH_LONG).show(); 76 } catch (Exception e) { 77 e.printStackTrace(); 78 }} 79 public void delete() { //刪除記錄的方法 80 try { //生成刪除所有記錄的SQL語句 81 String sql = "delete from student; "; 82 sld.execSQL(sql); //執行SQL語句 83 Toast.makeText(getBaseContext(), "成功刪除所有記錄。", 84 Toast.LENGTH_LONG).show(); 85 } catch (Exception e) { 86 e.printStackTrace(); 87 }} 88 public void query(){ //查詢的方法 89 try { //生成查詢記錄的SQL語句 90 String sql = "select * from student where sage>? "; 91 Cursor cur = sld.rawQuery(sql, new String[] { "20" }); //獲取Cursor對象引用 92 while (cur.moveToNext()) { //若存在記錄 93 String sno = cur.getString(0); //獲取第一列信息 94 String sname = cur.getString(1); //獲取第二列信息 95 int sage = cur.getInt(2); //獲取第三列信息 96 String sclass = cur.getString(3); //獲取第四列信息 97 Toast.makeText( 98 getBaseContext(), 99 "查詢到的記錄為:'" + sno + "'\t'" + sname 100 + "'\t\t'" + sage+ "'\t'" + sclass + "'", 101 Toast.LENGTH_LONG).show(); 102 } 103 cur.close(); //關閉Cursor 104 } catch (Exception e) { 105 e.printStackTrace(); 106 }}}
? 第9-43行為案例中的各個按鈕添加監聽器,監聽器中調用對應的方法來實現數據庫的打開/創建、關閉、插入、刪除和查詢等操作。
? 第43-60行為創建及打開數據庫的方法,方法中首先獲取了SQLiteDatabase對象的引用,并為其指定數據庫的存儲路徑和讀寫模式,然后用“create table”語句創建了一張名稱為student的表。
? 第69-78行為向數據庫中插入一條記錄的方法,插入的記錄內容為“'001', 'Android',22, '283'”。
? 第79-87行為刪除數據庫中所有記錄的方法。
? 第88-106行為從數據庫中查找符合條件記錄的方法,首先需要獲取Cursor對象的引用,并為其添加查找范圍(具體范圍為年齡大于20)。若查到相應記錄,則將該記錄信息用Toast顯示出來。
2.3.4 使用ContentProvider組件共享數據
前一小節介紹了SQLite數據庫中的一些操作,但有時數據庫中的信息不但創建其的應用程序要使用,還希望能夠分享給其他應用程序使用。這時就需要使用ContentProvider組件了,ContentProvider組件的基本情況如下所列。
? Android平臺中每個應用程序都有自己的用戶ID并在自己的進程中運行,每個進程擁有獨立的運行環境,這樣可以保證程序的完整性。但這也使得應用程序在需要進行資源共享和數據通信時很不方便。為了解決這一問題,Android提供了專門用來在應用程序之間分享數據的ContentProvider組件。
? ContentProvider能將應用程序中特定的數據提供給其他應用程序使用,這些數據可以來自應用程序私有文件夾下的私有數據文件,也可以來自應用程序自己私有的SQLite數據庫。當然,數據的來源還有很多其他選擇,ContentProvider組件本身并沒有做出限制,讀者可以充分發揮想象的空間。
? 使用ContentProvider組件共享數據的基本方式是繼承ContentProvider類并重寫其中的相應方法,具體情況在后面的案例中進行介紹。
? 別的應用程序想分享數據時需要使用ContentResolver,通過ContentResolver對象將需要分享數據的請求發送給ContentProvider組件,而不能直接調用ContentProvider組件。
下面使用ContentProvider組件將上一小節的案例進行升級,使得此案例具有分享數據給其他應用程序的能力,其具體開發步驟如下。
(1)在案例Sample2_4的com/bn/pp4包下創建MyContentProvider類,該類繼承自ContentProvider類,并要實現其中所有的抽象方法,具體代碼如下。
代碼位置:見隨書中源代碼/第2章/Sample2_4/src/com/bn/pp4目錄下的MyContentProvider.java。
1 package com.bn.pp4; 2 ……//此處省略了部分類的引入代碼,讀者可自行查看隨書的源代碼 3 public class MyContentProvider extends ContentProvider { //繼承ContentProvider 4 private static final UriMatcher um; //聲明Uri匹配引用 5 static { 6 um = new UriMatcher(UriMatcher.NO_MATCH); //創建UriMatcher 7 um.addURI("com.bn.pp4.provider.student", "stu", 1); //設置匹配字符串 8 } 9 SQLiteDatabase sld; //聲明SQLiteDatabase引用 10 @Override 11 public String getType(Uri uri) { 12 return null; 13 } 14 @Override //調用數據庫的query方法時會自動調用該方法 15 public Cursor query(Uri uri, String[] projection, String selection, 16 String[] selectionArgs, String sortOrder) { 17 switch (um.match(uri)) { //若匹配成功 18 case 1: //執行操作,獲取Cursor對象引用 19 Cursor cur = sld.query("student", projection, selection, 20 selectionArgs, null, null, sortOrder); 21 return cur; //返回Cursor對象引用 22 } 23 return null; 24 } 25 @Override 26 public int delete(Uri arg0, String arg1, String[] arg2) { //空實現 27 return 0; 28 } 29 @Override 30 public Uri insert(Uri uri, ContentValues values) { //空實現 31 return null; 32 } 33 @Override 34 public boolean onCreate() { //創建數據庫時自動調用該方法 35 sld = SQLiteDatabase.openDatabase( 36 "/data/data/com.bn.pp4/mydb", //數據庫所在路徑 37 null, //游標工廠,默認為null 38 SQLiteDatabase.OPEN_READWRITE| 39 SQLiteDatabase.CREATE_IF_NECESSARY //讀寫、若不存在則創建 40 ); 41 return false; 42 } 43 @Override 44 public int update(Uri uri, ContentValues values, String selection, 45 String[] selectionArgs) { //空實現 46 return 0; 47 }}
? 第4-8行為聲明Uri匹配對象,并且設置匹配字符串。此匹配字符串在需要得到分享數據的應用程序中提供給ContentResolver使用,以進行配對。
? 第11-13行重寫了getType方法,本案例中對getType方法沒有要求,因此其返回空值。
? 第15-24行重寫了query方法,在匹配成功后,數據需求方通過ContentResolver調用此方法查詢需要的數據。
? 第26-32行重寫了delete與insert方法,本案例中對這兩個方法沒有要求,因此都設置為返回空值。
? 第34-42行重寫了onCreate方法,其功能為首先獲取SQLiteDatabase對象引用,然后創建或打開數據庫,為信息的分享做好準備。
? 第44-47行重寫了update方法,本案例中對這個方法沒有要求,因此設置為返回空值。
(2)僅僅是完成上面的代碼還是不夠的,在Android程序開發中,有一個很重要的配置文件AndroidManifest.xml。要想使用ContentProvider組件,在完成代碼的開發后,還必須在該配置文件中進行相應的配置,將如下代碼插入到AndroidManifest.xml文件中的application標簽中。
代碼位置:見隨書中源代碼/第2章/Sample2_4目錄下的AndroidManifest.xml。
1 <provider 2 android:name="MyContentProvider" <! --將調用的類名--> 3 android:authorities="com/bn/pp4.provider.student" <! --要匹配的Uri字符串--> 4 android:exported="true"/>
2.3.5 使用ContentResolver獲取分享數據
升級完了Sample2_4使其具有了數據分享能力之后,就可以在別的應用程序中通過ContentResolver匹配到Sample2_4案例中的ContentProvider組件獲取分享的數據了。具體的開發步驟如下所列。
(1)創建項目Sample2_4_From,將項目的包名設定為com.bn.pp4f,并創建一個繼承自Activity的類ContentConsumerActivity,其代碼如下。
代碼位置:見隨書中源代碼/第2章/ Sample2_4_From/src/com/bn/pp4f目錄下的Content ConsumerActivity.java。
1 package com.bn.pp4f; //包聲明 2 import android.app.Activity; //相關類的引入 3 //……此處省略了部分相關類的引入代碼,讀者可自行查看隨書的源代碼 4 import android.widget.EditText; //相關類的引入 5 public class ContentConsumerActivity extends Activity { 6 ContentResolver cr; // ContentResolver的引用 7 @Override //重寫方法的標志 8 public void onCreate(Bundle savedInstanceState) { 9 super.onCreate(savedInstanceState); //繼承父類的onCreate方法 10 setContentView(R.layout.main); //跳轉到主界面 11 cr=this.getContentResolver(); //獲取ContentResolver的對象 12 //初始化查詢按鈕 13 Button b=(Button)this.findViewById(R.id.Button01); //Button類的引用 14 b.setOnClickListener( //設置按鈕監聽 15 new OnClickListener(){ 16 @Override //重寫方法的標志 17 public void onClick(View v) { //重寫onClick方法 18 String stuname="Android"; //設置查詢的字符串 19 Cursor cur=cr.query( 20 Uri.parse("content://com.bn.pp4.provider.student/stu"), 21 new String[]{"sno", "stuname", "sage", "sclass"}, 22 "stuname=? ", //查詢條件 23 new String[]{stuname}, 24 "sage ASC" 25 ); 26 while(cur.moveToNext()){ 27 String sno=cur.getString(0); //獲取學號 28 String sname=cur.getString(1); //獲取名稱 29 int sage=cur.getInt(2); //獲取年齡 30 String sclass=cur.getString(3); //獲取班級 31 appendMessage(sno+"\t"+sname+"\t\t"+sage+"\t"+sclass); 32 } 33 cur.close(); //關閉ContentResolver 34 }}); } 35 public void appendMessage(String msg){ //向文本區中添加文本 36 EditText et=(EditText)this.findViewById(R.id.EditText02); //獲取EditText的對象 37 et.append(msg+"\n"); //添加顯示的字符串 38 }}
? 第8-25行主要功能為獲取ContentResolver對象的引用,并給按鈕添加監聽器,使得按鈕按下后可以通過ContentResolver匹配到Sample2_4案例中的ContentProvider組件獲取需要的數據。
? 第26-33行功能為將獲取的Sample2_4案例分享的數據顯示到屏幕上的EditText控件中。
? 第35-38行為向EditText控件中添加文本信息的方法。
(2)Sample2_4_From案例開發完成后,運行該案例,其效果如圖2-13和圖2-14所示。

▲圖2-13 運行界面1

▲圖2-14 運行界面2
說明
圖2-13為運行該案例后的界面效果圖,圖2-14為單擊“獲取”按鈕后,通過ContentResolver匹配到Sample2_4案例中的ContentProvider組件獲取數據后的效果圖。