質問

I've developed an app for Android which stores car filling information in an SQLite database. Everything seems to work fine on my 4.0.3 phone and my 4.0.3 AVD, but I would like it to also work in Jelly Bean and newer versions. The app really doesn't do anything very complicated. When I fire the app up in the emulated Jelly Bean device I get a FATAL EXCEPTION error:

09-05 01:45:40.311: W/dalvikvm(1062): threadid=1: thread exiting with uncaught exception >(group=0x414c4700) 09-05 01:45:40.331: E/AndroidRuntime(1062): FATAL EXCEPTION: main 09-05 01:45:40.331: E/AndroidRuntime(1062): java.lang.RuntimeException: Unable to start >activity ComponentInfo{ard.util.fueltracker/ard.util.fueltracker.TitleScreenActivity}: >java.lang.IllegalStateException: attempt to re-open an already-closed object: >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)

In my initial searches in Google I thought I had Instance issues somehow, so I followed Approach #1 here (http://www.androiddesignpatterns.com/2012/05/correctly-managing-your-sqlite-database.html), but this doesn't seem to make a difference. Still works in ICS with the changes too.

Here's the code from my 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);
    }



}

And here's my DatabaseHandler class:

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;
    }
}

Some of it probably looks ugly - this is my first app project and Eclipse auto-populated some of the methods upon creation - but it is working OK in Ice Cream Sandwich.

Thanks for any clues.

役に立ちましたか?

解決

db.close() is not needed in public void onCreate(). This will probably fix your problem.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top