2017-08-15 63 views
0

我正在關注來自udacity的數據存儲課程。在他們的應用程序中,您可以插入動物的詳細信息,如姓名,品種,性別和體重。在CatalogActivity中,我插入一些虛擬數據,並通過ContentResolver從提供者中讀取它們。在EditorActivity中,我手動插入數據。 基本上查詢方法返回一個包含我們表的行的Cursor對象。 ContentResolver將查詢方法傳遞給PetProvider。然後PetProvider現在執行兩個操作。查詢和插入。這是我的提供者的代碼。應用程序因null遊標而崩潰 - 內容提供商

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的的查詢(...)方法。所以

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在CatalogActivity發生

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

的異常。

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

您是否嘗試過任何調試,如果是的話? –

+0

那麼遊標是空的。我在這裏放置了一個斷點。idColumnIndex = cursor.getColumnIndex(PetEntry._ID);並調試給我一個遊標= null – Theo

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

檢查內容URI在查詢中CatalogActivity

更新您的getType()過它的返回null

+0

URI似乎是正確的。 – Theo

+0

遊標在此處爲null遊標cursor = getContentResolver()。query(...); – Theo

+0

是的,在finally塊中如果你把try catch和catch ** IllegalArgumentException **並且檢查 –