NullPointerException: uri - LoaderManager / SQLiteManager / ListView

NullPointerException: uri - LoaderManager / SQLiteManager / ListView

使用 SQLiteCursorLoader 和 LoaderManager 在 Fragment 中填充 ListView 时出现此错误。 如果我从适配器中删除 SQLiteCursorLoader 和直接填充,一切正常。 如果我尝试自定义适配器,我会得到同样的错误。 错误似乎是 CursorLoader 的 mUri 成员从未填充并留空,SQLiteCursorLoader 从不调用设置 mUri 的 CursorLoader 上的构造函数。

public CursorLoader(Context context, Uri uri, String[] projection, String selection,
        String[] selectionArgs, String sortOrder) {
    super(context);
    mObserver = new ForceLoadContentObserver();
    mUri = uri;
    mProjection = projection;
    mSelection = selection;
    mSelectionArgs = selectionArgs;
    mSortOrder = sortOrder;
}

相反,它调用了仅传递上下文的构造函数版本。

public CursorLoader(Context context) {
    super(context);
    mObserver = new ForceLoadContentObserver();
}

这里是错误

03-08 15:00:36.906 2601-2683/com.overunitystudios.medbuddy E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
                                                                             Process: com.overunitystudios.medbuddy, PID: 2601
                                                                             java.lang.RuntimeException: An error occurred while executing doInBackground()
                                                                                 at android.os.AsyncTask.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.NullPointerException: uri
                                                                                 at com.android.internal.util.Preconditions.checkNotNull(Preconditions.java:60)
                                                                                 at android.content.ContentResolver.query(ContentResolver.java:474)
                                                                                 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.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) 

这是包含 ListView

的片段的代码
public class fragment_medications extends Fragment  implements LoaderManager.LoaderCallbacks<Cursor> {

    private database_helper_medications dbHelper;
    private SimpleCursorAdapter listAdapter;
    private SQLLiteCursorLoader loader=null;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        //return super.onCreateView(inflater, container, savedInstanceState);

        View view = inflater.inflate(R.layout.fragment_medications_layout, container,false);

        dbHelper = new database_helper_medications(getActivity());

        listAdapter = new SimpleCursorAdapter(getActivity(),R.layout.list_item_medications,null, new String[] {database_helper_medications.FIELD_MEDICATION}, new int[] {R.id.tvMedication},0);

        ListView lvMedications = (ListView) view.findViewById(R.id.lvMedications);
        lvMedications.setAdapter(listAdapter);

        return view;
    }

    @Override public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getLoaderManager().initLoader(0, null, this);
    }

    //TODO: 03-08 00:16:48.563 3508-3516/com.overunitystudios.medbuddy W/SQLiteConnectionPool: A SQLiteConnection object for database '/data/user/0/com.overunitystudios.medbuddy/databases/medbuddy' was leaked!  Please fix your application to end transactions in progress properly and to close the database when it is no longer needed.

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (dbHelper!=null) dbHelper.close();
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        loader=new SQLLiteCursorLoader(getActivity(), dbHelper, dbHelper.DATABASE_SELECT_ALL, null);
        return(loader);
    }


    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        this.loader=(SQLLiteCursorLoader)loader;
        listAdapter.changeCursor(data);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        listAdapter.changeCursor(null);
    }

}

数据库助手

public class database_helper_medications extends SQLiteOpenHelper {

    private static final String DATABASE_NAME = "medbuddy";
    private static final String TABLE_NAME = "medications";
    private static final int DATABASE_VERSION = 1;

    public final static String FIELD_ID="_id"; //Changed table to use a column named "_id" instead of "id" as CursorAdapter must have an _id column in the table it is using.
    public final static String FIELD_MEDICATION="name";

    private SQLiteDatabase database;

    // Database creation sql statement
    private static final String DATABASE_CREATE = "create table if not exists " + TABLE_NAME + " ( " + FIELD_ID + " integer primary key," + FIELD_MEDICATION + " text not null);";
    public static final String DATABASE_SELECT_ALL = "select " + FIELD_ID + "," + FIELD_MEDICATION + " from " + TABLE_NAME + " order by " + FIELD_MEDICATION;

    public database_helper_medications(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        database = this.getWritableDatabase();
    }

