AndroidのSQLiteのCursorのデータ容量制限
制作中のAndroidアプリでカメラ撮影した画像をDBにBLOBで挿入していたのですが、サイズの大きい画像データだとSELECT時にアプリが落ちる現象に遭遇しました。
仮説でしかないのですが、Cursor内部の1レコード保持のためのメモリが1Mbyteしか用意されていない可能性が考えられます。
以下は検証のために作成したプログラムです。
DBHelper
package net.n2works.SQLiteTest;
// SYSTEM PACKAGE
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
public class DatabaseHelper extends SQLiteOpenHelper
{
public DatabaseHelper(Context context, String name, CursorFactory factory, int version)
{
super(context, name, factory, version);
}
public void onCreate(SQLiteDatabase db)
{
String sql = " CREATE TABLE `blob_test`(`テストデータ` BLOB NOT NULL) ";
db.beginTransaction();
try {
SQLiteStatement st = db.compileStatement(sql);
st.execute();
db.setTransactionSuccessful();
}
finally {
db.endTransaction();
}
}
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
}
}
Activity
package net.n2works.SQLiteTest;
// SYSTEM_PACKAGE
import android.app.Activity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteStatement;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
public class SQLiteTest extends Activity
{
// DBHelper
DatabaseHelper dbh;
/**
* クラス定数
*/
// テストデータサイズ(1Mbyte = 1048576byte = 1024byte * 1024byte)
private static final int BLOB_SIZE = 1048498;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// DBHelperの取得
dbh = new DatabaseHelper(
this,
"sqlitetest.db",
null,
1
);
// DBハンドル取得
SQLiteDatabase db = GetDB();
// テストデータ作成
byte[] test_data = new byte[SQLiteTest.BLOB_SIZE];
for (int i = 0; i < SQLiteTest.BLOB_SIZE; i++) {
test_data[i] = 0xf;
}
// テストデータ入れ替え
SQLiteStatement st = db.compileStatement(" DELETE FROM `blob_test` ");
db.beginTransaction();
try {
st.execute();
st.close();
st = db.compileStatement(" INSERT INTO `blob_test`(`テストデータ`) VALUES(?) ");
st.bindBlob(1, test_data);
st.executeInsert();
st.close();
db.setTransactionSuccessful();
}
finally {
db.endTransaction();
}
db.close();
// レイアウト作成
LinearLayout layout = new LinearLayout(this);
LayoutParams params = new LayoutParams(
LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT
);
layout.setLayoutParams(params);
layout.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
setContentView(layout);
// ボタンの作成
Button btn = new Button(this);
layout.addView(btn);
params = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
);
btn.setLayoutParams(params);
btn.setText("データ取得");
btn.setOnClickListener(new Listener());
}
private SQLiteDatabase GetDB()
{
try {
return dbh.getWritableDatabase();
}
catch (SQLiteException e) {
return dbh.getReadableDatabase();
}
}
private class Listener
implements OnClickListener
{
public void onClick(View v)
{
SQLiteDatabase db = GetDB();
Cursor cur = db.rawQuery(
" SELECT `テストデータ` FROM `blob_test` ",
null
);
byte[] data = null;
if (cur.moveToFirst()) {
data = cur.getBlob(0);
}
cur.close();
db.close();
}
}
}
BLOB_SIZE = 1048498
という値がmoveToFirstしてもアプリが落ちない値です。
これに1を加算して1048499にすると落ちます。
1Mbyteが1048576byteなので多少誤差がありますが、それはカラム情報などレコードの内部情報を保持しているからではないかと見ています。
スキーマを変更してIntegerのカラムを増やしてみましたが、4byteではなく9byteの差が出たからです。
androidライブラリのターゲットVersionによって今後変更される可能性があるので、実行した環境を記しておきます。
[実行機種]:SO-01B Xperia
[ターゲットversion]:android-2.1-update
BLOB型を使用する場合は注意してください。