しかし、あらかじめ作成しておいた SQLite database をアプリに仕込みたい場合があります。
そこで、ここでは sqlite3 など使って作成した自分の SQLite database ファイルを、アプリの asset に入れ、初回起動時にアプリのシステムデータベース領域にコピーする方法を紹介します。
1. SQLite database ファイルを用意する
私は Ubuntu 派なので普通に sqlite3 を使います。(Windows とか Mac はよくわかりません... これとか? SQLite Database Browser)
主テーブルの他に android_metadata という名前のテーブルを作成します。中身は locale です。主テーブルの primary key は _id という名前にします(Android が id を理解できる用にするためです)。
$ cat createTable.sql
create table android_metadata (
locale text default 'en_US'
);
create table sample_data (
_id integer primary key not null,
name text,
address text,
tel text
);
ちなみに日本は ja_JP です。
データベースとテーブルを作成します。
$ sqlite3 mysqlite_database.db < createTable.sql
作成したテーブルに CSVファイルをインポートします。
ここでは、
1,name1,address1,tel1
2,name2,address2,tel2
...
というデータを想定しています。
# 改行コードの "\r" や、各カラムの " 囲みを削除するには
#
# $ tr -d "\r" < old.csv | tr -d '"' > new.csv
#
# とかやると幸せになれるかも。
# 文字コードは nkf とかで。
# primary key 部分は awk とかで頑張ってみるとか。
sqlite3 -separator , mysqlite_database.db ".import mydata.csv sample_data"
なにも言われなければうまくいったかも
sqlite3 mysqlite_database.db "select * from sample_data where _id=1"
とかで中身をチェックして入っていればOK。
2. 展開&コピーして Android アプリからアクセスできるようにする
1. で作った SQLite database ファイルを asset に入れ、SQLiteOpenHelper を継承したクラスを作成します。
public class DataBaseHelper extends SQLiteOpenHelper {
private static String DB_NAME = "my_database";
private static String DB_NAME_ASSET = "my_database.db";
private static final int DATABASE_VERSION = 17;
private SQLiteDatabase mDatabase;
private final Context mContext;
private final File mDatabasePath;
public DataBaseHelper(Context context) {
super(context, DB_NAME, null, DATABASE_VERSION);
mContext = context;
mDatabasePath = mContext.getDatabasePath(DB_NAME);
}
/**
* asset に格納したデータベースをコピーするための空のデータベースを作成する
*/
public void createEmptyDataBase() throws IOException {
boolean dbExist = checkDataBaseExists();
if (dbExist) {
// すでにデータベースは作成されている
} else {
// このメソッドを呼ぶことで、空のデータベースがアプリのデフォルトシステムパスに作られる
getReadableDatabase();
try {
// asset に格納したデータベースをコピーする
copyDataBaseFromAsset();
String dbPath = mDatabasePath.getAbsolutePath();
SQLiteDatabase checkDb = null;
try {
checkDb = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READWRITE);
} catch (SQLiteException e) {
}
if (checkDb != null) {
checkDb.setVersion(DATABASE_VERSION);
checkDb.close();
}
} catch (IOException e) {
throw new Error("Error copying database");
}
}
}
/**
* 再コピーを防止するために、すでにデータベースがあるかどうか判定する
*
* @return 存在している場合 {@code true}
*/
private boolean checkDataBaseExists() {
String dbPath = mDatabasePath.getAbsolutePath();
SQLiteDatabase checkDb = null;
try {
checkDb = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
} catch (SQLiteException e) {
// データベースはまだ存在していない
}
if (checkDb == null) {
// データベースはまだ存在していない
return false;
}
int oldVersion = checkDb.getVersion();
int newVersion = DATABASE_VERSION;
if (oldVersion == newVersion) {
// データベースは存在していて最新
checkDb.close();
return true;
}
// データベースが存在していて最新ではないので削除
File f = new File(dbPath);
f.delete();
return false;
}
/**
* asset に格納したデーだベースをデフォルトのデータベースパスに作成したからのデータベースにコピーする
*/
private void copyDataBaseFromAsset() throws IOException{
// asset 内のデータベースファイルにアクセス
InputStream mInput = mContext.getAssets().open(DB_NAME_ASSET);
// デフォルトのデータベースパスに作成した空のDB
OutputStream mOutput = new FileOutputStream(mDatabasePath);
// コピー
byte[] buffer = new byte[1024];
int size;
while ((size = mInput.read(buffer)) > 0) {
mOutput.write(buffer, 0, size);
}
// Close the streams
mOutput.flush();
mOutput.close();
mInput.close();
}
public SQLiteDatabase openDataBase() throws SQLException {
return getReadableDatabase();
}
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
@Override
public synchronized void close() {
if(mDataBase != null)
mDataBase.close();
super.close();
}
}
あとは、普通にアクセスする
private DataBaseHelper mDbHelper;
private SQLiteDatabase db;
private StationCodeDao mStationCodeDao;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDatabase();
}
@Override
public void onDestroy() {
db.close();
super.onDestroy();
}
private void setDatabase() {
mDbHelper = new DataBaseHelper(this);
try {
mDbHelper.createEmptyDataBase();
db = mDbHelper.openDataBase();
} catch (IOException ioe) {
throw new Error("Unable to create database");
} catch(SQLException sqle){
throw sqle;
}
}
private static final String[] COLUMNS = {"_id", "name", "address", "tel"};
private Cursor findData(int id) {
Cursor cursor = db.query(TABLE_NAME, COLUMNS, "where _id=" + id, null, null, null, COLUMN_ID);
return cursor;
}
---------------------------------------
2011年2月4日 追記
assets に入れた db が 1MB 以上の場合、うまくいかないことがあるみたいです。
そんなときは、itog さんのブログ
apkに1MB以上のデータベースファイルを含める - Hacking My Way ~ itogのhack日記 -
を参考に、db を圧縮するなり分割するなりすると、うまくいくようです。
---------------------------------------
2013年10月22日 追記
データベースのパスを固定から Context の getDatabasePath() を使うように修正しました。
バージョン番号を使って、アップデート時にデータベースを再度 Asset からコピーできるようにしました。
はじめまして、平塚と申します
返信削除すみません、質問させてください。
上記の方法で、実装してるのですが、
while ((size = mInput.read(buffer)) > 0){
のところでIOExceptionをはいて、落ちてしまいます。
この場合に考えられる原因ってなんでしょうか??
教えていただけると幸いです。
よろしくお願いいたします
はじめまして、塚原と申します。
返信削除お聞きしたいことがあり、書き込みしました。
上記の方法で実装しているのですが、携帯によって何事もなく動く携帯と強制終了する携帯があるのですが、原因がわからずこまっています。教えて頂けると幸いです。
よろしくお願いいたします。
動く携帯はGALAXYS SC-02B
動かない携帯はARROWS X LTE F-05D
私もArrowsシリーズで動かなかったので調べたところ、Arrowsでは
返信削除"/data/data/YOUR_PACKAGE/databases/" フォルダへのファイルアクセスがパーミッションで制限されているようです。
databases を使わずに、以下のようにfilesフォルダを利用するようにしたら動きました。
public void createEmptyDataBase() throws IOException {
boolean dbExist = checkDataBaseExists();
if (dbExist) {
// すでにデータベースは作成されている
} else {
try {
// asset に格納したデータベースをコピーする
copyDataBaseFromAsset();
} catch (IOException e) {
throw new Error("Error copying database");
}
}
}
private boolean checkDataBaseExists() {
SQLiteDatabase checkDb = null;
try {
File fileObj = mContext.getFileStreamPath(DATABASE_NAME);
String dbPath = fileObj.getAbsolutePath();
checkDb = SQLiteDatabase.openDatabase(dbPath, null,
SQLiteDatabase.OPEN_READONLY);
} catch (SQLiteException e) {
// データベースはまだ存在していない
}
if (checkDb != null) {
checkDb.close();
}
return checkDb != null ? true : false;
}
private void copyDataBaseFromAsset() throws IOException {
// asset 内のデータベースファイルにアクセス
InputStream mInput = mContext.getAssets().open(DATABASE_NAME);
OutputStream mOutput = mContext.openFileOutput(DATABASE_NAME, Context.MODE_PRIVATE);
// コピー
byte[] buffer = new byte[1024];
int size;
while ((size = mInput.read(buffer)) > 0) {
mOutput.write(buffer, 0, size);
}
// Close the streams
mOutput.flush();
mOutput.close();
mInput.close();
}
@Override
public SQLiteDatabase getWritableDatabase() {
SQLiteDatabase db = null;
try {
File fileObj = mContext.getFileStreamPath(DATABASE_NAME);
String dbPath = fileObj.getAbsolutePath();
db = SQLiteDatabase.openDatabase(dbPath, null,
SQLiteDatabase.OPEN_READWRITE);
} catch (SQLiteException e) {
throw new Error("Error opening database");
}
return db;
}
@Override
public SQLiteDatabase getReadableDatabase() {
SQLiteDatabase db = null;
try {
File fileObj = mContext.getFileStreamPath(DATABASE_NAME);
String dbPath = fileObj.getAbsolutePath();
db = SQLiteDatabase.openDatabase(dbPath, null,
SQLiteDatabase.OPEN_READONLY);
} catch (SQLiteException e) {
throw new Error("Error opening database");
}
return db;
}
唐沢 様
削除タロイです。
ありがとうございます。
早速、その方法で作り直して動かしてみたいと思います。
困り果てていたので、助かりました。本当にありがとうございます。(*・ω・)*_ _))ペコリン
//The Android のデフォルトでのデータベースパス
返信削除private static String DB_PATH = "/data/data/YOUR_PACKAGE/databases/"
このパスにデータベースが置かれるかどうかは Androidのバージョンによって依存するでしょうから APIを利用して取得すべきでしょう
はじめまして。
返信削除参考にさせていただき、動かすことができました。
ありがとうございました。
しかし、つい先日まで、何の問題もなく動いていたのですが、
メジャーアップデートした実機(android4.1.2)で
android.database.sqlite.SQliteException:no such table・・・のエラーを吐いて落ちるようになってしまいました。
もちろんテーブルもちゃんと存在していますし、何より、全くプログラムは動いていたものから変わっていないのです。
どう対応すればよいのか分からず困っています。
どんなことが考えられるでしょうか。
Thank you for your post. This is excellent information. It is amazing and wonderful to visit your site.
返信削除mobile app developers
いつもお世話になってます。
返信削除DataBaseHelperの27行目
getReadableDatabase();
は
SQLiteDatabase db = getReadableDatabase();
db.close();
のようにしないと端末によっては例外を吐いて落ちますね。
返信削除The birbal video interview platform is one of the few mobile-friendly tools on the market. This software allows candidates to record their video responses on their smartphones and tablets. This software also uses AI to analyze non-verbal cues like facial expressions, eye movements, and voice nuances. This helps the interviewer make meaningful assessments at the end of the interview.
video interview platform
video interview software
Develop performance-optimized, intuitive, and conversion-generating apps by hire react developer.
返信削除Video Analytics Solutions uses video surveillance systems to extract accessible, usable, and measurable information from live or stored video footage.
返信削除Take your web and mobile app development vision to the next level of innovation with high-quality and cost-effective services from our react js agency!
返信削除このコメントは投稿者によって削除されました。
返信削除Wow!!! Super cool and very inspiring. Thanks for the collection! Healthcare App Development
返信削除Healthcare Software Development
返信削除websites for pharmacists
responsive webdesign
alternatives to react js
best web development stack
difference between full stack and mern stack
The fili is a medicine, particularly intended to treat hypoactive sexual craving issue otherwise called (HSDD) in ladies who are pre-menopausal.
返信削除Flibanserin 100mg