CursorLoader 第二次不刷新数据

CursorLoader does not refresh data the second time

我正在使用三种 ContentProviders 方法:bulkInsert()query()delete()(用于删除旧数据)。 这是我的 ContentProvider class:

public class MoviesProvider extends ContentProvider {
    private static final String LOG_TAG = MoviesProvider.class.getSimpleName();

    // The URI Matcher used by this content provider.
    private static final UriMatcher sUriMatcher = buildUriMatcher();
    private MoviesDbHelper mOpenHelper;

    static final int MOVIES = 100;
    static final int MOVIE_ID = 101;

    static UriMatcher buildUriMatcher() {
        // 1) The code passed into the constructor represents the code to return for the root
        // URI.  It's common to use NO_MATCH as the code for this case. Add the constructor below.
        final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
        final String authority = CONTENT_AUTHORITY;

        // 2) Use the addURI function to match each of the types.
        matcher.addURI(authority, PATH_MOVIES, MOVIES);
        matcher.addURI(authority, PATH_MOVIES + "/#", MOVIE_ID);

        return matcher;
    }

    @Override
    public boolean onCreate() {
        mOpenHelper = new MoviesDbHelper(getContext());
        return true;
    }


    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        // Here's the switch statement that, given a URI, will determine what kind of request it is,
        // and query the database accordingly.
        Cursor retCursor;
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
        queryBuilder.setTables(MoviesEntry.TABLE_NAME);
        switch (sUriMatcher.match(uri)) {
            case MOVIE_ID:
            {
                queryBuilder.appendWhere(MoviesEntry._ID + "=" + uri.getLastPathSegment());
                break;
            }
            case MOVIES:
            {
                break;
            }
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);

        }

        retCursor = queryBuilder.query(mOpenHelper.getReadableDatabase(),
                projection, selection, selectionArgs, null, null, sortOrder);

        retCursor.setNotificationUri(getContext().getContentResolver(), uri);

        return retCursor;
    }

    @Override
    public String getType(Uri uri) {
        // Use the Uri Matcher to determine what kind of URI this is.
        final int match = sUriMatcher.match(uri);
        switch (match) {
            case MOVIES:
                return MoviesEntry.CONTENT_TYPE;
            case MOVIE_ID:
                return MoviesEntry.CONTENT_ITEM_TYPE;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        Uri returnUri;

        switch (match) {
            case MOVIES: {
                long _id = db.insert(MoviesEntry.TABLE_NAME, null, values);

                if (_id > 0)
                    returnUri = MoviesEntry.buildMoviesUri(_id);
                else
                    throw new android.database.SQLException("Failed to insert row into " + uri);

                break;
            }
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);

        return returnUri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        //Start by getting a writable database
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        //Use the uriMatcher to match the MOVIES URI's we are going to handle.
        final int match = sUriMatcher.match(uri);

        int rowsDeleted;
        //A null value deletes all rows.  In my implementation of this, I only notified
        // the uri listeners (using the content resolver) if the rowsDeleted != 0 or the selection
        // is null.
        switch (match) {
            case MOVIES:
                rowsDeleted = db.delete(MoviesEntry.TABLE_NAME, selection, selectionArgs);
                break;
            case MOVIE_ID:
                String id = uri.getLastPathSegment();
                if (TextUtils.isEmpty(selection)) {
                    rowsDeleted = db.delete(MoviesEntry.TABLE_NAME,
                            MoviesEntry._ID + "=" + id,
                            null);
                } else {
                    rowsDeleted = db.delete(MoviesEntry.TABLE_NAME,
                            MoviesEntry._ID + "=" + id + " and " + selection, selectionArgs);
                }
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }

        Log.d(LOG_TAG, "Deleted rows: " + rowsDeleted);
        Log.d(LOG_TAG, "Uri: " + uri.toString());

        if (rowsDeleted != 0) getContext().getContentResolver().notifyChange(uri, null);

        return rowsDeleted;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        //This is a lot like the delete function.  We return the number of rows impacted
        // by the update.
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();

        final int match = sUriMatcher.match(uri);
        int rowsUpdated;

        switch (match) {
            case MOVIES:
                rowsUpdated = db.update(MoviesEntry.TABLE_NAME, values, selection, selectionArgs);
                break;
            case MOVIE_ID:
                String id = uri.getLastPathSegment();
                if (TextUtils.isEmpty(selection)) {
                    rowsUpdated = db.update(MoviesEntry.TABLE_NAME,
                            values,
                            MoviesEntry._ID + "=" + id,
                            null);
                } else {
                    rowsUpdated = db.update(MoviesEntry.TABLE_NAME,
                            values,
                            MoviesEntry._ID + "=" + id
                            + " and "
                            + selection,
                            selectionArgs);
                }
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }

        if (rowsUpdated != 0) getContext().getContentResolver().notifyChange(uri, null);

        return rowsUpdated;
    }

    @Override
    public int bulkInsert(Uri uri, ContentValues[] values) {
        final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        switch (match) {
            case MOVIES:
                db.beginTransaction();
                int returnCount = 0;
                try {
                    for (ContentValues value: values) {
                        long _id = db.insert(MoviesEntry.TABLE_NAME, null, value);
                        if (_id != -1) {
                            returnCount++;
                        }
                    }
                    db.setTransactionSuccessful();
                } finally {
                    db.endTransaction();
                }
                getContext().getContentResolver().notifyChange(uri, null);
                return returnCount;
            default:
                return super.bulkInsert(uri, values);
        }
    }

    // You do not need to call this method. This is a method specifically to assist the testing
    // framework in running smoothly. You can read more at:
    // http://developer.android.com/reference/android/content/ContentProvider.html#shutdown()
    @Override
    @TargetApi(11)
    public void shutdown() {
        mOpenHelper.close();
        super.shutdown();
    }
}

