简体   繁体   English

应用程序因空光标而崩溃 - 内容提供者

[英]App crashes because of null Cursor - Content Providers

I am following the data storage lessons from udacity.我正在学习 udacity 的数据存储课程。 And in their app, you insert an animal's details such as name, breed, gender and weight.在他们的应用程序中,您可以插入动物的详细信息,例如名称、品种、性别和体重。 In the CatalogActivity I insert some dummy data,and read them from the provider through the ContentResolver.在 CatalogActivity 中,我插入了一些虚拟数据,并通过 ContentResolver 从提供者读取它们。 And in the EditorActivity I insert the data manually.在 EditorActivity 中,我手动插入数据。 Basically the query method returns a Cursor object that holds the rows of our table.基本上,查询方法返回一个 Cursor 对象,该对象保存我们表的行。 The ContentResolver passes the query method to the PetProvider. ContentResolver 将查询方法传递给 PetProvider。 Then the PetProvider performs two operations for now.然后 PetProvider 现在执行两个操作。 Query and Insert.查询和插入。 This is the code of my provider.这是我的提供者的代码。

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

Then in CatalogueActivity, I use the query(...) method of the ContentResolver.然后在 CatalogueActivity 中,我使用 ContentResolver 的 query(...) 方法。 So所以

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

And the EditorActiviyEditorActivity

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

The exception occurs in CatalogActivity. 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;
  }

} }

Any ideas?有什么想法吗?

Thanks,谢谢,

Theo.提奥。

Your content URI is not matching while query, so it's throwing IllegalArgumentException , and your cursor is null, but you are trying to close the cursor, so it's crashing查询时您的内容URI不匹配,因此它抛出IllegalArgumentException ,并且您的游标为空,但您试图关闭游标,因此它崩溃了

In final check null before close,在关闭前的最后检查 null 中,

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

Check your content URI while querying in CatalogActivity ,CatalogActivity查询时检查您的内容 URI,

Update your getType() too its returning null更新你的getType()也返回 null

I just did this and my app worked:我只是这样做,我的应用程序工作:

In PetContract.PetEntry add在 PetContract.PetEntry 添加

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

then in catalogue activity and edit activity use CONTENT_URI instead of BASE_CONTENT_URI .然后在目录活动和编辑活动中使用CONTENT_URI而不是BASE_CONTENT_URI

<provider android:name="com.example.android.pets.PetProvider"
            android:authorities="com.example.android.pets">
        </provider>

Just use this provider tag in manifest file.只需在清单文件中使用此提供程序标记。 I have used it.我已经用过了。 and it is working fine它工作正常

Use this in your suriMatcher :在您的suriMatcher使用它:

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

instead of this:而不是这个:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM