這幾天我們討論數個跟資料庫存取相關的 Android 類別,包含了 SQLiteOpenHelper, SQLiteDatabase, Cursor 等,然而關於資料庫還有許多可介紹的,例如筆者多年前曾寫過一篇文章介紹如何從 assets 資料夾讀取一個已經存在的資料庫,並將之寫入 /data/data/<package name>/databases 裡,鐵人賽參賽至今的確是略感疲憊了,今天先暫時用舊文章代替。然而還是有一點需要說明,若使用 ADT Bundle (Eclipse),assets 資料夾位於 Project 的根目錄下,若使用 Android Studio,您必須在 /app/src/main 自行建立 assets 資料夾 [1]。
有一些應用程式會需要讀取已經建立好的資料庫,例如一個試題測驗應用程式,裡面的試題可能已經於電腦上,使用任何的 SQLite 資料庫產生工具,產生一個資料庫檔案(例如:questions.db),我們會希望我們的應用程式能夠直接讀取這個檔案,而不是在應用程式第一次執行時,利用程式去做 INSERT 的動作,下面將示範如何實作出這樣的功能。
第一個步驟是產生 SQLite 資料庫檔案,產生資料庫檔案的工具很多,其中一個是到 SQLite 的官方網站[1]下載,以 Windows 作業系統為例,網站有提供一個已經編譯好的命令列工具 sqlite3.exe,首先進入 Windows 所提供的『命令提示字元』,進入放置 sqlite3.exe 的資料夾後就可以執行它,執行時後面加個檔案名稱當參數,下面是一個範例:
C:\>sqlite3 questions.db
SQLite version 3.7.8 2011-09-19 14:49:19
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> create table questions(_question, _type, _answer);
sqlite> insert into questions values("1+1", "Math", "2");
sqlite> .exit
在上面的範例中,我們做了兩件事,首先建立一個表格,接著再插入一筆資料到表格內,我們可以再次執行 sqlite3,來驗證資料是否有正確被寫入:
C:\>sqlite3 questions.db
SQLite version 3.7.8 2011-09-19 14:49:19
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> select * from questions;
1+1|Math|2
除然而為了讓資料庫能夠正常地被 Android 所讀取,有兩點必須注意:
表格必須有一個名為『_id』的欄位,且 type 要設成『INTEGER PRIMARY KEY』。
必須建立一個名為『android_metadata』的表格,表格中要建立一個名為『locale』的欄位,經筆者的測試,我們不需要新增任何記錄。
產生好 questions.db 後我們就可以把這個檔案放到 assets 資料夾,接著就是要進行程式的撰寫:
package lincyu.demo.database_from_outside;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
class DatabaseHelper extends SQLiteOpenHelper {
private static String DB_PATH = "/data/data/" +
"lincyu.demo.database_from_outside" + "/databases/";
private static String DB_NAME = "questions.db";
private final Context mCtx;
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, 1); 24 this.mCtx = context;
}
public boolean createDatabase() {
boolean dbExist = checkDatabase();
this.getReadableDatabase();
if (dbExist == false) {
if (copyDatabase() == false) {
return false;
}
}
return true;
}
private boolean checkDatabase() {
SQLiteDatabase checkDB = null;
String dbpath = DB_PATH + DB_NAME;
try {
checkDB = SQLiteDatabase.openDatabase(dbpath,
null, SQLiteDatabase.OPEN_READONLY);
} catch (SQLiteException e) {
return false;
}
if (checkDB != null) {
checkDB.close();
return true;
}
return false;
}
private boolean copyDatabase() {
try {
InputStream input = mCtx.getAssets().open(DB_NAME);
this.getReadableDatabase();
String outFileName = DB_PATH + DB_NAME;
OutputStream output =
new FileOutputStream(outFileName);
byte [] buffer = new byte[1024];
int length;
while ((length = input.read(buffer)) > 0) {
output.write(buffer, 0, length);
}
output.flush();
output.close();
input.close();
} catch (Exception e) {
return false;
}
return true;
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldV, int newV) {
}
}
從程式碼我們不能發現,其實就只是單純的讀寫檔案,然而必須一提的是,要存取 assets 資料夾必須利用 Context 類別的 getAssets 方法來存取,getAssets 會回傳一個 AssetManager 物件,再呼叫其 open,就可讀取 assets 資料夾下的檔案。
參考資料
[1] Where to place Assets folder in Android Studio - Stack Overflow, http://stackoverflow.com/questions/18302603/where-to-place-assets-folder-in-android-studio