带有 SQLite 的 Android 应用程序在 ICS 中运行,但在 Jelly Bean 中不运行 - IllegalStateException
Posted
技术标签:
【中文标题】带有 SQLite 的 Android 应用程序在 ICS 中运行,但在 Jelly Bean 中不运行 - IllegalStateException【英文标题】:Android App with SQLite runs in ICS but not Jelly Bean - IllegalStateException 【发布时间】:2013-09-05 06:07:55 【问题描述】:我开发了一个适用于 android 的应用程序,它将汽车加油信息存储在 SQLite 数据库中。在我的 4.0.3 手机和 4.0.3 AVD 上似乎一切正常,但我希望它也能在 Jelly Bean 和更新版本中工作。该应用程序真的没有做任何非常复杂的事情。当我在模拟的 Jelly Bean 设备中启动应用程序时,我收到一个 FATAL EXCEPTION 错误:
09-05 01:45:40.311: W/dalvikvm(1062): threadid=1: 线程以未捕获的异常退出 >(group=0x414c4700) 09-05 01:45:40.331:E/AndroidRuntime(1062):致命异常:主要 09-05 01:45:40.331: E/AndroidRuntime(1062): java.lang.RuntimeException: 无法启动 >activity ComponentInfoard.util.fueltracker/ard.util.fueltracker.TitleScreenActivity: >java.lang.IllegalStateException :尝试重新打开一个已经关闭的对象:> SQLiteDatabase:/data/data/ard.util.fueltracker/databases/vehicleDatabase 09-05 01:45:40.331: E/AndroidRuntime(1062): at >android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2211)
在我最初在 Google 中搜索时,我认为我遇到了实例问题,所以我在这里遵循了方法 #1 (http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html),但这似乎没有什么不同。仍然可以在 ICS 中使用这些更改。
这是我的 TitleScreenActivity 中的代码:
package ard.util.fueltracker;
import java.util.List;
import ard.util.fueltracker.util.SystemUiHider;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.content.Intent;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.Toast;
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*
* @see SystemUiHider
*/
@SuppressLint("NewApi")
public class TitleScreenActivity extends Activity implements OnItemSelectedListener
/**
* Whether or not the system UI should be auto-hidden after
* @link #AUTO_HIDE_DELAY_MILLIS milliseconds.
*/
private static final boolean AUTO_HIDE = false;
/**
* If @link #AUTO_HIDE is set, the number of milliseconds to wait after
* user interaction before hiding the system UI.
*/
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
/**
* If set, will toggle the system UI visibility upon interaction. Otherwise,
* will show the system UI visibility upon interaction.
*/
private static final boolean TOGGLE_ON_CLICK = false;
/**
* The flags to pass to @link SystemUiHider#getInstance.
*/
//private static final int HIDER_FLAGS = SystemUiHider.FLAG_HIDE_NAVIGATION;
private static final int HIDER_FLAGS = 0;
/**
* The instance of the @link SystemUiHider for this activity.
*/
private SystemUiHider mSystemUiHider;
// Spinner element
Spinner spinner;
// Buttons
Button btnAdd;
Button btnLogo;
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
getWindow().requestFeature(Window.FEATURE_ACTION_BAR); //new
getActionBar().hide(); //new
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_title_screen);
// Spinner element
spinner = (Spinner) findViewById(R.id.titleSelectionSpinner);
// buttons
btnAdd = (Button) findViewById(R.id.button_go);
btnLogo = (Button) findViewById(R.id.button_ard_logo);
// Spinner click listener
spinner.setOnItemSelectedListener(this);
// Check for Add New Vehicle entry to create menu option in drop down list
checkAddNewVehicle();
// Loading spinner data from database
loadSpinnerData();
//"Go" button actions
btnAdd.setOnClickListener(new View.OnClickListener()
public void onClick(View arg0)
//Starting a new Intent
Intent screenAddVehicle = new Intent(getApplicationContext(), AddVehicleActivity.class);
Intent screenRecordViewing = new Intent(getApplicationContext(), RecordViewing.class);
String SpinnerChoice = spinner.getSelectedItem().toString();
//Sending data to another Activity
//store value of vehicle label for Record Viewing screen
screenRecordViewing.putExtra("vehicleLabel", SpinnerChoice);
//interpret Spinner choice as Menu items
//"Add New Vehicle" always at Position 0
if (SpinnerChoice == spinner.getItemAtPosition(0).toString())
startActivity(screenAddVehicle);
else
//display value of selection if not "Add New Vehicle"
//Toast.makeText(spinner.getContext(), "Selection:" + SpinnerChoice, Toast.LENGTH_LONG).show();
//go to Record Viewing screen
startActivity(screenRecordViewing);
);
//"Logo" button - display program copyright
btnLogo.setOnClickListener(new View.OnClickListener()
public void onClick(View arg0)
Toast.makeText(getApplicationContext(), "FuelTracker is Copyright 2013 by Authentic Ruby Designs", Toast.LENGTH_LONG).show();
);
final View controlsView = findViewById(R.id.fullscreen_content_controls);
final View contentView = findViewById(R.id.fullscreen_content);
// Set up an instance of SystemUiHider to control the system UI for
// this activity.
mSystemUiHider = SystemUiHider.getInstance(this, contentView,
HIDER_FLAGS);
mSystemUiHider.setup();
mSystemUiHider
.setOnVisibilityChangeListener(new SystemUiHider.OnVisibilityChangeListener()
// Cached values.
int mControlsHeight;
int mShortAnimTime;
@Override
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public void onVisibilityChange(boolean visible)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2)
// If the ViewPropertyAnimator API is available
// (Honeycomb MR2 and later), use it to animate the
// in-layout UI controls at the bottom of the
// screen.
if (mControlsHeight == 0)
mControlsHeight = controlsView.getHeight();
if (mShortAnimTime == 0)
mShortAnimTime = getResources().getInteger(
android.R.integer.config_shortAnimTime);
controlsView
.animate()
.translationY(visible ? 0 : mControlsHeight)
.setDuration(mShortAnimTime);
else
// If the ViewPropertyAnimator APIs aren't
// available, simply show or hide the in-layout UI
// controls.
controlsView.setVisibility(visible ? View.VISIBLE
: View.GONE);
if (visible && AUTO_HIDE)
// Schedule a hide().
delayedHide(AUTO_HIDE_DELAY_MILLIS);
);
// Set up the user interaction to manually show or hide the system UI.
contentView.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View view)
if (TOGGLE_ON_CLICK)
mSystemUiHider.toggle();
else
mSystemUiHider.show();
);
// Upon interacting with UI controls, delay any scheduled hide()
// operations to prevent the jarring behavior of controls going away
// while interacting with the UI.
findViewById(R.id.button_go).setOnTouchListener(
mDelayHideTouchListener);
@Override
protected void onPostCreate(Bundle savedInstanceState)
super.onPostCreate(savedInstanceState);
// Trigger the initial hide() shortly after the activity has been
// created, to briefly hint to the user that UI controls
// are available.
//delayedHide(100);
/**
* Touch listener to use for in-layout UI controls to delay hiding the
* system UI. This is to prevent the jarring behavior of controls going away
* while interacting with activity UI.
*/
View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener()
@Override
public boolean onTouch(View view, MotionEvent motionEvent)
if (AUTO_HIDE)
delayedHide(AUTO_HIDE_DELAY_MILLIS);
return false;
;
Handler mHideHandler = new Handler();
Runnable mHideRunnable = new Runnable()
@Override
public void run()
mSystemUiHider.hide();
;
/**
* Schedules a call to hide() in [delay] milliseconds, canceling any
* previously scheduled calls.
*/
private void delayedHide(int delayMillis)
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, delayMillis);
@Override
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2,
long arg3)
// TODO Auto-generated method stub
@Override
public void onNothingSelected(AdapterView<?> arg0)
// TODO Auto-generated method stub
/**
* Function to check that Add New Vehicle is the first label in the DB table
*/
private void checkAddNewVehicle()
// database handler
//DatabaseHandler db = new DatabaseHandler(getApplicationContext());
DatabaseHandler db = DatabaseHandler.getInstance(getApplicationContext());
// table data
List<String> labels = db.getAllLabels();
//Check for "Add New Vehicle" entry at first row of table
if (labels.isEmpty() )
db.insertLabel("Add New Vehicle");
/**
* Function to load the spinner data from SQLite database
* */
private void loadSpinnerData()
// database handler
//DatabaseHandler db = new DatabaseHandler(getApplicationContext());
DatabaseHandler db = DatabaseHandler.getInstance(getApplicationContext());
// Spinner Drop down elements
List<String> labels = db.getAllLabels();
// Creating adapter for spinner
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, labels);
// Drop down layout style - list view with radio button
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// attaching data adapter to spinner
spinner.setAdapter(dataAdapter);
这是我的 DatabaseHandler 类:
package ard.util.fueltracker;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.TableLayout;
public class DatabaseHandler extends SQLiteOpenHelper
//create static instance
private static DatabaseHandler mInstance = null;
// Database Version
private static final int DATABASE_VERSION = 1;
// Database Name
private static final String DATABASE_NAME = "vehicleDatabase";
// Labels table name
private static final String TABLE_LABELS = "labels";
// Fuel Data table name
private static final String TABLE_FUELDATA = "fuel_data";
// Labels Table Columns names
private static final String KEY_ID = "id";
private static final String KEY_NAME = "name";
// Fuel Data Tables Columns names
private static final String KEY_ENTRY = "entry_id";
private static final String FIELD_VEHICLEID = "vehicle_id";
private static final String FIELD_DATE = "date";
private static final String FIELD_FTYPE = "fuel_type";
private static final String FIELD_BRAND = "brand";
private static final String FIELD_PRICE = "price";
private static final String FIELD_KMS = "kms";
private static final String FIELD_LITRES = "litres";
private static final String FIELD_LPER = "l_per";
private static final String FIELD_MPG = "mpg";
// Fuel Data Columns for display - query shorthand
private static final String[] fuelDataDisplayCols = FIELD_DATE, FIELD_FTYPE, FIELD_BRAND, FIELD_PRICE,
FIELD_KMS, FIELD_LITRES, FIELD_LPER, FIELD_MPG ;
public static DatabaseHandler getInstance(Context ctx)
//Use the application context to avoid leaking Activity's context
if (mInstance == null)
mInstance = new DatabaseHandler(ctx.getApplicationContext());
return mInstance;
private DatabaseHandler(Context context)
super(context, DATABASE_NAME, null, DATABASE_VERSION);
// Creating Tables
@Override
public void onCreate(SQLiteDatabase db)
// Category table create query
String CREATE_CATEGORIES_TABLE = "CREATE TABLE " + TABLE_LABELS + "("
+ KEY_ID + " INTEGER PRIMARY KEY," + KEY_NAME + " TEXT)";
db.execSQL(CREATE_CATEGORIES_TABLE);
// Fuel Data table create query
String CREATE_FUELDATA_TABLE = "CREATE TABLE " + TABLE_FUELDATA + "("
+ KEY_ENTRY + " INTEGER PRIMARY KEY," + FIELD_VEHICLEID + " INTEGER,"
+ FIELD_DATE + " TEXT," + FIELD_FTYPE + " TEXT," + FIELD_BRAND +
" TEXT," + FIELD_PRICE + " REAL," + FIELD_KMS + " REAL,"
+ FIELD_LITRES + " REAL," + FIELD_LPER + " REAL," + FIELD_MPG +
" REAL)";
db.execSQL(CREATE_FUELDATA_TABLE);
db.close();
// Upgrading database
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
// Drop older table if existed
db.execSQL("DROP TABLE IF EXISTS " + TABLE_LABELS);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_FUELDATA);
// Create tables again
onCreate(db);
/**
* Inserting new label into Labels table
* */
public void insertLabel(String label)
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_NAME, label);
// Inserting Row
db.insert(TABLE_LABELS, null, values);
db.close(); // Closing database connection
/**
* Inserting entry data into Fuel Data table
*/
//public void insertFuelData(int vehicleID, String date, String ftype, String brand,
// float price, float kms, float litres)
public void insertFuelData(ArrayList<String> fuelEntryData)
SQLiteDatabase dbWrite = this.getWritableDatabase();
ArrayList<String> fuelEntry = fuelEntryData;
ContentValues values = new ContentValues();
//turn array values into individual field values with correct datatype
int vehicleID = getVehicleID(fuelEntry.get(0));
String date = fuelEntry.get(1);
String ftype = fuelEntry.get(2);
String brand = fuelEntry.get(3);
double price = Double.parseDouble((fuelEntry.get(4)));
double kms = Double.parseDouble((fuelEntry.get(5)));
double litres = Double.parseDouble((fuelEntry.get(6)));
//values for calculations
double lper;
double mpg;
//Format calculations to round to one significant digit
DecimalFormat df = new DecimalFormat("###.#");
//calculate Liters/100Kilometres
lper = (100 / (kms / litres));
//calculate U.S. Miles Per Gallon
mpg = (kms / litres) * 2.35;
//Prepare data and Insert row into table
values.put(FIELD_VEHICLEID, vehicleID);
values.put(FIELD_DATE, date);
values.put(FIELD_FTYPE, ftype);
values.put(FIELD_BRAND, brand);
values.put(FIELD_PRICE, price);
values.put(FIELD_KMS, kms);
values.put(FIELD_LITRES, litres);
values.put(FIELD_LPER, df.format(lper));
values.put(FIELD_MPG, df.format(mpg));
dbWrite.insert(TABLE_FUELDATA, null, values);
dbWrite.close();
/**
* Getting all labels
* returns list of labels
* */
public List<String> getAllLabels()
List<String> labels = new ArrayList<String>();
// Select All Query
String selectQuery = "SELECT * FROM " + TABLE_LABELS;
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst())
do
labels.add(cursor.getString(1));
while (cursor.moveToNext());
// closing connection
cursor.close();
db.close();
// returning lables
return labels;
/**
* Get entries based on vehicle id
*/
public List<List<String>> getFuelData(int vehicleID)
//List of Lists - each row/entry of data is a separate List
List<List<String>> fuelDataTable = new ArrayList<List<String>>();
List<String> fuelDataRow = new ArrayList<String>();
//Select Query
String selectQuery = "SELECT * FROM " + TABLE_FUELDATA +
" WHERE vehicle_id = ? ";
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(selectQuery, new String[] String.valueOf(vehicleID) );
// looping through all rows and 9 columns (not incl key) and adding to list
if (cursor.moveToFirst())
do
for(int i = 1; i < 10; i++)
//doesn't work with non-string values
//fuelDataRow.add(cursor.getString(i));
//add each row in its entirety to the List; multi-dimenionsal list
fuelDataTable.add(fuelDataRow);
//empty fuelDataRow
fuelDataRow.clear();
while (cursor.moveToNext());
// closing connection
cursor.close();
db.close();
//return data
return fuelDataTable;
public List<String> getFuelDataRows(int vehicleID)
List<String> fuelDataTable = new ArrayList<String>();
SQLiteDatabase db = this.getReadableDatabase();
//Get data from database table
Cursor c = db.query(TABLE_FUELDATA, fuelDataDisplayCols, " vehicle_id=? ", new String[] String.valueOf(vehicleID) , null, null, FIELD_DATE);
//Insert Header row into Array
fuelDataTable.add("Date");
fuelDataTable.add("Type");
fuelDataTable.add("Brand");
fuelDataTable.add("Price");
fuelDataTable.add("KMs");
fuelDataTable.add("Litres");
fuelDataTable.add("L/100");
fuelDataTable.add("MPG");
//Go to beginning of Cursor data and loop through
c.moveToFirst();
while (!c.isAfterLast())
//add each cell in the row to List array
fuelDataTable.add(c.getString(c.getColumnIndex(FIELD_DATE)));
fuelDataTable.add(c.getString(c.getColumnIndex(FIELD_FTYPE)));
fuelDataTable.add(c.getString(c.getColumnIndex(FIELD_BRAND)));
fuelDataTable.add(String.valueOf(c.getDouble(c.getColumnIndex(FIELD_PRICE))));
fuelDataTable.add(String.valueOf(c.getDouble(c.getColumnIndex(FIELD_KMS))));
fuelDataTable.add(String.valueOf(c.getDouble(c.getColumnIndex(FIELD_LITRES))));
fuelDataTable.add(String.valueOf(c.getDouble(c.getColumnIndex(FIELD_LPER))));
fuelDataTable.add(String.valueOf(c.getDouble(c.getColumnIndex(FIELD_MPG))));
c.moveToNext();
// Make sure to close the cursor
c.close();
db.close();
return fuelDataTable;
public int getVehicleID(String label)
// set variables
SQLiteDatabase dbReader = this.getReadableDatabase();
String vLabel = label;
//Query String
String selectQuery = "SELECT " + KEY_ID + " FROM " + TABLE_LABELS +
" WHERE name = ? ";
Cursor c = dbReader.rawQuery(selectQuery, new String[] vLabel );
//avoid out of bounds exception
c.moveToFirst();
//extract value as integer
int vID = c.getInt(c.getColumnIndex(KEY_ID));
c.close();
return vID;
其中一些可能看起来很丑 - 这是我的第一个应用程序项目,Eclipse 在创建时自动填充了一些方法 - 但它在 Ice Cream Sandwich 中运行良好。
感谢任何线索。
【问题讨论】:
【参考方案1】:public void onCreate()
中不需要db.close()
。这可能会解决您的问题。
【讨论】:
就是这样!我很高兴这是一个如此简单的解决方案。有什么特别的原因让它继续在原来的 ICS 机器上工作,而不是在更新的 Jellybean 上工作吗?是因为操作系统还是因为模拟器软件? 不太确定,但据我所知,对于onCreate(db)
onUpgrade()
等方法,比如您将数据库对象作为参数接收,您不需要显式关闭数据库,因为框架需要照顾这个。以上是关于带有 SQLite 的 Android 应用程序在 ICS 中运行,但在 Jelly Bean 中不运行 - IllegalStateException的主要内容,如果未能解决你的问题,请参考以下文章
带有 SQLite 数据库的 Cordova 应用程序在 iOS 中首次运行后冻结,但在 Android 中运行良好
DataNucleus 支持带有内置 sqlite 或远程数据库的 android?
带有 Sqlite 的 Android - 如何检查表是不是存在以及是不是为空