应用程序崩溃是因为null Cursor - Content Providers

时间:2017-08-15 16:34:59

标签: android android-sqlite android-contentprovider

我正在关注udacity的数据存储课程。在他们的应用程序中,您可以插入动物的详细信息,例如姓名,品种,性别和体重。在CatalogActivity中,我插入了一些虚拟数据,并通过ContentResolver从提供程序中读取它们。在EditorActivity中,我手动插入数据。 基本上,查询方法返回一个Cursor对象,该对象保存表的行。 ContentResolver将查询方法传递给PetProvider。然后PetProvider现在执行两个操作。查询和插入。这是我的提供商的代码。

PetProvider

        /**
         * {@link ContentProvider} for Pets app.
        */
        public class PetProvider extends ContentProvider {

            private PetHelper mHelper;
            /** Tag for the log messages */
            public static final String LOG_TAG = PetProvider.class.getSimpleName();

            /**
             * URI matcher code for the content URI for the pets table
             */
            public static final int PETS = 100;

            /**
             * URI matcher code for the content URI for a single pet in the pets table
             */
            public static final int PET_ID = 101;

            /** URI matcher object to match a context URI to a corresponding code.
             * The input passed into the constructor represents the code to return for the root URI.
             * It's common to use NO_MATCH as the input for this case.
             */
            private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

            // Static initializer. This is run the first time anything is called from this class.
            static{
                // The calls to addURI() go here, for all of the content URI patterns that the provider
                // should recognize. All paths added to the UriMatcher have a corresponding code to return
                // when a match is found.

                // The content URI of the form "content://com.example.android.pets/pets" will map to the
                // integer code {@link #PETS}. This URI is used to provide access to MULTIPLE rows
                // of the pets table.
                sUriMatcher.addURI(PetContract.CONTENT_AUTHORITY,PetContract.PATH_PETS,PETS);

                // The content URI of the form "content://com.example.android.pets/pets/#" will map to the
                // integer code {@link #PETS_ID}. This URI is used to provide access to ONE single row
                // of the pets table.

                // In this case, the "#" wildcard is used where "#" can be substituted for an integer.
                // For example, "content://com.example.android.pets/pets/3" matches, but
                // "content://com.example.android.pets/pets" (without a number at the end) doesn't match.
                sUriMatcher.addURI(PetContract.CONTENT_AUTHORITY, PetContract.PATH_PETS + "/#", PET_ID);
            }

            /**
             * Initialize the provider and the database helper object.
             */
            @Override
            public boolean onCreate() {

                mHelper = new PetHelper(getContext());
                return true;
            }

            /**
             * Perform the query for the given URI. Use the given projection, selection, selection arguments, and sort order.
             */
            @Override
            public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                                String sortOrder) {
                // Get readable database
                SQLiteDatabase database = mHelper.getReadableDatabase();

                // This cursor will hold the result of the query
                Cursor cursor;

                // Figure out if the URI matcher can match the URI to a specific code
                int match = sUriMatcher.match(uri);
                switch (match) {
                    case PETS:
                        // For the PETS code, query the pets table directly with the given
                        // projection, selection, selection arguments, and sort order. The cursor
                        // could contain multiple rows of the pets table.
                        cursor = database.query(PetContract.PetEntry.TABLE_NAME, projection, selection, selectionArgs,
                                null, null, sortOrder);
                        break;
                    case PET_ID:
                        // For the PET_ID code, extract out the ID from the URI.
                        // For an example URI such as "content://com.example.android.pets/pets/3",
                        // the selection will be "_id=?" and the selection argument will be a
                        // String array containing the actual ID of 3 in this case.
                        //
                        // For every "?" in the selection, we need to have an element in the selection
                        // arguments that will fill in the "?". Since we have 1 question mark in the
                        // selection, we have 1 String in the selection arguments' String array.
                        selection = PetContract.PetEntry._ID + "=?";
                        selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) };

                        // This will perform a query on the pets table where the _id equals 3 to return a
                        // Cursor containing that row of the table.
                        cursor = database.query(PetContract.PetEntry.TABLE_NAME, projection, selection, selectionArgs,
                                null, null, sortOrder);
                        break;
                    default:
                        throw new IllegalArgumentException("Cannot query unknown URI " + uri);
                }
                return cursor;
            }

            /**
             * Insert new data into the provider with the given ContentValues.
             */
            @Override
            public Uri insert(Uri uri, ContentValues contentValues) {
                final int match = sUriMatcher.match(uri);
                switch (match) {
                    case PETS:
                        return insertPet(uri, contentValues);
                    default:
                        throw new IllegalArgumentException("Insertion is not supported for " + uri);
                }
            }

            private Uri insertPet(Uri uri, ContentValues values) {
                // Get writeable database
                SQLiteDatabase database = mHelper.getWritableDatabase();

                // Insert the new pet with the given values
                long id = database.insert(PetContract.PetEntry.TABLE_NAME, null, values);
                // If the ID is -1, then the insertion failed. Log an error and return null.
                if (id == -1) {
                    Log.e(LOG_TAG, "Failed to insert row for " + uri);
                    return null;
                }

                // Return the new URI with the ID (of the newly inserted row) appended at the end
                return ContentUris.withAppendedId(uri, id);
            }
            /**
             * Updates the data at the given selection and selection arguments, with the new ContentValues.
             */
            @Override
            public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
                return 0;
            }

            /**
             * Delete the data at the given selection and selection arguments.
             */
            @Override
            public int delete(Uri uri, String selection, String[] selectionArgs) {
                return 0;
            }

            /**
             * Returns the MIME type of data for the content URI.
             */
            @Override
            public String getType(Uri uri) {
                return null;
            }
        }

