我正在学习Udacity Android数据存储课程,但是我只是碰壁。我们正在制作一个宠物应用程序,用户可以在其中添加,更新和删除宠物。在一个片段中,我们不得不使用一个浮动按钮来转到另一个活动(从Catalog Activity到Edit Activity)。但是当我单击浮动按钮时,出现错误:
执行
时发生错误doInBackground()
我没有使用AsyncTask doInBackground()
方法,但是我知道我的Loader使用的是AsyncTask
类。我还认为问题可能是我试图在doInBackground()
上显示某些内容。据我所知,我无法在后台线程上执行任何GUI工作。但是,我没有直接使用doInBackground()
方法,那么如何更改未使用的内容?
我尝试使用Google搜索,尝试在android开发人员上查看任何内容,尝试在SO上找到一些帖子,但是我没有找到我知道如何应用的解决方案。
我的目录活动
public class CatalogActivity extends AppCompatActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
Uri newUri;
/**
* Identifier for the pet data loader
*/
private static final int PET_LOADER = 0;
PetCursorAdapter mCursorAdapter;
/**
* Database helper that will provide us access to the database
*/
@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);
}
});
// Find the ListView which will be populated with the pet data
ListView petListView = (ListView) findViewById(R.id.list);
// Find and set empty view on the ListView, so that it only shows when the list has 0 items.
View emptyView = findViewById(R.id.empty_view);
petListView.setEmptyView(emptyView);
// Setup an Adapter to create a list item for each row of pet data in the Cursor.
mCursorAdapter = new PetCursorAdapter(this, null);
// Attach the adapter to the ListView.
petListView.setAdapter(mCursorAdapter);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
petListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(CatalogActivity.this, EditorActivity.class);
Uri curentUri = ContentUris.withAppendedId(PetContract.Pets.CONTENT_URI, id);
intent.setData(curentUri);
startActivity(intent);
}
});
getSupportLoaderManager().initLoader(PET_LOADER, null, this);
}
private void insertPet() {
ContentValues contentValues = new ContentValues();
contentValues.put(PetContract.Pets.COLUMN_NAME, "Toto");
contentValues.put(PetContract.Pets.COLUMN_BREED, "Terrier");
contentValues.put(PetContract.Pets.COLUMN_GENDER, PetContract.Pets.GENDER_MALE);
contentValues.put(PetContract.Pets.COLUMN_WEIGHT, 7);
newUri = getApplication().getContentResolver().insert(
PetContract.Pets.CONTENT_URI,
contentValues
);
// Show a toast message depending on whether or not the insertion was successful
if (newUri == null) {
// If the row ID is -1, then there was an error with insertion.
Toast.makeText(this, "Error with saving pet", Toast.LENGTH_SHORT).show();
} else {
// Otherwise, the insertion was successful and we can display a toast with the row ID.
Toast.makeText(this, "Pet saved with row id: ", Toast.LENGTH_SHORT).show();
}
}
@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();
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);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
//ovde su navedene kolone koje zelim da prikazem
String[] projection = {
PetContract.Pets._ID,
PetContract.Pets.COLUMN_NAME,
PetContract.Pets.COLUMN_BREED,
};
return new CursorLoader(
this,
PetContract.Pets.CONTENT_URI,
projection,
null,
null,
null);
}
// This is the Adapter being used to display the list's data.
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mCursorAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mCursorAdapter.swapCursor(null);
}
}
我的编辑器活动:
public class EditorActivity extends AppCompatActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
private Uri mCurrentPetUri;
private static final int PET_LOADER = 0;
public PetHelper mDbHelper;
/**
* 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);
Intent intent = getIntent();
mCurrentPetUri = intent.getData();
if (mCurrentPetUri == null) {
setTitle(getResources().getText(R.string.add_pet_title));
} else setTitle(getResources().getText(R.string.edit_pet_title));
// 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);
getSupportLoaderManager().initLoader(PET_LOADER, null, this);
setupSpinner();
}
private void savetPet() {
String insertName = mNameEditText.getText().toString().trim();
String insertBreed = mBreedEditText.getText().toString().trim();
String insertWeiightAsIteger = mWeightEditText.getText().toString().toString();
int insertWeight = Integer.parseInt(insertWeiightAsIteger);
mDbHelper = new PetHelper(this);
ContentValues contentValues = new ContentValues();
contentValues.put(PetContract.Pets.COLUMN_NAME, insertName);
contentValues.put(PetContract.Pets.COLUMN_BREED, insertBreed);
contentValues.put(PetContract.Pets.COLUMN_GENDER, mGender);
contentValues.put(PetContract.Pets.COLUMN_WEIGHT, insertWeight);
if (mCurrentPetUri == null) {
Uri newUri = getApplication().getContentResolver().insert(
PetContract.Pets.CONTENT_URI,
contentValues);
// Show a toast message depending on whether or not the insertion was successful
if (newUri == null) {
// If the row ID is -1, then there was an error with insertion.
Toast.makeText(this, "Error with saving pet", Toast.LENGTH_SHORT).show();
} else {
// Otherwise, the insertion was successful and we can display a toast with the row ID.
Toast.makeText(this, "Pet saved with row id: " + mCurrentPetUri, Toast.LENGTH_SHORT).show();
}
} else { // Otherwise this is an EXISTING pet, so update the pet with content URI: mCurrentPetUri
// and pass in the new ContentValues. Pass in null for the selection and selection args
// because mCurrentPetUri will already identify the correct row in the database that
// we want to modify.
int rowsAffected = getContentResolver().update(mCurrentPetUri, contentValues, null, null);
// Show a toast message depending on whether or not the update was successful.
if (rowsAffected == 0) {
// If no rows were affected, then there was an error with the update.
Toast.makeText(this, getString(R.string.editor_update_pet_failed),
Toast.LENGTH_SHORT).show();
} else {
// Otherwise, the update was successful and we can display a toast.
Toast.makeText(this, getString(R.string.editor_update_pet_successful),
Toast.LENGTH_SHORT).show();
}
}
}
/**
* 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 = PetContract.Pets.GENDER_MALE; // Male
} else if (selection.equals(getString(R.string.gender_female))) {
mGender = PetContract.Pets.GENDER_FEMALE; // Female
} else {
mGender = PetContract.Pets.GENDER_UNKOWN; // Unknown
}
}
}
// Because AdapterView is an abstract class, onNothingSelected must be defined
@Override
public void onNothingSelected(AdapterView<?> parent) {
mGender = 0; // Unknown
}
});
}
@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:
// Save new pet
savetPet();
finish();
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);
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
// Since the editor shows all pet attributes, define a projection that contains
// all columns from the pet table
String[] projection = {
PetContract.Pets._ID,
PetContract.Pets.COLUMN_NAME,
PetContract.Pets.COLUMN_BREED,
PetContract.Pets.COLUMN_GENDER,
PetContract.Pets.COLUMN_WEIGHT};
// This loader will execute the ContentProvider's query method on a background thread
return new CursorLoader(this, // Parent activity context
mCurrentPetUri, // Query the content URI for the current pet
projection, // Columns to include in the resulting Cursor
null, // No selection clause
null, // No selection arguments
null); // Default sort order
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// Bail early if the cursor is null or there is less than 1 row in the cursor
if (cursor == null || cursor.getCount() < 1) {
return;
}
// Proceed with moving to the first row of the cursor and reading data from it
// (This should be the only row in the cursor)
if (cursor.moveToFirst()) {
// Find the columns of pet attributes that we're interested in
int nameColumnIndex = cursor.getColumnIndex(PetContract.Pets.COLUMN_NAME);
int breedColumnIndex = cursor.getColumnIndex(PetContract.Pets.COLUMN_BREED);
int genderColumnIndex = cursor.getColumnIndex(PetContract.Pets.COLUMN_GENDER);
int weightColumnIndex = cursor.getColumnIndex(PetContract.Pets.COLUMN_WEIGHT);
// Extract out the value from the Cursor for the given column index
String name = cursor.getString(nameColumnIndex);
String breed = cursor.getString(breedColumnIndex);
int gender = cursor.getInt(genderColumnIndex);
int weight = cursor.getInt(weightColumnIndex);
// Update the views on the screen with the values from the database
mNameEditText.setText(name);
mBreedEditText.setText(breed);
mWeightEditText.setText(Integer.toString(weight));
// Gender is a dropdown spinner, so map the constant value from the database
// into one of the dropdown options (0 is Unknown, 1 is Male, 2 is Female).
// Then call setSelection() so that option is displayed on screen as the current selection.
switch (gender) {
case PetContract.Pets.GENDER_MALE:
mGenderSpinner.setSelection(1);
break;
case PetContract.Pets.GENDER_FEMALE:
mGenderSpinner.setSelection(2);
break;
default:
mGenderSpinner.setSelection(0);
break;
}
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// If the loader is invalidated, clear out all the data from the input fields.
mNameEditText.setText("");
mBreedEditText.setText("");
mWeightEditText.setText("");
mGenderSpinner.setSelection(0); // Select "Unknown" gender
}
}
logcat
FATAL EXCEPTION: AsyncTask #2
Process: com.example.android.pets, PID: 31266
java.lang.RuntimeException: An error occurred while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:354)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
at java.util.concurrent.FutureTask.run(FutureTask.java:271)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
Caused by: java.lang.NullPointerException: uri
at
com.android.internal.util.Preconditions.checkNotNull(Preconditions.java:128)
at android.content.ContentResolver.query(ContentResolver.java:787)
at android.content.ContentResolver.query(ContentResolver.java:753)
at android.content.CursorLoader.loadInBackground(CursorLoader.java:68)
at android.content.CursorLoader.loadInBackground(CursorLoader.java:45)
at android.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:319)
at android.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:73)
at android.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:61)
at android.os.AsyncTask$2.call(AsyncTask.java:333)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)