    // Method is called during creation of the database
    @Override
    public void onCreate(SQLiteDatabase database) {
        database.execSQL(DATABASE_CREATE);
    }

    // Method is called during an upgrade of the database,
    @Override
    public void onUpgrade(SQLiteDatabase database,int oldVersion,int newVersion){
        database.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME + "");
        onCreate(database);
    }

    public long createSingleRecord(Integer id, String medication){
        ContentValues values = new ContentValues();
        values.put(FIELD_ID, id);
        values.put(FIELD_MEDICATION, medication);
        return database.insert(TABLE_NAME, null, values);
    }

    public long createSingleRecord(String medication){
        ContentValues values = new ContentValues();
        //values.put(FIELD_ID, id); //We dont need to do this as it's marked autoincrement.
        values.put(FIELD_MEDICATION, medication);
        return database.insert(TABLE_NAME, null, values);
    }


    public Cursor selectAllRecords() {
        String[] cols = new String[] {FIELD_ID, FIELD_MEDICATION};
        Cursor mCursor = database.query(true, TABLE_NAME, cols,null, null, null, null, null, null);
        if (mCursor != null)
            mCursor.moveToFirst();

        return mCursor; // iterate to get each value.
    }

    public void DumpToLog()
    {
        Log.i("DumpToLog", DatabaseUtils.dumpCursorToString(selectAllRecords()));
    }
}

这里是SQLLiteCursorLoader(我改名为流行的SQLiteCursorLoader)

public class SQLLiteCursorLoader extends CursorLoader
{

    SQLiteOpenHelper db=null;
    String rawQuery=null;
    String[] args=null;
    /**
     * Creates a fully-specified SQLiteCursorLoader. See
     * {@link SQLiteDatabase#rawQuery(SQLiteDatabase, String, String[])
     * SQLiteDatabase.rawQuery()} for documentation on the
     * meaning of the parameters. These will be passed as-is
     * to that call.
     */
    public SQLLiteCursorLoader(Context context, SQLiteOpenHelper db, String rawQuery, String[] args)
    {
        super(context);
        this.db=db;
        this.rawQuery=rawQuery;
        this.args=args;
    }

    /**
     * Runs on a worker thread and performs the actual
     * database query to retrieve the Cursor.
     */
    //@Override
    protected Cursor buildCursor() {
        return(db.getReadableDatabase().rawQuery(rawQuery, args));
    }

    /**
     * Writes a semi-user-readable roster of contents to
     * supplied output.
     */
    @Override
    public void dump(String prefix, FileDescriptor fd,PrintWriter writer, String[] args)
    {
        super.dump(prefix, fd, writer, args);
        writer.print(prefix);
        writer.print("rawQuery=");
        writer.println(rawQuery);
        writer.print(prefix);
        writer.print("args=");
        writer.println(Arrays.toString(args));
    }

    public void insert(String table, String nullColumnHack, ContentValues values)
    {
        buildInsertTask(this).execute(db, table, nullColumnHack, values);
    }

    public void update(String table, ContentValues values,String whereClause, String[] whereArgs)
    {
        buildUpdateTask(this).execute(db, table, values, whereClause,whereArgs);
    }

    public void replace(String table, String nullColumnHack,ContentValues values)
    {
        buildReplaceTask(this).execute(db, table, nullColumnHack, values);
    }

    public void delete(String table, String whereClause,String[] whereArgs)
    {
        buildDeleteTask(this).execute(db, table, whereClause, whereArgs);
    }

    public void execSQL(String sql, Object[] bindArgs)
    {
        buildExecSQLTask(this).execute(db, sql, bindArgs);
    }

    protected ContentChangingTask buildInsertTask(SQLLiteCursorLoader loader)
    {
        return(new InsertTask(loader));
    }

    protected ContentChangingTask buildUpdateTask(SQLLiteCursorLoader loader)
    {
        return(new UpdateTask(loader));
    }

    protected ContentChangingTask buildReplaceTask(SQLLiteCursorLoader loader)
    {
        return(new ReplaceTask(loader));
    }

    protected ContentChangingTask buildDeleteTask(SQLLiteCursorLoader loader)
    {
        return(new DeleteTask(loader));
    }