然后在CatalogueActivity中,我使用ContentResolver的query(...)方法。所以

CatalogActivity

public class CatalogActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_catalog);

    // Setup FAB to open EditorActivity
    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent(CatalogActivity.this, EditorActivity.class);
            startActivity(intent);
        }
    });
}

@Override
protected void onStart() {
    super.onStart();
    displayDatabaseInfo();
}

/**
 * Temporary helper method to display information in the onscreen TextView about the state of
 * the pets database.
 */
private void displayDatabaseInfo() {
    // Define a projection that specifies which columns from the database
    // you will actually use after this query.
    String[] projection = {
            PetEntry._ID,
            PetEntry.COLUMN_PET_NAME,
            PetEntry.COLUMN_PET_BREED,
            PetEntry.COLUMN_PET_GENDER,
            PetEntry.COLUMN_PET_WEIGHT };

    // Perform a query on the provider using the ContentResolver.
    // Use the {@link PetEntry#CONTENT_URI} to access the pet data.
    Cursor cursor = getContentResolver().query(
            PetEntry.CONTENT_URI,   // The content URI of the words table
            projection,             // The columns to return for each row
            null,                   // Selection criteria
            null,                   // Selection criteria
            null);                  // The sort order for the returned rows

    TextView displayView = (TextView) findViewById(R.id.text_view_pet);

    try {
        // Create a header in the Text View that looks like this:
        //
        // The pets table contains <number of rows in Cursor> pets.
        // _id - name - breed - gender - weight
        //
        // In the while loop below, iterate through the rows of the cursor and display
        // the information from each column in this order.
        displayView.setText("The pets table contains " + cursor.getCount() + " pets.\n\n");
        displayView.append(PetEntry._ID + " - " +
                PetEntry.COLUMN_PET_NAME + " - " +
                PetEntry.COLUMN_PET_BREED + " - " +
                PetEntry.COLUMN_PET_GENDER + " - " +
                PetEntry.COLUMN_PET_WEIGHT + "\n");

        // Figure out the index of each column
        int idColumnIndex = cursor.getColumnIndex(PetEntry._ID);
        int nameColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_NAME);
        int breedColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_BREED);
        int genderColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_GENDER);
        int weightColumnIndex = cursor.getColumnIndex(PetEntry.COLUMN_PET_WEIGHT);

        // Iterate through all the returned rows in the cursor
        while (cursor.moveToNext()) {
            // Use that index to extract the String or Int value of the word
            // at the current row the cursor is on.
            int currentID = cursor.getInt(idColumnIndex);
            String currentName = cursor.getString(nameColumnIndex);
            String currentBreed = cursor.getString(breedColumnIndex);
            int currentGender = cursor.getInt(genderColumnIndex);
            int currentWeight = cursor.getInt(weightColumnIndex);
            // Display the values from each column of the current row in the cursor in the TextView
            displayView.append(("\n" + currentID + " - " +
                    currentName + " - " +
                    currentBreed + " - " +
                    currentGender + " - " +
                    currentWeight));
        }
    } finally {
        // Always close the cursor when you're done reading from it. This releases all its
        // resources and makes it invalid.
        cursor.close();
    }
}

/**
 * Helper method to insert hardcoded pet data into the database. For debugging purposes only.
 */
