閲覧ありがとうございます。
専門の知識が乏しいため読みづらかったり、用語の使い方が間違っていたりすると思いますが、何卒よろしくお願いします。
SQLiteを用いてデータ保存をするAndroidアプリの開発中、実機に繋いでインストールし実行していました。
今までデータの読み書きともに正常に行えていたのですが、一旦本体の設定からアプリのデータ削除をしたところ、途端に読み書きともに行えなくなってしまいました。(SQLiteDatabaseオブジェクトをヘルパーからgetReadableDatabase/getWritableDatabaseで開く処理だけtry文に入れるとSQLiteExceptionをcatchしました)
自分では経験・知識ともに浅く、また調べても同じようなケースが見当たらないため、原因や解決方法を教えて頂きたいです。よろしくお願いします。
(ソースコードですが、どの部分を載せるべきか判断しかねましたので、ご指摘いただいてから記載したいと思います)
追記(10/22/20:10):ご指摘いただきましたのでソースコードを追記いたしました。
乱雑で読みづらいコードで申し訳ありません。必要ないと思われる箇所は適宜省略しましたが、いかんせんどこを載せたらよいのかわからず、無駄な部分が多いと思いますがご容赦ください。
また、ご要望頂いておりますエラーログですが、LogCatの調子が悪く全く出力されなくなっているため、自分も把握できていない状況です。申し訳ありません。
該当のソースコード
public class MainActicity extends FragmentActivity {
//扱うitemを管理
ArrayList<Item> items = new ArrayList<Item>();
//各種数値
private int[] values;
/*
* valuesはアクティビティ全体として保持すべき数値のうち、
* データベースに保存すべきもの。
* SALES_AMOUNT = 総売り上げ額
*/
private final int SALES_AMOUNT = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//各ViewをfindViewByIdしたりOnClickを実装したりしていますが省略
//データロード
values = DataUtils.load(this,items);
}
@Override
protected void onPause() {
DataUtils.store(this,values);
super.onPause();
}
}
public class Item {
//商品名
public String name;
//設定価格
public int price;
//売上個数
private int sales;
//コンストラクタ
public Item(String name, int price, int sales) {
this.name = name;
this.price = price;
this.sales = sales;
}
//セッター
public void setName(String name) {
this.name = name;
}
public void setPrice(int price) {
this.price = price;
}
//ゲッター
public String getName(){
return this.name;
}
public int getPrice(){
return this.price;
}
public int getSales(){
return this.sales;
}
}
public class MySQLiteOpenHelper extends SQLiteOpenHelper {
//データベース・テーブル・カラム・レコード作成用の値はすべてDataUtilsクラスで管理
static final String DB_NAME = DataUtils.DB_NAME;
static final int DB_VERSION = DataUtils.DB_VERSION;
static final String TABLE_NAME_ITEM = DataUtils.TABLE_NAME_ITEM;
static final String TABLE_NAME_INTEGER = DataUtils.TABLE_NAME_INTEGER;
static final String COLUMN_NAME_ID = DataUtils.COLUMN_NAME_ID;
static final String COLUMN_NAME_ITEM_NAME = DataUtils.COLUMN_NAME_ITEM_NAME;
static final String COLUMN_NAME_ITEM_PRICE = DataUtils.COLUMN_NAME_ITEM_PRICE;
static final String COLUMN_NAME_ITEM_SALES = DataUtils.COLUMN_NAME_ITEM_SALES;
static final String COLUMN_NAME_INTEGER_NOMINAL = DataUtils.COLUMN_NAME_INTEGER_NOMINAL;
static final String COLUMN_NAME_INTEGER_VALUE = DataUtils.COLUMN_NAME_INTEGER_VALUE;
static final String RECORD_NAME_SALESAMOUNT = DataUtils.RECORD_NAME_SALESAMOUNT;
//コンストラクタ
public MySQLiteOpenHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/**
* 初回作成時に呼ばれる
* テーブルを作成する
*/
@Override
public void onCreate(SQLiteDatabase db) {
//Item用テーブル作成
db.execSQL("CREATE TABLE " + TABLE_NAME_ITEM + " ("
+ COLUMN_NAME_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ COLUMN_NAME_ITEM_NAME + " TEXT NOT NULL,"
+ COLUMN_NAME_ITEM_PRICE + " INTEGER NOT NULL,"
+ COLUMN_NAME_ITEM_SALES + " INTEGER NOT NULL)");
//整数用テーブル作成
db.execSQL("CREATE TABLE " + TABLE_NAME_INTEGER + " ("
+ COLUMN_NAME_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ COLUMN_NAME_INTEGER_NOMINAL + "TEXT PRIMARY KEY NOT NULL"
+ COLUMN_NAME_INTEGER_VALUE + " INTEGER)");
db.execSQL("INSERT INTO " + TABLE_NAME_INTEGER + " VALUES("
+ RECORD_NAME_SALESAMOUNT + ",0)");
}
/**
* データベースの更新が必要な時に呼ばれる
* テーブルを作り直す
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
public class DataUtils {
//データベース
private static MySQLiteOpenHelper mySQLiteOpenHelper;
private static SQLiteDatabase DB;
//データベース名
static final String DB_NAME = "myregister.db";
//データベースバージョン
static final int DB_VERSION = 1;
//テーブル名
static final String TABLE_NAME_ITEM = "item_table";
static final String TABLE_NAME_INTEGER = "integer_table";
//カラム名
static final String COLUMN_NAME_ID = "_id";
static final String COLUMN_NAME_ITEM_NAME = "name";
static final String COLUMN_NAME_ITEM_PRICE = "price";
static final String COLUMN_NAME_ITEM_SALES = "sales";
static final String COLUMN_NAME_INTEGER_NOMINAL = "nominal";
static final String COLUMN_NAME_INTEGER_VALUE = "value";
//INTEGERテーブルのレコード名
static final String RECORD_NAME_SALESAMOUNT = "salesAmount";
protected static int[] load(Activity activity, ArrayList<Item> items) {
mySQLiteOpenHelper = new MySQLiteOpenHelper(activity.getApplicationContext());
try{
//データベースを読み取り専用で開く
DB = mySQLiteOpenHelper.getReadableDatabase();
}catch(SQLiteException e) {
//異常終了
//ここで引っかかってしまう(トーストが出る)********************************
Toast.makeText(activity, "データベースを開けません", Toast.LENGTH_SHORT).show();
return null;
}
try{
//itemテーブルからデータを取得
Cursor c;
c = DB.rawQuery("SELECT * "
+ "FROM " + TABLE_NAME_ITEM + " "
+ "ORDER BY " + COLUMN_NAME_ID + " ASC"
, null);
//取得したデータからitemsを復元
if(c.moveToFirst()){
//レコードが存在する場合、ID順にitemsにItemオブジェクトを作成
do{
items.add(new Item (c.getString (c.getColumnIndex(COLUMN_NAME_ITEM_NAME)),
c.getInt (c.getColumnIndex(COLUMN_NAME_ITEM_PRICE)),
c.getInt(c.getColumnIndex(COLUMN_NAME_ITEM_SALES)) ) );
}while(c.moveToNext());
}else{
//レコードが存在しない場合の処理
}
//integerテーブルからデータ取得
int[] values = new int[1];
c = DB.rawQuery("SELECT * "
+ "FROM " + TABLE_NAME_INTEGER + " "
+ "ORDER BY " + COLUMN_NAME_ID + " ASC"
, null);
int n = 0;
if(c.moveToFirst()){
do{
values[n] = c.getInt(c.getColumnIndex(COLUMN_NAME_INTEGER_VALUE));
n++;
}while(c.moveToNext());
}else{
//レコードが存在しない場合の処理
}
DB.close();
return values;
}catch(SQLiteException e){
//異常終了
Toast.makeText(activity, "データ取得に失敗しました", Toast.LENGTH_SHORT).show();
return null;
}
}
/**
* データベースへデータを保存
*/
protected static void store(Activity activity, int[] values){
mySQLiteOpenHelper = new MySQLiteOpenHelper(activity.getApplicationContext());
try{
//データベースを書き込み可能で開く
DB = mySQLiteOpenHelper.getWritableDatabase();
//INTEGERテーブルに値を保存
for(int i=0;i<values.length;i++){
DB.execSQL("UPDATE " + TABLE_NAME_INTEGER + " "
+ "SET " + COLUMN_NAME_INTEGER_VALUE + "=" + values[i] + " "
+ "WHERE " + COLUMN_NAME_ID + "=" + String.valueOf(i+1));
}
DB.close();
}catch(SQLiteException e) {
//異常終了
//ここも引っかかる(トーストが出る)********************
Toast.makeText(activity, "保存に失敗しました", Toast.LENGTH_SHORT).show();
return;
}
}
}
試したこと
一度データ削除をしたことで、データベース自体かもしくはテーブルが無くなり、ヘルパーのonCreateが上手く作動してないのかと思い、データベースの中身を見ようと試行錯誤しましたがそもそも見つからずじまいでした。
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+1
こんにちは。
開けなくなるのが正常な動作ですので大丈夫です。SQLiteのデータベースを作ると、データベースのファイルは自アプリで読み書きできるディレクトリ下に作成されます。
例えば自アプリが「Sample」で、パッケージが「products.examples」、データベースファイル名が「sample.db」であれば、ファイルは
/data/data/examples.products.sample/databases/sample.db
として作成されます。これを本体の「設定」でアプリデータの消去をするとdatabases/sample.dbが削除されてしまうので、開けなくなります。
(ファイルの位置はAndroid Device Monitorや、場合にもよりますがadb shellでシェル上で確認することもできます)
アプリで適切にデータベースの作成をするコードがあるのであれば、本体を再起動させればまた正常に開けるようになるはずです。
「本体を再起動させれば」と書いたのは念の為で、データベースを作るコードがどこにあるか、ActivityのonCreate
にある場合やonResume
など、Activityのライフサイクルに関わるメソッドで変わってくるからです。onCreate
上にあると、操作の流れによって呼び出されないことがあります。このあたりは「Android Activity ライフサイクル」などで検索して確認してみてください。
追記
ご提示いただいたコードを確認させてもらいました。このコードが全部ではないと言うことでしたが、少なくとも2点、問題を見つけましたので追記します。MySQLiteOpenHelper 内で生成しているSQLに問題があります。
1つ目、2つのカラムにプライマリキー指定がされているので、CREATE文の実行でエラー(Exception)になります。
//整数用テーブル作成
db.execSQL("CREATE TABLE " + TABLE_NAME_INTEGER + " ("
+ COLUMN_NAME_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ COLUMN_NAME_INTEGER_NOMINAL + "TEXT PRIMARY KEY NOT NULL"
+ COLUMN_NAME_INTEGER_VALUE + " INTEGER)");
# Exceptionの内容
android.database.sqlite.SQLiteException: table "integer_table" has more than one primary key (code 1): , while compiling: CREATE TABLE integer_table (_id INTEGER PRIMARY KEY AUTOINCREMENT,nominalTEXT PRIMARY KEY NOT NULLvalue INTEGER)
2つ目、INSERT文のSQLにも問題があり、正しくカラム名が指定できてません。
db.execSQL("INSERT INTO " + TABLE_NAME_INTEGER + " VALUES("
+ RECORD_NAME_SALESAMOUNT + ",0)");
# Exceptionの内容
android.database.sqlite.SQLiteException: no such column: salesAmount (code 1): , while compiling: INSERT INTO integer_table VALUES(salesAmount,0)
ひとつの例ですが、以下のようなSQLにしてカラムを指定し、完成させる必要があるかと思います。
INSERT INTO integer_table(nominal,value) VALUES ('salesAmount',0)
ご提示のコードでは上記のように途中でExceptionが発生し、完了しないのでテーブルが完成していません。
ただ、上の指摘はSQLの問題なので、データベースファイル自体はgetReadableDatabase
を呼び出した時点で作成されるはずです。その後、SQLiteOpenHelper.onCreate
が呼ばれるはずですので、ブレークポイントを張って確認してみてください。尚、既にファイルがあればSQLiteOpenHelper.onCreate
は呼ばれません。
ファイルはadb shellで確認できるのですが、実機を使っているとユーザーのパーミッションの関係で実行が失敗するかもしれません。以下はmacOS上のAndroidエミュレーターを相手として実行した結果です。myregister.dbファイルがあることが分かります。
bash-3.2$ adb shell ls -l /data/data/examples.products.test27/databases/
-rw-rw---- u0_a57 u0_a57 24576 2017-10-23 04:23 myregister.db
-rw------- u0_a57 u0_a57 8720 2017-10-23 04:23 myregister.db-journal
Test27と言うアプリ名、packageが examples.products で確認用アプリを作ったのでこのようなPATHになっています。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.37%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
質問への追記・修正、ベストアンサー選択の依頼
yona
2017/10/22 10:38
エラーログを質問に追記してください。