    protected ContentChangingTask buildExecSQLTask(SQLLiteCursorLoader loader)
    {
        return(new ExecSQLTask(loader));
    }

    protected static class InsertTask extends ContentChangingTask
    {
        InsertTask(SQLLiteCursorLoader loader)
        {
            super(loader);
        }

        @Override
        protected Void doInBackground(Object... params) {
            SQLiteOpenHelper db=(SQLiteOpenHelper)params[0];
            String table=(String)params[1];
            String nullColumnHack=(String)params[2];
            ContentValues values=(ContentValues)params[3];

            db.getWritableDatabase().insert(table, nullColumnHack, values);

            return(null);
        }
    }

    protected static class UpdateTask extends ContentChangingTask
    {
        UpdateTask(SQLLiteCursorLoader loader)
        {
            super(loader);
        }

        @Override
        protected Void doInBackground(Object... params) {
            SQLiteOpenHelper db=(SQLiteOpenHelper)params[0];
            String table=(String)params[1];
            ContentValues values=(ContentValues)params[2];
            String where=(String)params[3];
            String[] whereParams=(String[])params[4];

            db.getWritableDatabase().update(table, values, where, whereParams);

            return(null);
        }
    }

    protected static class ReplaceTask extends ContentChangingTask
    {
        ReplaceTask(SQLLiteCursorLoader loader)
        {
            super(loader);
        }

        @Override
        protected Void doInBackground(Object... params) {
            SQLiteOpenHelper db=(SQLiteOpenHelper)params[0];
            String table=(String)params[1];
            String nullColumnHack=(String)params[2];
            ContentValues values=(ContentValues)params[3];

            db.getWritableDatabase().replace(table, nullColumnHack, values);

            return(null);
        }
    }

    protected static class DeleteTask extends ContentChangingTask
    {
        DeleteTask(SQLLiteCursorLoader loader)
        {
            super(loader);
        }

        @Override
        protected Void doInBackground(Object... params) {
            SQLiteOpenHelper db=(SQLiteOpenHelper)params[0];
            String table=(String)params[1];
            String where=(String)params[2];
            String[] whereParams=(String[])params[3];

            db.getWritableDatabase().delete(table, where, whereParams);

            return(null);
        }
    }

    protected static class ExecSQLTask extends ContentChangingTask
    {
        ExecSQLTask(SQLLiteCursorLoader loader)
        {
            super(loader);
        }

        @Override
        protected Void doInBackground(Object... params) {
            SQLiteOpenHelper db=(SQLiteOpenHelper)params[0];
            String sql=(String)params[1];
            Object[] bindParams=(Object[])params[2];

            db.getWritableDatabase().execSQL(sql, bindParams);

            return(null);
        }
    }
}

我不会包括布局 XML,正如我之前所说,如果我直接从适配器填充而不使用加载程序,它就可以工作。

来自CursorLoader API,

public CursorLoader (Context context)

Added in API level 11 Creates an empty unspecified CursorLoader. You must follow this with calls to setUri(Uri), setSelection(String), etc to specify the query to perform.

因此在您的自定义 SQLLiteCursorLoader class 中,您必须在 super(context);

之后在构造函数中显式调用 setUri(Uri)

更新

看看你在下面的评论中提到的 github 项目,我发现了一个不同之处。

你的

public class SQLLiteCursorLoader extends CursorLoader 

而 github 的

public class SQLiteCursorLoader extends AbstractCursorLoader

猜猜看,CursorLoader 有一个方法 (link)

/* Runs on a worker thread */
@Override
public Cursor loadInBackground() {
    Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
            mSelectionArgs, mSortOrder);
    if (cursor != null) {
        // Ensure the cursor window is filled
        cursor.getCount();
        registerContentObserver(cursor, mObserver);
    }
    return cursor;
}

使用 mUri 这就是为什么它不能为 null,而 AbstractCursorLoader (link) 中的等效方法是:

@Override
public Cursor loadInBackground() {
    Cursor cursor=buildCursor();

    if (cursor!=null) {
      // Ensure the cursor window is filled
      cursor.getCount();
    }

    return(cursor);
}

所以我想如果你纠正你的 SQLLiteCursorLoader class 中的继承(让它扩展 AbstractCursorLoader)你最终会解决你的问题。