private void insertPet() {
    // Create a ContentValues object where column names are the keys,
    // and Toto's pet attributes are the values.
    ContentValues values = new ContentValues();
    values.put(PetEntry.COLUMN_PET_NAME, "Toto");
    values.put(PetEntry.COLUMN_PET_BREED, "Terrier");
    values.put(PetEntry.COLUMN_PET_GENDER, PetEntry.GENDER_MALE);
    values.put(PetEntry.COLUMN_PET_WEIGHT, 7);

    // Insert a new row for Toto into the provider using the ContentResolver.
    // Use the {@link PetEntry#CONTENT_URI} to indicate that we want to insert
    // into the pets database table.
    // Receive the new content URI that will allow us to access Toto's data in the future.
    Uri newUri = getContentResolver().insert(PetEntry.CONTENT_URI, values);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu options from the res/menu/menu_catalog.xml file.
    // This adds menu items to the app bar.
    getMenuInflater().inflate(R.menu.menu_catalog, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // User clicked on a menu option in the app bar overflow menu
    switch (item.getItemId()) {
        // Respond to a click on the "Insert dummy data" menu option
        case R.id.action_insert_dummy_data:
            insertPet();
            displayDatabaseInfo();
            return true;
        // Respond to a click on the "Delete all entries" menu option
        case R.id.action_delete_all_entries:
            // Do nothing for now
            return true;
    }
    return super.onOptionsItemSelected(item);
 }
}

EditorActiviy

**
* Allows user to create a new pet or edit an existing one.
*/
public class EditorActivity extends AppCompatActivity {

/** EditText field to enter the pet's name */
private EditText mNameEditText;

/** EditText field to enter the pet's breed */
private EditText mBreedEditText;

/** EditText field to enter the pet's weight */
private EditText mWeightEditText;

/** EditText field to enter the pet's gender */
private Spinner mGenderSpinner;

/**
 * Gender of the pet. The possible values are:
 * 0 for unknown gender, 1 for male, 2 for female.
 */
private int mGender = 0;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_editor);

    // Find all relevant views that we will need to read user input from
    mNameEditText = (EditText) findViewById(R.id.edit_pet_name);
    mBreedEditText = (EditText) findViewById(R.id.edit_pet_breed);
    mWeightEditText = (EditText) findViewById(R.id.edit_pet_weight);
    mGenderSpinner = (Spinner) findViewById(R.id.spinner_gender);

    setupSpinner();
}

/**
 * Setup the dropdown spinner that allows the user to select the gender of the pet.
 */
private void setupSpinner() {
    // Create adapter for spinner. The list options are from the String array it will use
    // the spinner will use the default layout
    ArrayAdapter genderSpinnerAdapter = ArrayAdapter.createFromResource(this,
            R.array.array_gender_options, android.R.layout.simple_spinner_item);

    // Specify dropdown layout style - simple list view with 1 item per line
    genderSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);

    // Apply the adapter to the spinner
    mGenderSpinner.setAdapter(genderSpinnerAdapter);

    // Set the integer mSelected to the constant values
    mGenderSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            String selection = (String) parent.getItemAtPosition(position);
            if (!TextUtils.isEmpty(selection)) {
                if (selection.equals(getString(R.string.gender_male))) {
                    mGender = 1; // Male
                } else if (selection.equals(getString(R.string.gender_female))) {
                    mGender = 2; // Female
                } else {
                    mGender = 0; // Unknown
                }
            }
        }

        // Because AdapterView is an abstract class, onNothingSelected must be defined
        @Override
        public void onNothingSelected(AdapterView<?> parent) {
            mGender = 0; // Unknown
        }
    });
}
/**
 *  Get user input from editor and save new pet into database.
*/
private void insertPet() {
    // Read from input fields
    // Use trim to eliminate leading or trailing white space
    String nameString = mNameEditText.getText().toString().trim();
    String breedString = mBreedEditText.getText().toString().trim();
    String weightString = mWeightEditText.getText().toString().trim();
    int weight = Integer.parseInt(weightString);

    // Create a ContentValues object where column names are the keys,
    // and pet attributes from the editor are the values.
    ContentValues values = new ContentValues();
    values.put(PetEntry.COLUMN_PET_NAME, nameString);
    values.put(PetEntry.COLUMN_PET_BREED, breedString);
    values.put(PetEntry.COLUMN_PET_GENDER, mGender);
    values.put(PetEntry.COLUMN_PET_WEIGHT, weight);

    // Insert a new pet into the provider, returning the content URI for the new pet.
    Uri newUri = getContentResolver().insert(PetEntry.CONTENT_URI, values);

    // Show a toast message depending on whether or not the insertion was successful
    if (newUri == null) {
        // If the new content URI is null, then there was an error with insertion.
        Toast.makeText(this, getString(R.string.editor_insert_pet_failed),
                Toast.LENGTH_SHORT).show();
    } else {
        // Otherwise, the insertion was successful and we can display a toast.
        Toast.makeText(this, getString(R.string.editor_insert_pet_successful),
                Toast.LENGTH_SHORT).show();
    }
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu options from the res/menu/menu_editor.xml file.
    // This adds menu items to the app bar.
    getMenuInflater().inflate(R.menu.menu_editor, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // User clicked on a menu option in the app bar overflow menu
    switch (item.getItemId()) {
        // Respond to a click on the "Save" menu option
        case R.id.action_save:
            insertPet();
            return true;
        // Respond to a click on the "Delete" menu option
        case R.id.action_delete:
            // Do nothing for now
            return true;
        // Respond to a click on the "Up" arrow button in the app bar
        case android.R.id.home:
            // Navigate back to parent activity (CatalogActivity)
            NavUtils.navigateUpFromSameTask(this);
            return true;
    }
    return super.onOptionsItemSelected(item);
 }
}

