我收到了一个DoInBackground错误,因为我添加了游标通知uri但在此之前我收到错误我的数据库被锁定。这是什么意思我似乎无法理解为什么它是这样的,因为没有背景方法,我正在使用Loader。 活动是
ProductProvider.java
package com.example.bahubali.inventoryapp.data;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
public class ProductProvider extends ContentProvider {
//Tag for the log messages
public static final String LOG_TAG = ProductProvider.class.getSimpleName();
//Database helper that will provide access to the database.
private ProductDbHelper mDbHelper;
/** URI matcher code for the content URI for the pets table */
public static final int PRODUCTS = 100;
/** URI matcher code for the content URI for a single pet in the pets table */
public static final int PRODUCT_ID = 101;
Context context;
/** 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(ProductContract.CONTENT_AUTHORITY, ProductContract.PATH_PRODUCTS, PRODUCTS);
// 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(ProductContract.CONTENT_AUTHORITY, ProductContract.PATH_PRODUCTS + "/#", PRODUCT_ID);
}
/**
* Initialize the provider and the database helper object.
*/
@Override
public boolean onCreate()
{
/*
* Creates a new helper object. This method always returns quickly.
* until SQLiteOpenHelper.getWritableDatabase is called
*/
mDbHelper = new ProductDbHelper(getContext());
context = getContext();
return true;
}
/*
*Perform the query to the given URI.Use the given projection,selection,selectionArgs, and sort
* order.
*/
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// Get readable database
SQLiteDatabase database = mDbHelper.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 PRODUCTS:
// 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.
// Perform database query on pets
cursor = database.query(ProductContract.ProductEntry.TABLE_NAME,
projection,selection,selectionArgs,null,null,sortOrder);
break;
case PRODUCT_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 = PRODUCT_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(ProductContract.ProductEntry.TABLE_NAME,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
break;
default:
throw new IllegalArgumentException("Cannot query unknown URI " + uri);
}
//Set notification URI to the cursor,
//So we know what content URI the cursor was created for.
//If the data at this URI changes, then we need to update the cursor.
cursor.setNotificationUri(getContext().getContentResolver(),null);
cursor.close();
return cursor;
}
/*
*Return the MIME type of data for the content URI.
*/
@Nullable
@Override
public String getType(@NonNull Uri uri) {
final int match = sUriMatcher.match(uri);
switch (match){
case PRODUCTS:
return ProductContract.ProductEntry.CONTENT_LIST_TYPE;
case PRODUCT_ID:
return ProductContract.ProductEntry.CONTENT_ITEM_TYPE;
default:
throw new IllegalStateException("UNKNOWN URI" + uri + " with match" + match);
}
}
//Added a commit here
/*
*Insert a new data into the provider with the given Constructor.
*/
@Nullable
@Override
public Uri insert(@NonNull Uri uri,
@Nullable ContentValues contentValues) {
final int match = sUriMatcher.match(uri);
switch (match){
case PRODUCTS:
return insertProduct(uri,contentValues);
default:
throw new IllegalArgumentException("Insertion is not supported for " +uri);
}
}
/*
Insert a product in the database with the given content values.Return the new content URI
for that specific row in the database.
*/
private Uri insertProduct(Uri uri, ContentValues contentValues){
//Get writeable database
SQLiteDatabase database = mDbHelper.getWritableDatabase();
//Insert the new product with the given values
long id = database.insert(ProductContract.ProductEntry.TABLE_NAME,null,contentValues);
//If the Id is -1, then the insertion failed.Log on an error to return null
if (id == -1){
Log.e(LOG_TAG,"Failed to insert a row" + uri);
return null;
}
//Check that the name is not null
String name = contentValues.getAsString(ProductContract.ProductEntry.COLUMN_PRODUCT_NAME);
if (name == null){
throw new IllegalArgumentException("Product requires a name");
}
//Check that the price is not null
Integer price = contentValues.getAsInteger(ProductContract.ProductEntry.COLUMN_PRODUCT_PRICE);
if (price != null && price < 0 ){
throw new IllegalArgumentException("Product requires valid price");
}
//Notify all listeners that the data has changed for the product content URI
getContext().getContentResolver().notifyChange(uri,null);
return ContentUris.withAppendedId(uri,id);
}
/*
*Delete the data at the given selection and selection arguements.
*/
@Override
public int delete(@NonNull Uri uri,
@Nullable String selection,
@Nullable String[] selectionArgs) {
// Get writeable database
SQLiteDatabase database = mDbHelper.getWritableDatabase();
//Track the no. of rows that were deleted
int rowsDeleted;
final int match = sUriMatcher.match(uri);
switch (match) {
case PRODUCTS:
// Delete all rows that match the selection and selection args
rowsDeleted = database.delete(ProductContract.ProductEntry.TABLE_NAME,selection,selectionArgs);
break;
case PRODUCT_ID:
// Delete a single row given by the ID in the URI
selection = ProductContract.ProductEntry._ID + "=?";
selectionArgs = new String[]{String.valueOf(ContentUris.parseId(uri))};
rowsDeleted = database.delete(ProductContract.ProductEntry.TABLE_NAME,selection,selectionArgs);
break;
default:
throw new IllegalArgumentException("Deletion is not supported for " + uri);
}
//If 1 or more rows were deleted, then notify all listeners that the data at the given
//given uri has changed
if (rowsDeleted != 0){
getContext().getContentResolver().notifyChange(uri,null);
}
return rowsDeleted;
}
/*
*Updates the data at the given selection and the selection arguements, with the new content
* values.
*/
@Override
public int update(@NonNull Uri uri,
@Nullable ContentValues contentValues,
@Nullable String selection,
@Nullable String[] selectionArgs) {
final int match = sUriMatcher.match(uri);
switch (match) {
case PRODUCTS:
return updateProduct(uri, contentValues, selection, selectionArgs);
case PRODUCT_ID:
// For the PRODUCT_D code, extract out the ID from the URI,
// so we know which row to update. Selection will be "_id=?" and selection
// arguments will be a String array containing the actual ID.
selection = ProductContract.ProductEntry._ID + "=?";
selectionArgs = new String[] { String.valueOf(ContentUris.parseId(uri)) };
return updateProduct(uri, contentValues, selection, selectionArgs);
default:
throw new IllegalArgumentException("Update is not supported for " + uri);
}
}
/**
* Update pets in the database with the given content values. Apply the changes to the rows
* specified in the selection and selection arguments (which could be 0 or 1 or more pets).
* Return the number of rows that were successfully updated.
*/
private int updateProduct(Uri uri,ContentValues contentValues,String selection,String[] selectionArgs){
// If the {@link ProductEntry#COLUMN_PRODUCT_NAME} key is present,
// check that the name value is not null.
if (contentValues.containsKey(ProductContract.ProductEntry.COLUMN_PRODUCT_NAME)) {
String name = contentValues.getAsString(ProductContract.ProductEntry.COLUMN_PRODUCT_NAME);
if (name == null) {
throw new IllegalArgumentException("Product requires a name");
}
}
// If the {@link ProductEntry#COLUMN_PRODUCT_PRICE} key is present,
// check that the price value is valid.
if (contentValues.containsKey(ProductContract.ProductEntry.COLUMN_PRODUCT_PRICE)) {
// Check that the weight is greater than or equal to 0 kg
Integer price = contentValues.getAsInteger(ProductContract.ProductEntry.COLUMN_PRODUCT_PRICE);
if (price!= null && price < 0) {
throw new IllegalArgumentException("Product requires valid weight");
}
}
// If there are no values to update, then don't try to update the database
if (contentValues.size() == 0) {
return 0;
}
// Otherwise, get writeable database to update the data
SQLiteDatabase database = mDbHelper.getWritableDatabase();
//perform the update on the database and the get the number of rows affected
int rowsUpdated = database.update(ProductContract.ProductEntry.TABLE_NAME,contentValues,selection
,selectionArgs);
//If 1 or more rows were updated, then notify all the listeners that the data at the
//given URI has changed
if (rowsUpdated != 0){
getContext().getContentResolver().notifyChange(uri,null);
}
//Return the no. of rows updated
return rowsUpdated;
}
}
CatalogActivity.java
package com.example.bahubali.inventoryapp;
import android.app.LoaderManager;
import android.content.ContentValues;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import com.example.bahubali.inventoryapp.data.ProductContract;
import com.example.bahubali.inventoryapp.data.ProductDbHelper;
public class CatalogActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
/** Database helper that will provide us access to the database */
private ProductDbHelper mDbHelper;
//Define an PetCursor adapter
private ProductCursorAdapter mCursorAdapter;
//The integer loader constant for Product loader
private static final int PRODUCT_LOADER = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_catalog);
//Setup fab to open editor activity
FloatingActionButton fab = 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);
}
});
// To access our database, we instantiate our subclass of SQLiteOpenHelper
// and pass the context, which is the current activity.
mDbHelper = new ProductDbHelper(this);
//Find the listView which will be with the product data
ListView productListView = (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);
productListView.setEmptyView(emptyView);
//Setup an Adapter to create a list item for each row of product data in the Cursor.
//There is no product data yet(until the loader finishes) so pass in the null Cursor.
mCursorAdapter = new ProductCursorAdapter(this,null);
productListView.setAdapter(mCursorAdapter);
//Kick off the loader
getLoaderManager().initLoader(PRODUCT_LOADER, null,this);
}
@Override
protected void onStart() {
super.onStart();
}
/*helper method to insert hardcoded data in the database*/
private void insertProduct(){
//Get the database in the write mode
SQLiteDatabase db = mDbHelper.getWritableDatabase();
//Create a contentValues where column names are the keys,
//and the boat attributes are values
ContentValues values = new ContentValues();
values.put(ProductContract.ProductEntry.COLUMN_PRODUCT_NAME,"Boat");
values.put(ProductContract.ProductEntry.COLUMN_PRODUCT_PRICE,600);
values.put(ProductContract.ProductEntry.COLUMN_PRODUCT_QUANTITY,4);
values.put(ProductContract.ProductEntry.COLUMN_PRODUCT_SUPPLIER,"Amazon");
/*
Insert a new row in the database ,returning the id of that new row
The first argument for the db.insert() is the products table name
The second arguments provides the name of the column in which the framework
can insert Null if the event that the content values is empty(if this is set to "null",then
the framework will not insert a row when there is no values.
*/
long newRowId = db.insert(ProductContract.ProductEntry.TABLE_NAME,null,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_new_data:
insertProduct();
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 i, Bundle bundle) {
//Define a projection that specifies the column from the table we care about
String projection[] = {
ProductContract.ProductEntry._ID,
ProductContract.ProductEntry.COLUMN_PRODUCT_NAME,
ProductContract.ProductEntry.COLUMN_PRODUCT_SUPPLIER
};
//This loader will implement the ContentProvider's method in background thread
return new CursorLoader(this,
ProductContract.ProductEntry.CONTENT_URI,
projection,
null,
null,
null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
//Update {@link PetCursorAdapter} with this new cursor containing updated product data
mCursorAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
//Callback called when the data needs to be deleted
mCursorAdapter.swapCursor(null);
}
}
}
}
LogCat:
10-14 12:20:31.131 2900-3121/com.example.bahubali.inventoryapp E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
Process: com.example.bahubali.inventoryapp, PID: 2900
java.lang.RuntimeException: An error occurred while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:309)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
at java.util.concurrent.FutureTask.run(FutureTask.java:242)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.IllegalArgumentException: You must pass a valid uri and observer
at android.os.Parcel.readException(Parcel.java:1624)
at android.os.Parcel.readException(Parcel.java:1573)
at android.content.IContentService$Stub$Proxy.registerContentObserver(IContentService.java:713)
at android.content.ContentResolver.registerContentObserver(ContentResolver.java:1605)
at android.database.AbstractCursor.setNotificationUri(AbstractCursor.java:402)
at android.database.AbstractCursor.setNotificationUri(AbstractCursor.java:390)
at com.example.bahubali.inventoryapp.data.ProductProvider.query(ProductProvider.java:131)
at android.content.ContentProvider.query(ContentProvider.java:1017)
at android.content.ContentProvider$Transport.query(ContentProvider.java:238)
at android.content.ContentResolver.query(ContentResolver.java:491)
at android.content.CursorLoader.loadInBackground(CursorLoader.java:64)
at android.content.CursorLoader.loadInBackground(CursorLoader.java:56)
at android.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:312)
at android.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:69)
at android.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:66)
at android.os.AsyncTask$2.call(AsyncTask.java:295)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)