【问题标题】:NullPointerException: uri - LoaderManager / SQLiteManager / ListViewNullPointerException: uri - LoaderManager / SQLiteManager / ListView
【发布时间】:2016-03-08 15:15:17
【问题描述】:

使用 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$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.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$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) 

这是包含 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,正如我之前所说,如果我直接从适配器填充并且不使用加载器,它可以工作。

【问题讨论】:

    标签: android sqlite listview simplecursoradapter android-loadermanager


    【解决方案1】:

    来自CursorLoader API

    public CursorLoader(上下文上下文)

    在 API 级别 11 中添加 创建一个空的未指定的 CursorLoader。您必须遵循调用 setUri(Uri)setSelection(String) 等来指定要执行的查询。

    因此,在您的自定义 SQLLiteCursorLoader 类中,您必须在 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,而AbstractCursorLoaderlink)中的等效方法是:

    @Override
    public Cursor loadInBackground() {
        Cursor cursor=buildCursor();
    
        if (cursor!=null) {
          // Ensure the cursor window is filled
          cursor.getCount();
        }
    
        return(cursor);
    }
    

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

    【讨论】:

    • 这很有趣,我会研究一下,谢谢。 SQLiteCursorLoader 来自非常流行(并且使用良好)的 SQLLiteCursorLoader 项目:github.com/commonsguy/cwac-loaderex 虽然它确实说它已经停产了。
    • @JohnStock 请看看我上面更新的答案。
    • 就是这样!哇.. 典型的错误,看不到树林中的树木!.. 谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多