这是一个片段,我正在其中更新我的数据:

for (Movie movie: moviesList) {
    ContentValues movieValues = new ContentValues();

    timeCounter++;

    movieValues.put(MoviesEntry.COLUMN_TITLE, movie.getTitle());
    movieValues.put(MoviesEntry.COLUMN_DIRECTORS, movie.getDirectors().get(0).getName());
    movieValues.put(MoviesEntry.COLUMN_GENRES, movie.getGenres().toString());
    movieValues.put(MoviesEntry.COLUMN_WRITERS, movie.getWriters().get(0).getName());
    movieValues.put(MoviesEntry.COLUMN_COUNTRIES, movie.getCountries().get(0));
    movieValues.put(MoviesEntry.COLUMN_YEAR, movie.getYear());
    movieValues.put(MoviesEntry.COLUMN_RUNTIME, movie.getRuntime().get(0));
    movieValues.put(MoviesEntry.COLUMN_URL_POSTER, movie.getUrlPoster());
    movieValues.put(MoviesEntry.COLUMN_RATING, movie.getRating());
    movieValues.put(MoviesEntry.COLUMN_PLOT, movie.getPlot());
    movieValues.put(MoviesEntry.COLUMN_URL_IMDB, movie.getUrlIMDB());
    movieValues.put(MoviesEntry.COLUMN_DATE, dateTime + timeCounter);

    Log.d(LOG_TAG, "" + dateTime);

    downloadPosters(movie.getUrlPoster());

    cVVector.add(movieValues);
}

//add to database
if (cVVector.size() > 0) {
    ContentValues[] cvArray = new ContentValues[cVVector.size()];
    cVVector.toArray(cvArray);
    int inserted = getContext().getContentResolver()
            .bulkInsert(MoviesEntry.CONTENT_URI, cvArray);
    Log.d(LOG_TAG, "Inserted: " + inserted);
    Log.d(LOG_TAG, "DateTime: " + dateTime);

    Cursor cursor = getContext().getContentResolver().query(MoviesEntry.CONTENT_URI,
            null, null, null, null);
    if (cursor.getCount() > numberOfMovies) {
        // delete old data so we don't build up an endless history
        int deleted = getContext().getContentResolver().delete(MoviesEntry.CONTENT_URI,
                MoviesEntry.COLUMN_DATE + "<?",
                new String[] {Long.toString(dateTime)});
        Log.d(LOG_TAG, "Deleted: " + deleted);
        Log.d(LOG_TAG, "DateTime#2: " + dateTime);
    }

    updateNotifications();
}

这是我在主要片段中的片段,我在其中实现了 RecycleView 和 Loaders(工作正常):

