我正在关注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;
}
}
有什么想法吗?
谢谢,
西奥。
答案 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
,
更新您的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);