带有 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 - 如何检查表是不是存在以及是不是为空

sqlite android提取带有电话号码的联系人

带有日期字符串的 Android SQLite rawquery 在 WHERE 子句中不起作用

Android - SQLite - 在 Date1 和 Date2 之间选择