李華明Himi 原創,轉載務必在明顯處註明:
轉載自【黑米GameDev街區】 原文連結: http://www.himigame.com/android-game/329.html
很多童鞋說My Code運行後,點擊home或者back後會程式異常,如果你也這樣遇到過,那麼你肯定沒有仔細讀完Himi的博文,第十九篇Himi專門寫了關於這些錯誤的原因和解決方案,這裡我在部落格都補充說明下,省的童鞋們總疑惑這一塊;請點擊下面聯絡進入閱讀:
【Android遊戲開發十九】(必看篇)SurfaceView運行機制詳解—剖析Back與Home按鍵及切入後台等異常處理!
上一篇跟各位童鞋介紹了SharedPreference 和 File流如何儲存資料,並且推薦使用FileOutputStream/FileInputStream來儲存咱們遊戲資料,那麼這一篇則是像大家介紹另外一種適合遊戲資料存放區的方式:SQLite 輕量級資料庫!
先介紹幾個基本概念知識:
什麼是SQLite:
SQLite是一款輕量級資料庫,它的設計目的是嵌入式,而且它佔用的資源非常少,在嵌入式裝置中,只需要幾百KB!!!!!
SQLite的特性:
- 輕量級
使用 SQLite 只需要帶一個動態庫,就可以享受它的全部功能,而且那個動態庫的尺寸想當小。
- 獨立性
SQLite 資料庫的核心引擎不需要依賴第三方軟體,也不需要所謂的“安裝”。
- 隔離性
SQLite 資料庫中所有的資訊(比如表、視圖、觸發器等)都包含在一個檔案夾內,方便管理和維護。
- 跨平台
SQLite 目前支援大部分作業系統,不至電腦作業系統更在眾多的手機系統也是能夠運行,比如:Android。
- 多語言介面
SQLite 資料庫支援多語言編程介面。
- 安全性
SQLite 資料庫通過資料庫級上的獨佔性和共用鎖定來實現獨立交易處理。這意味著多個進程可以在同一時間從同一資料庫讀取資料,但只能有一個可以寫入資料.
優點:1.能儲存較多的資料。
2.能將資料庫檔案存放到SD卡中!
什麼是 SQLiteDatabase?
一個 SQLiteDatabase 的執行個體代表了一個SQLite 的資料庫,通過SQLiteDatabase 執行個體的一些方法,我們可以執行SQL 陳述式,對數 據庫進行增、刪、查、改的操作。需要注意的是,資料庫對於一個應用來說是私人的,並且在一個應用當中,資料庫的名字也是惟一的。
什麼是 SQLiteOpenHelper ?
根據這名字,我們可以看出這個類是一個輔助類。這個類主要產生一個資料庫,並對資料庫的版本進行管理。當在程式當中調用這個類的 方法getWritableDatabase(),或者getReadableDatabase()方法的時候,如果當時沒有資料,那麼Android 系統就會自動產生一 個數 據庫。SQLiteOpenHelper 是一個抽象類別,我們通常需要繼承它,並且實現裡邊的3 個函數,
什麼是 ContentValues 類?
ContentValues 類和Hashmap/Hashtable 比較類似,它也是負責儲存一些名值對,但是它儲存的名值對當中的名是一個
String 類型,而值都是基本類型。
什麼是 Cursor ?
Cursor 在Android 當中是一個非常有用的介面,通過Cursor 我們可以對從資料庫查詢出來的結果集進行隨 機的讀寫訪問。
OK,基本知識就介紹到這裡,下面開始上代碼:還是按照我的一貫風格,代碼中該解釋的地方都已經在代碼中及時注釋和講解了!
順便來張項目:
先給出xml:
<?xml version="1.0" encoding="utf-8"?><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="vertical" android:layout_width="fill_parent"<br />android:layout_height="fill_parent"><br /><TextView android:layout_width="fill_parent"<br />android:layout_height="wrap_content" android:text="SQL 練習!(如果你使用的SD卡儲存資料方式,為了保證正常操作,請你先點擊建立一張表然後再操作)"<br />android:textSize="20sp" android:textColor="#ff0000" android:id="@+id/tv_title" /><br /><Button android:id="@+id/sql_addOne" android:layout_width="fill_parent"<br />android:layout_height="wrap_content" android:text="插入一條記錄"></Button><br /><Button android:id="@+id/sql_check" android:layout_width="fill_parent"<br />android:layout_height="wrap_content" android:text="查詢資料庫"></Button><br /><Button android:id="@+id/sql_edit" android:layout_width="fill_parent"<br />android:layout_height="wrap_content" android:text="修改一條記錄"></Button><br /><Button android:id="@+id/sql_deleteOne" android:layout_width="fill_parent"<br />android:layout_height="wrap_content" android:text="刪除一條記錄"></Button><br /><Button android:id="@+id/sql_deleteTable" android:layout_width="fill_parent"<br />android:layout_height="wrap_content" android:text="刪除資料表單"></Button><br /><Button android:id="@+id/sql_newTable" android:layout_width="fill_parent"<br />android:layout_height="wrap_content" android:text="建立資料表單"></Button><br /></LinearLayout><br />
xml中定義了我們需要練慣用到的幾個操作按鈕,這裡不多解釋了,下面看java源碼:先看我們繼承的 SQLiteOpenHelper 類
package com.himi;<br />import android.content.Context;<br />import android.database.sqlite.SQLiteDatabase;<br />import android.database.sqlite.SQLiteOpenHelper;<br />import android.util.Log;<br />/**<br /> *<br /> * @author Himi<br /> * @解釋 此類我們只需要傳建一個建構函式 以及重寫兩個方法就OK啦、<br /> *<br /> */<br />public class MySQLiteOpenHelper extends SQLiteOpenHelper {<br />public final static int VERSION = 1;// 版本號碼<br />public final static String TABLE_NAME = "himi";// 表名<br />public final static String ID = "id";// 後面ContentProvider使用<br />public final static String TEXT = "text";<br />public static final String DATABASE_NAME = "Himi.db";<br />public MySQLiteOpenHelper(Context context) {<br />// 在Android 中建立和開啟一個資料庫都可以使用openOrCreateDatabase 方法來實現,<br />// 因為它會自動去檢測是否存在這個資料庫,如果存在則開啟,不過不存在則建立一個資料庫;<br />// 建立成功則返回一個 SQLiteDatabase對象,否則拋出異常FileNotFoundException。<br />// 下面是來建立一個名為"DATABASE_NAME"的資料庫,並返回一個SQLiteDatabase對象 </p><p>super(context, DATABASE_NAME, null, VERSION);<br />}<br />@Override<br />// 在資料庫第一次產生的時候會調用這個方法,一般我們在這個方法裡邊產生資料庫表;<br />public void onCreate(SQLiteDatabase db) {<br />String str_sql = "CREATE TABLE " + TABLE_NAME + "(" + ID<br />+ " INTEGER PRIMARY KEY AUTOINCREMENT," + TEXT + " text );";<br />// CREATE TABLE 建立一張表 然後後面是我們的表名<br />// 然後表的列,第一個是id 方便操作資料,int類型<br />// PRIMARY KEY 是指主鍵 這是一個int型,用於唯一的標識一行;<br />// AUTOINCREMENT 表示資料庫會為每條記錄的key加一,確保記錄的唯一性;<br />// 最後我加入一列文本 String類型<br />// ----------注意:這裡str_sql是sql語句,類似dos命令,要注意空格!<br />db.execSQL(str_sql);<br />// execSQL()方法是執行一句sql語句<br />// 雖然此句我們產生了一張資料庫表和包含該表的sql.himi檔案,<br />// 但是要注意 不是方法是建立,是傳入的一句str_sql這句sql語句表示建立!!<br />}<br />@Override<br />public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {<br />// 一般預設情況下,當我們插入 資料庫就立即更新<br />// 當資料庫需要升級的時候,Android 系統會主動的調用這個方法。<br />// 一般我們在這個方法裡邊刪除資料表,並建立新的資料表,<br />// 當然是否還需要做其他的操作,完全取決於遊戲需求。<br />Log.v("Himi", "onUpgrade");<br />}<br />}
我喜歡代碼中立即附上解釋,感覺這樣代碼比較讓大家更容易理解和尋找,當然如果童鞋們不喜歡,可以告訴我,我改~嘿嘿~
下面看最重要的MainActivity中的代碼:
package com.himi;<br />import java.io.File;<br />import java.io.IOException;<br />import android.app.Activity;<br />import android.content.ContentValues;<br />import android.database.Cursor;<br />import android.database.sqlite.SQLiteDatabase;<br />import android.os.Bundle;<br />import android.view.View;<br />import android.view.Window;<br />import android.view.WindowManager;<br />import android.view.View.OnClickListener;<br />import android.widget.Button;<br />import android.widget.TextView;<br />// ------------第三種儲存方式--------《SQLite》---------<br />/**<br /> * @author Himi<br /> * @儲存方式:SQLite 輕量級資料庫、<br /> * @優點: 可以將自己的資料存放區到檔案系統或者資料庫當中, 也可以將自己的資料存<br /> * 儲到SQLite資料庫當中,還可以存到SD卡中<br /> * @注意1:資料庫對於一個遊戲(一個應用)來說是私人的,並且在一個遊戲當中,<br /> * 資料庫的名字也是唯一的。<br /> * @注意2 apk中建立的資料庫外部的進程是沒有許可權去讀/寫的,<br /> * 我們需要把資料庫檔案建立到sdcard上可以解決類似問題.<br /> * @注意3 當你刪除id靠前的資料或者全部刪除資料的時候,SQLite不會自動排序,<br /> * 也就是說再添加資料的時候你不指定id那麼SQLite預設還是在原有id最後添加一條新資料<br /> * @注意4 android 中 的SQLite 文法大小寫不敏感,也就是說不區分大小寫;<br /> *<br /> */<br />public class MainActivity extends Activity implements OnClickListener {<br />private Button btn_addOne, btn_deleteone, btn_check, btn_deleteTable,<br />btn_edit, btn_newTable;<br />private TextView tv;<br />private MySQLiteOpenHelper myOpenHelper;// 建立一個繼承SQLiteOpenHelper類執行個體<br />private SQLiteDatabase mysql ;<br />//---------------以下兩個成員變數是針對在SD卡中儲存資料庫檔案使用<br />//private File path = new File("/sdcard/himi");// 建立目錄<br />//private File f = new File("/sdcard/himi/himi.db");// 建立檔案<br />@Override<br />public void onCreate(Bundle savedInstanceState) {<br />super.onCreate(savedInstanceState);<br />getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,<br />WindowManager.LayoutParams.FLAG_FULLSCREEN);<br />this.requestWindowFeature(Window.FEATURE_NO_TITLE);<br />setContentView(R.layout.main);<br />tv = (TextView) findViewById(R.id.tv_title);<br />btn_addOne = (Button) findViewById(R.id.sql_addOne);<br />btn_check = (Button) findViewById(R.id.sql_check);<br />btn_deleteone = (Button) findViewById(R.id.sql_deleteOne);<br />btn_deleteTable = (Button) findViewById(R.id.sql_deleteTable);<br />btn_newTable = (Button) findViewById(R.id.sql_newTable);<br />btn_edit = (Button) findViewById(R.id.sql_edit);<br />btn_edit.setOnClickListener(this);<br />btn_addOne.setOnClickListener(this);<br />btn_check.setOnClickListener(this);<br />btn_deleteone.setOnClickListener(this);<br />btn_deleteTable.setOnClickListener(this);<br />btn_newTable.setOnClickListener(this);<br />myOpenHelper = new MySQLiteOpenHelper(this);// 執行個體一個資料庫輔助器<br />//備忘1 ----如果你使用的是將資料庫的檔案建立在SD卡中,那麼建立資料庫mysql如下操作:<br />//if (!path.exists()) {// 目錄存在返回false<br />//path.mkdirs();// 建立一個目錄<br />//}<br />//if (!f.exists()) {// 檔案存在返回false<br />//try {<br />//f.createNewFile();//建立檔案<br />//} catch (IOException e) {<br />//// TODO Auto-generated catch block<br />//e.printStackTrace();<br />//}<br />//}<br />}<br />@Override<br />public void onClick(View v) {<br />try {<br />//備忘2----如果你使用的是將資料庫的檔案建立在SD卡中,那麼建立資料庫mysql如下操作:<br />// mysql = SQLiteDatabase.openOrCreateDatabase(f, null);<br />//備忘3--- 如果想把資料庫檔案預設放在系統中,那麼建立資料庫mysql如下操作:<br />mysql = myOpenHelper.getWritableDatabase(); // 執行個體資料庫<br />if (v == btn_addOne) {// 添加資料<br />// ---------------------- 讀寫控制代碼來插入---------<br />// ContentValues 其實就是一個雜湊表HashMap, key值是欄位名稱,<br />//Value值是欄位的值。然後 通過 ContentValues 的 put 方法就可以<br />//把資料放到ContentValues中,然後插入到表中去!<br />ContentValues cv = new ContentValues();<br />cv.put(MySQLiteOpenHelper.TEXT, "測試新的資料");<br />mysql.insert(MySQLiteOpenHelper.TABLE_NAME, null, cv);<br />// inser() 第一個參數 標識需要插入操作的表名<br />// 第二個參數 :預設傳null即可<br />// 第三個是插入的資料<br />// ---------------------- SQL語句插入--------------<br />// String INSERT_DATA =<br />// "INSERT INTO himi (id,text) values (1, '通過SQL語句插入')";<br />// db.execSQL(INSERT_DATA);<br />tv.setText("添加資料成功!點擊查看資料庫查詢");<br />} else if (v == btn_deleteone) {// 刪除資料<br />// ---------------------- 讀寫控制代碼來刪除<br />mysql.delete("himi", MySQLiteOpenHelper.ID + "=1", null);<br />// 第一個參數 需要操作的表名<br />// 第二個參數為 id+操作的下標 如果這裡我們傳入null,表示全部刪除<br />// 第三個參數預設傳null即可<br />// ----------------------- SQL語句來刪除<br />// String DELETE_DATA = "DELETE FROM himi WHERE id=1";<br />// db.execSQL(DELETE_DATA);<br />tv.setText("刪除資料成功!點擊查看資料庫查詢");<br />} else if (v == btn_check) {// 遍曆資料<br />//備忘4------<br />Cursor cur = mysql.rawQuery("SELECT * FROM "<br />+ MySQLiteOpenHelper.TABLE_NAME, null);<br />if (cur != null) {<br />String temp = "";<br />int i = 0;<br />while (cur.moveToNext()) {//直到返回false說明表中到了資料末尾<br />temp += cur.getString(0);<br />// 參數0 指的是列的下標,這裡的0指的是id列<br />temp += cur.getString(1);<br />// 這裡的0相對於當前應該是咱們的text列了<br />i++;<br />temp += " "; // 這裡是我整理顯示格式 ,呵呵~<br />if (i % 3 == 0) // 這裡是我整理顯示格式 ,呵呵~<br />temp += "/n";// 這裡是我整理顯示格式 ,呵呵~<br />}<br />tv.setText(temp);<br />}<br />} else if (v == btn_edit) {// 修改資料<br />// ------------------------控制代碼方式來修改 -------------<br />ContentValues cv = new ContentValues();<br />cv.put(MySQLiteOpenHelper.TEXT, "修改後的資料");<br />mysql.update("himi", cv, "id " + "=" + Integer.toString(3), null);<br />// ------------------------SQL語句來修改 -------------<br />// String UPDATA_DATA =<br />// "UPDATE himi SET text='通過SQL語句來修改資料' WHERE id=1";<br />// db.execSQL(UPDATA_DATA);<br />tv.setText("修改資料成功!點擊查看資料庫查詢");<br />} else if (v == btn_deleteTable) {// 刪除表<br />mysql.execSQL("DROP TABLE himi");<br />tv.setText("刪除表成功!點擊查看資料庫查詢");<br />} else if (v == btn_newTable) {// 建立表<br />String TABLE_NAME = "himi";<br />String ID = "id";<br />String TEXT = "text";<br />String str_sql2 = "CREATE TABLE " + TABLE_NAME + "(" + ID<br />+ " INTEGER PRIMARY KEY AUTOINCREMENT," + TEXT<br />+ " text );";<br />mysql.execSQL(str_sql2);<br />tv.setText("建立表成功!點擊查看資料庫查詢");<br />}<br />// 刪除資料庫:<br />// this.deleteDatabase("himi.db");<br />} catch (Exception e) {<br />tv.setText("操作失敗!");<br />} finally {// 如果try中異常,也要對資料庫進行關閉<br />mysql.close();<br />}<br />}<br />}
以上代碼中我們實現了兩種儲存方式:
一種儲存預設系統路徑/data-data-com.himi-databases下,另外一種則是儲存在了/sdcard-himi下,產生資料庫檔案himi.db
那麼這裡兩種實現方式大概步驟和區別說下:
-----------如果我們使用預設系統路徑儲存資料庫檔案:
第一步:建立一個類繼承SQLiteOpenHelper;寫一個構造,重寫兩個函數!
第二步:在建立的類中的onCreate(SQLiteDatabase db) 方法中建立一個表;
第三步:在進行刪除資料、添加資料等操作的之前我們要得到資料庫讀寫控制代碼得到一個資料庫執行個體;
注意: 繼承寫這個輔助類,是為了在我們沒有資料庫的時候自動為我們產生一個資料庫,並且產生資料庫檔案,這裡也同時建立了一張表,因為我們在onCreate裡是在資料庫中建立一張表的操作;這裡還要注意在我們new 這個我們這個MySQLiteOpenHelper 類執行個體對象的時候並沒有建立資料庫喲~!而是在我們調用 (備忘3)MySQLiteOpenHelper ..getWritableDatabase() 這個方法得到資料庫讀寫控制代碼的時候,android 會分析是否已經有了資料庫,如果沒有會預設為我們建立一個資料庫並且在系統路徑data-data-com.himi-databases下產生himi.db 檔案!
(如果我們使用sd卡儲存資料庫檔案,就沒有必要寫這個類了,而是我們自己Open自己的檔案得到一個資料庫,西西,反而方便~ )
-----------如果我們需要把資料庫檔案儲存到SD卡中:
第一步:確認模擬器存在SD卡,關於SD卡的兩種建立方法見我的博文:【Android 2D遊戲開發之十】
第二步:(備忘1)先建立SD卡目錄和路徑已經我們的資料庫檔案!這裡不像上面預設路徑中的那樣,如果沒有資料庫會預設系統路徑產生一個資料庫和一個資料庫檔案!我們必須手動建立資料庫檔案!
第三步:在進行刪除資料、添加資料等操作的之前我們要得到資料庫讀寫控制代碼得到一個資料庫執行個體;(備忘2)此時的建立也不是像系統預設建立,而是我們通過開啟第一步建立好的檔案得到資料庫執行個體。這裡僅僅是建立一個資料庫!!!!
第四步:在進行刪除資料、添加資料等操作的之前我們還要建立一個表!
第五步:在設定檔AndroidMainfest.xml 聲明寫入SD卡的許可權,上一篇已經介紹許可權了,不知道的自己去看下吧。
有些童鞋不理解什麼預設路徑方式中就有表?那是因為我們在它預設給我們建立資料庫的時候我們有建立表的操作,就是MySQLiteOpenHelper類中的onCreate()方法裡的操作!所以我們如果要在進行刪除資料、添加資料等操作的之前還要建立一個表,建立表的方法都是一樣的。
總結:不管哪種方式我們都要-建立資料庫-建立表-然後進行操作!
備忘4:
在Android中查詢資料是通過Cursor類來實現的,當我們使用SQLiteDatabase.query()方法時,會得到一個Cursor對象,Cursor指向的就是每一條資料。它提供了很多有關查詢的方法,具體方法如下:
以下是方法和說明:
move 以當前的位置為參考,將Cursor移動到指定的位置,成功返回true, 失敗返回false
moveToPosition 將Cursor移動到指定的位置,成功返回true,失敗返回false
moveToNext 將Cursor向前移動一個位置,成功返回true,失敗返回false
moveToLast 將Cursor向後移動一個位置,成功返回true,失敗返回 false。
movetoFirst 將Cursor移動到第一行,成功返回true,失敗返回false
isBeforeFirst 返回Cursor是否指向第一項資料之前
isAfterLast 返回Cursor是否指向最後一項資料之後
isClosed 返回Cursor是否關閉
isFirst 返回Cursor是否指向第一項資料
isLast 返回Cursor是否指向最後一項資料
isNull 返回指定位置的值是否為null
getCount 返回總的資料項目數
getInt 返回當前行中指定的索引資料
對於SQLite的很多童鞋有接觸過,但是就不知道怎麼儲存在SD中,所以我也研究了下,這篇也寫了把sd卡中的方式也提供給大家。
OK 這元旦放假幾天就光給大家寫這個了,呵呵~淩晨2點了,咳咳~該睡覺了。(我一般淩成3點睡覺,早8點起來上班 呵呵~習慣了~)
(推薦大家訂閱本部落格,因為咱的更新速度可是很快的~娃哈哈)
本篇源碼: http://www.himigame.com/android-game/329.html