CatalogActivity中发生异常。

Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'void android.database.Cursor.close()' on a null object reference

PetContract

public final class PetContract {

// To prevent someone from accidentally instantiating the contract class,
// give it an empty constructor.
private PetContract() {}

/**
 * The "Content authority" is a name for the entire content provider, similar to the
 * relationship between a domain name and its website.  A convenient string to use for the
 * content authority is the package name for the app, which is guaranteed to be unique on the
 * device.
 */
public static final String CONTENT_AUTHORITY = "com.example.android.pets";

/**
 * Use CONTENT_AUTHORITY to create the base of all URI's which apps will use to contact
 * the content provider.
 */
public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);

/**
 * Possible path (appended to base content URI for possible URI's)
 * For instance, content://com.example.android.pets/pets/ is a valid path for
 * looking at pet data. content://com.example.android.pets/staff/ will fail,
 * as the ContentProvider hasn't been given any information on what to do with "staff".
 */
public static final String PATH_PETS = "pets";

/**
 * Inner class that defines constant values for the pets database table.
 * Each entry in the table represents a single pet.
 */
public static final class PetEntry implements BaseColumns {

    /** The content URI to access the pet data in the provider */
    public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, PATH_PETS);

    /** Name of database table for pets */
    public final static String TABLE_NAME = "pets";

    /**
     * Unique ID number for the pet (only for use in the database table).
     *
     * Type: INTEGER
     */
    public final static String _ID = BaseColumns._ID;

    /**
     * Name of the pet.
     *
     * Type: TEXT
     */
    public final static String COLUMN_PET_NAME ="name";

    /**
     * Breed of the pet.
     *
     * Type: TEXT
     */
    public final static String COLUMN_PET_BREED = "breed";

    /**
     * Gender of the pet.
     *
     * The only possible values are {@link #GENDER_UNKNOWN}, {@link #GENDER_MALE},
     * or {@link #GENDER_FEMALE}.
     *
     * Type: INTEGER
     */
    public final static String COLUMN_PET_GENDER = "gender";

    /**
     * Weight of the pet.
     *
     * Type: INTEGER
     */
    public final static String COLUMN_PET_WEIGHT = "weight";

    /**
     * Possible values for the gender of the pet.
     */
    public static final int GENDER_UNKNOWN = 0;
    public static final int GENDER_MALE = 1;
    public static final int GENDER_FEMALE = 2;
  }

}

有什么想法吗?

谢谢,

西奥。

3 个答案:

答案 0 :(得分:1)

您的内容URI在查询时不匹配,因此它正在抛出IllegalArgumentException,并且您的光标为空,但您正在尝试关闭光标,因此它正在崩溃

在结束前的最后一次检查中,

finally {
    // Always close the cursor when you're done reading from it. This releases all its
    // resources and makes it invalid.
    if(cursor != null)
        cursor.close();
}

CatalogActivity

中查询时检查您的内容URI

更新您的getType()并返回null

答案 1 :(得分:0)

我刚刚做到了,我的应用程序正常工作

在PetContract.PetEntry中添加

public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, PATH_PETS);

然后在目录活动和编辑活动中使用CONTENT_URI而不是BASE_CONTENT_URI

答案 2 :(得分:0)

在您的 suriMatcher 中使用它:

sUriMatcher.addURI(PetContract.CONTENT_AUTHORITY, PetContract.PATH_PETS + "/#", PET_ID);

而不是这个:

sURIMatcher.addURI(petcontract.CONTENT_AUTHORITY, pet_Path,PETS_ID);