public class MoviesFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {

    private static final String LOG_TAG = MoviesFragment.class.getSimpleName();

    private MoviesAdapter mMoviesAdapter;
    private RecyclerView mRecyclerView;
    private RecyclerView.LayoutManager mLayoutManager;

    private static final String[] CARDS_PROJECTION = {
            MoviesEntry._ID,
            MoviesEntry.COLUMN_URL_POSTER,
            MoviesEntry.COLUMN_TITLE,
            MoviesEntry.COLUMN_DIRECTORS,
            MoviesEntry.COLUMN_GENRES,
            MoviesEntry.COLUMN_RATING,
            MoviesEntry.COLUMN_YEAR
    };

    private static final int CARDS_LOADER = 0;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.movies_list, container, false);

        mRecyclerView = (RecyclerView) rootView.findViewById(R.id.list_of_movies);
        mLayoutManager = new LinearLayoutManager(getActivity());
        mRecyclerView.setLayoutManager(mLayoutManager);

        mMoviesAdapter = new MoviesAdapter(getActivity(), null, 0);
        mRecyclerView.setAdapter(mMoviesAdapter);

        mRecyclerView.addOnItemTouchListener(
                new RecyclerItemClickListener(getActivity(), new RecyclerItemClickListener.OnItemClickListener() {
                    @Override
                    public void onItemClick(View view, int position) {

                        Log.d(LOG_TAG, "POSITION: " + position);
                        //Because position starts from 0
                        Cursor cursor = mMoviesAdapter.getItem(position + 1);

                        Uri uri = MoviesEntry.buildMoviesUri((long) cursor.getPosition());
                        Intent intent = new Intent(getActivity(), DetailActivity.class);
                        intent.putExtra(Utility.ID_KEY, uri.toString());
                        startActivity(intent);
                    }
                })
        );

        return rootView;
    }

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


    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        Uri baseUri = MoviesEntry.CONTENT_URI;

        return new CursorLoader(getActivity(), baseUri, CARDS_PROJECTION, null, null, null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        mMoviesAdapter.swapCursor(data);
    }

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

最后是我的细节片段 class,我在其中使用加载程序更新数据:

public class DetailFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {

    private static final String LOG_TAG = DetailFragment.class.getSimpleName();
    private static final String DETAIL_URI = "URI";
    private Uri mUri;

    private static final int DETAIL_LOADER = 0;

    private static final String[] DETAIL_COLUMNS = {
            MoviesEntry.TABLE_NAME + "." + MoviesEntry._ID,
            MoviesEntry.COLUMN_URL_POSTER,
            MoviesEntry.COLUMN_TITLE,
            MoviesEntry.COLUMN_COUNTRIES,
            MoviesEntry.COLUMN_YEAR,
            MoviesEntry.COLUMN_RUNTIME,
            MoviesEntry.COLUMN_RATING,
            MoviesEntry.COLUMN_GENRES,
            MoviesEntry.COLUMN_DIRECTORS,
            MoviesEntry.COLUMN_WRITERS,
            MoviesEntry.COLUMN_PLOT,
            MoviesEntry.COLUMN_URL_IMDB
    };


    private static final String SHARE_HASHTAG = " #WhatToWatch";
    private ShareActionProvider mShareActionProvider;
    private String mMovieShareInfo;

    private ImageView mPosterView;
    private TextView mTitleView;
    private TextView mCountriesView;
    private TextView mYearView;
    private TextView mRuntimeView;
    private TextView mRatingView;
    private TextView mGenresView;
    private TextView mDirectorsView;
    private TextView mWritersView;
    private TextView mPlotView;

    public DetailFragment() {
        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_detail, container, false);


        Bundle extras = getActivity().getIntent().getExtras();
        if (extras != null) {
            mUri = Uri.parse(extras.getString(Utility.ID_KEY));
        }

        mPosterView = (ImageView) rootView.findViewById(R.id.poster);
        mTitleView = (TextView) rootView.findViewById(R.id.tv_title);
        mCountriesView = (TextView) rootView.findViewById(R.id.country);
        mYearView = (TextView) rootView.findViewById(R.id.release_year);
        mRuntimeView = (TextView) rootView.findViewById(R.id.runtime);
        mRatingView = (TextView) rootView.findViewById(R.id.rating);
        mGenresView = (TextView) rootView.findViewById(R.id.genre);
        mDirectorsView = (TextView) rootView.findViewById(R.id.director);
        mWritersView = (TextView) rootView.findViewById(R.id.writers);
        mPlotView = (TextView) rootView.findViewById(R.id.plot);

        return rootView;
    }

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

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // Inflate the menu; this adds items to the action bar if it is present.
        inflater.inflate(R.menu.detailfragment, menu);
        // Retrieve the share menu item
        MenuItem menuItem = menu.findItem(R.id.action_share);
        // Get the provider and hold onto it to set/change the share intent.
        mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(menuItem);

        if (mMovieShareInfo == null) {
            mShareActionProvider.setShareIntent(createShareMovieIntent());
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {

        if (null != mUri) {
            return new CursorLoader(
                    getActivity(),
                    mUri,
                    DETAIL_COLUMNS,
                    null,
                    null,
                    null
            );
        }

        return null;
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

        updateData(data);

    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        Log.d(LOG_TAG, "Data reseted!");
    }

    private Intent createShareMovieIntent() {
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        shareIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
        shareIntent.setType("text/plain");
        shareIntent.putExtra(Intent.EXTRA_TEXT, mMovieShareInfo + SHARE_HASHTAG);

        return shareIntent;
    }

    private void updateData(Cursor data) {
        if (data != null && data.moveToFirst()) {
            String posterUrl = data.getString(data.getColumnIndex(MoviesEntry.COLUMN_URL_POSTER));
            String title = data.getString(data.getColumnIndex(MoviesEntry.COLUMN_TITLE));
            String country = data.getString(data.getColumnIndex(MoviesEntry.COLUMN_COUNTRIES));
            String year = data.getString(data.getColumnIndex(MoviesEntry.COLUMN_YEAR));
            String runtime = data.getString(data.getColumnIndex(MoviesEntry.COLUMN_RUNTIME));
            String rating = data.getString(data.getColumnIndex(MoviesEntry.COLUMN_RATING));
            String genres = data.getString(data.getColumnIndex(MoviesEntry.COLUMN_GENRES));
            String director = data.getString(data.getColumnIndex(MoviesEntry.COLUMN_DIRECTORS));
            String writer = data.getString(data.getColumnIndex(MoviesEntry.COLUMN_WRITERS));
            String plot = data.getString(data.getColumnIndex(MoviesEntry.COLUMN_PLOT));

            Picasso.with(getActivity())
                    .load(posterUrl)
                    .placeholder(R.drawable.progress_animation)
                    .resize(205, 310)
                    .centerCrop()
                    .into(mPosterView);
            mTitleView.setText(title);
            mCountriesView.setText(country);
            mYearView.setText(year);
            mRuntimeView.setText(runtime);
            mRatingView.setText(rating);
            mGenresView.setText(genres);
            mDirectorsView.setText(director);
            mWritersView.setText(writer);
            mPlotView.setText(plot);

            mMovieShareInfo = "Awesome movie «" + title + "»" + "\n" +
                    "which IMDB rating is " + rating + "\n" +
                    "And directed by " + director + "\n" + genres + "\n";

            if (mShareActionProvider != null) {
                mShareActionProvider.setShareIntent(createShareMovieIntent());
            }
        }
    }
}

应用程序首次启动后(只有 bulkInsert() 方法调用时),一切正常,加载程序正确加载数据。但是当我更新数据时(当 bulkInsert(),然后 query()delete() (删除旧数据)方法调用时) CursorLoader 不会将新数据加载到 DetailFragment 并且它看起来像这样:

也许您可以保留对 CursorLoader 的引用,当您的数据发生变化时,您可以调用 Loader.onContentChanged() 强制重新加载数据。

因为在 SQLite 中自动递增的 id 计数器不会重置,所以我只是替换了这个:

Cursor cursor = mMoviesAdapter.getItem(position + 1);
Uri uri = MoviesEntry.buildMoviesUri((long) cursor.getPosition());

通过这个:

Uri uri = MoviesEntry.buildMoviesUri(mMoviesAdapter.getItemId(position));

在我的主要片段中。一切正常。