通知异步任务加载器数据更改

notifying async task loader of data changes

在过去的五天里,我已经尽我所能来了解如何让 AsyncTask Loader 获得使用内容提供程序的数据更改通知,但没有运气,而且 Whosebug 中提供的所有答案都没有得到我的帮助。 我真的需要你的帮助。

我将 AsyncTaskLoader 与内容提供程序一起使用以从数据库中检索数据,将其添加到我的数组列表中并使用 recyclerview 适配器更新我的片段。同步适配器从服务器异步下载新数据并存储在数据库中。通常,我希望当新数据插入数据库时​​,AsyncTask Loader 应该立即得到通知,但我已经尝试了所有可能的方法,但从未调用对此负责的 takeContentChanged() 方法,并且无法更新新数据。

在下面查看我的实现:

public class NewsListLoader extends AsyncTaskLoader<List<News>> {
private static final String LOG_TAG = NewsListLoader.class.getSimpleName();
private List<News> mNews;
private ContentResolver mContentResolver;
private Cursor mCursor;
private Uri mUri;
private String mSortOrder;

public NewsListLoader(Context context, Uri uri, String sortOrder, ContentResolver contentResolver) {
    super(context);
    mContentResolver = contentResolver;
    mUri = uri;
    mSortOrder = sortOrder;

}

@Override
public List<News> loadInBackground() {
    String[] projection = {NewsContract.NewsEntry.NEWS_TABLE_NAME
            + "." + NewsContract.NewsEntry._ID,
            NewsContract.NewsEntry.NEWS_TITLE_COLUMN,
            NewsContract.NewsEntry.NEWS_CATEGORY_COLUMN,
            NewsContract.NewsEntry.NEWS_IMAGEURL_COLUMN,
            NewsContract.NewsEntry.NEWS_URL_COLUMN,
            NewsContract.NewsEntry.NEWS_DATE_COLUMN,
            NewsContract.NewsEntry.NEWS_AUTHOR_COLUMN};
           // NewsContract.SourceEntry.SOURCE_COLUMN};

    List<News> entries = new ArrayList<News>();




    mCursor = mContentResolver.query(NewsContract.NewsEntry.NEWS_TABLE_CONTENT_URI,
            projection, null, null, mSortOrder);

    if (mCursor != null){
        if (mCursor.moveToFirst()){
            do{
                //int _id = mCursor.getInt(mCursor.getColumnIndex(NewsContract.NewsEntry._ID));
                /*String source = mCursor.getString(
                        mCursor.getColumnIndex(NewsContract.SourceEntry.SOURCE_COLUMN));*/
                String source = "mySource";
                String title = mCursor.getString(
                        mCursor.getColumnIndex(NewsContract.NewsEntry.NEWS_TITLE_COLUMN));
                String category = mCursor.getString(
                        mCursor.getColumnIndex(NewsContract.NewsEntry.NEWS_CATEGORY_COLUMN));
                String imageUrl = mCursor.getString(
                        mCursor.getColumnIndex(NewsContract.NewsEntry.NEWS_IMAGEURL_COLUMN));
                String url = mCursor.getString(
                        mCursor.getColumnIndex(NewsContract.NewsEntry.NEWS_URL_COLUMN));
                String date = mCursor.getString(
                        mCursor.getColumnIndex(NewsContract.NewsEntry.NEWS_DATE_COLUMN));
                String author = mCursor.getString(
                        mCursor.getColumnIndex(NewsContract.NewsEntry.NEWS_AUTHOR_COLUMN));


                News news = new News(source,title, category, imageUrl, url, date, author);
                entries.add(news);
            }while (mCursor.moveToNext());
        }
    } else {Log.d(LOG_TAG, "Cursor is null");

       //mCursor.setNotificationUri(mContentResolver, mUri);
    }

    return entries;

}

@Override
public void deliverResult(List<News> news) {
   if (isReset()){
       if (news != null){
           mCursor.close();
       }
   }

    List<News> oldNews = mNews;
    if (mNews == null || mNews.size() == 0){
        Log.d(LOG_TAG, "+++++++ No data returned");

    }

    mNews = news;
    if (isStarted()){
        super.deliverResult(news);
    }

    if (oldNews != null && oldNews != news){
        mCursor.close();
    }
}

@Override
protected void onStartLoading() {
    if (mNews != null){
        deliverResult(mNews);
    }

    if (takeContentChanged() || mNews == null){
        // here this forceLoad() is called only when mNews is null and never 
        // takeContentChanged.
        // the ui gets refresh only when i restart the app
        forceLoad();

    }
}

@Override
protected void onStopLoading() {
    cancelLoad();
}

@Override
protected void onReset() {
    onStopLoading();
    if (mCursor != null){
        mCursor.close();
    }

    mNews = null;
}

@Override
public void onCanceled(List<News> news) {
    super.onCanceled(news);
    if (mCursor != null){
        mCursor.close();
    }
}

@Override
public void forceLoad() {
    super.forceLoad();
}


public void deleteOldItems (int num){

    String where = NewsContract.NewsEntry._ID +
            " IN (SELECT " + NewsContract.NewsEntry._ID + " FROM " +
            NewsContract.NewsEntry.NEWS_TABLE_NAME +
            " ORDER BY " + NewsContract.NewsEntry._ID + " DESC " + " LIMIT ?)";
    String limit = "2, 10";
    String[] selectionArgs = new String[]{limit};

    mContentResolver.delete(
            NewsContract.NewsEntry.NEWS_TABLE_CONTENT_URI, where, selectionArgs );



}

}

内容提供商代码片段

@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
    final SQLiteDatabase db = mHelper.getWritableDatabase();
    final int match = sUriMatcher.match(uri);
    Log.d(LOG_TAG, "Insert Uri " + uri);
    switch (match) {
        case NEWS:

            /**
            * Begins a transaction in "exclusive" mode. No other mutations can occur on the
                * db until this transaction finishes.*/

            db.beginTransaction();
            int returnCount = 0;
            try {
                for (ContentValues value : values) {
                    long _id = db.insert(NewsContract.NewsEntry.NEWS_TABLE_NAME, null, value);
                    if (_id != -1) {
                        returnCount++;
                    }
                }
                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
            }

            getContext().getContentResolver().notifyChange(uri, null);

            //Log.d(LOG_TAG, "Insert Count is " + returnCount);
            return returnCount;
        default:
            return super.bulkInsert(uri, values);
    }
}

@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;
    Log.d(LOG_TAG, "Match Uri is " + uri);
    Log.d(LOG_TAG, "Matcher is " + sUriMatcher.match(uri));


    switch (sUriMatcher.match(uri)) {

        case NEWS: {

            //retCursor = getNewsWithAuthor( uri,projection, sortOrder);
            //String author = NewsContract.NewsEntry.getAuthorFromUri(uri);
            //Log.d(LOG_TAG, "URI for News is "+ uri);
            //selection = sAuthorSlection;
            retCursor = mHelper.getReadableDatabase().query(
                    NewsContract.NewsEntry.NEWS_TABLE_NAME,
                    projection,
                    selection,
                    selectionArgs,
                    null,
                    null,
                    sortOrder
            );
            break;

        }


        case NEWS_WITH_SOURCE: {

            retCursor = getNewsSourceAndCategory(uri, projection, sortOrder);
            break;
        }

        case NEWS_WITH_CATEGORY: {

            retCursor = getNewsWithCategory(uri, projection, sortOrder);
            break;
        }


        case NEWS_WITH_SOURCE_AND_CATEGORY:{
            Log.d(LOG_TAG, "Provider Uri is "+ uri);
            retCursor = getNewsSourceAndCategory(uri, projection, sortOrder);
            break;
        }



        // "news/*/*/*
        case NEWS_WITH_SOURCE_DATE_AND_CATEGORY: {
            // Log.d(LOG_TAG, "Provider Uri is "+ uri);
            retCursor = getNewsWithSourceDateAndCategory(uri, projection, sortOrder);

            break;
        }



        case SOURCE: {
            retCursor = mHelper.getReadableDatabase().query(
                    NewsContract.SourceEntry.NEWS_SOURCE_TABLE_NAME,
                    projection,
                    selection,
                    selectionArgs,
                    null,
                    null,
                    null,
                    sortOrder
            );
            break;
        }
        // "source/*"
        case SOURCE_ID: {

            retCursor = mHelper.getReadableDatabase().query(
                    NewsContract.SourceEntry.NEWS_SOURCE_TABLE_NAME,
                    projection,
                    NewsContract.SourceEntry._ID + " = '" + ContentUris.parseId(uri) + "'",
                    null,
                    null,
                    null,
                    sortOrder
            );
            break;
        }
        // "source"


        default:
            throw new UnsupportedOperationException("Unknown uri: " + uri);
    }
    retCursor.setNotificationUri(getContext().getContentResolver(), uri);

    return retCursor;
}

实现 LoaderCallback 的片段

public class RecyclerViewFragment  extends Fragment implements LoaderManager.LoaderCallbacks<List<News>> {
private static final String LOG_TAG = RecyclerViewFragment.class.getSimpleName();

private RecyclerView mRecyclerView;
private NewsRecyclerViewAdapter mAdapter;
private List<News> mNews;
private static final int LOADER_ID = 0;
private ContentResolver mContentResolver;
private Object mSyncObserverHandle;
Menu mOptionsMenu;



private String[] mUris;



public static RecyclerViewFragment newInstance() {
    return new RecyclerViewFragment();
}






@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
}

@Override
public View onCreateView(LayoutInflater inflater,  ViewGroup container,  Bundle savedInstanceState) {

    return inflater.inflate(R.layout.fragment_recyclerview, container, false);



}



@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    mRecyclerView = (RecyclerView) view.findViewById(R.id.recyclerView);
    RecyclerView.LayoutManager layoutManager;

    mRecyclerView.setLayoutManager(layoutManager);
    mRecyclerView.setHasFixedSize(false);

    mRecyclerView.addItemDecoration(new MaterialViewPagerHeaderDecorator());




    mAdapter = new NewsRecyclerViewAdapter(getActivity(),new ArrayList<News>());
    mRecyclerView.setAdapter(mAdapter);


              ...


@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);


    mContentResolver = getActivity().getContentResolver();
    getLoaderManager().initLoader(LOADER_ID, null,this);






}

@Override
public void onAttach(Context context) {
    super.onAttach(context);
    ....



}

@Override
public void onResume() {
    super.onResume();
   ...
}

@Override
public void onPause() {
    super.onPause();
    if (mSyncObserverHandle != null) {
        ContentResolver.removeStatusChangeListener(mSyncObserverHandle);
        mSyncObserverHandle = null;
    }
}

@Override
public Loader<List<News>> onCreateLoader(int id, Bundle args) {
    mContentResolver = getActivity().getContentResolver();
     String source = "techcrunch";
      String category = null;
    String author = "Anna Escher";
    String sortOrder = NewsContract.NewsEntry.NEWS_DATE_COLUMN + " DESC";
    return new NewsListLoader(getActivity(), NewsContract.NewsEntry
            .buildNewsSource(source), sortOrder, mContentResolver);
}

@Override
public void onLoadFinished(Loader<List<News>> loader, List<News> news) {

    mAdapter.loadData(news);
    mNews = news;

   mAdapter.notifyDataSetChanged();
    //mNews = news;

}


@Override
public void onLoaderReset(Loader<List<News>> loader) {
    mAdapter.loadData(null);
}

Recyclerview 适配器代码

@Override
public View_Holder onCreateViewHolder(ViewGroup parent, int viewType) {

    View  view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.list_item_card_big, parent, false);
            return new View_Holder(view);



}

@Override
public void onBindViewHolder(View_Holder holder, int position) {

    List<News> newsItem  = mNewsItems;
    Log.d(LOG_TAG, "Processing "+ newsItem.get(position).getTitle() + " -->" + Integer.toString(position));

            String title = newsItem.get(position).getTitle();
            holder.title.setText(title);
            String author = newsItem.get(position).getAuthor();
            holder.author.setText(author);
            String source = (newsItem.get(position).getSource());
            holder.source.setText(source);
            String date = Utility.getDayName(mContext,newsItem.get(position).getPublishedAt());
            holder.publisedAt.setText(date);

            Picasso.with(mContext).load(newsItem.get(position).getImageToUrl())
                    .fit()
                    .error(R.drawable.placeholder)
                    .placeholder(R.drawable.placeholder)
                    .into(holder.imageView);


}
@Override
public int getItemCount() {

    return (null != mNewsItems ? mNewsItems.size(): 0);
}

public void loadData(List<News> newNews){
    mNewsItems.clear();
    if (newNews != null){
        mNewsItems.addAll(newNews);

        notifyDataSetChanged();
    }



}

也许您必须使用自己的回收站视图。我只是给你一个想法。您必须实施 checkIfempty 方法。

@Override
public void setAdapter(Adapter adapter) {
}



 final private AdapterDataObserver observer = new AdapterDataObserver() {

    @Override
    public void onChanged() {
        checkIfEmpty();
    }

    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        checkIfEmpty();
    }

    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        checkIfEmpty();
    }

   };


 @Override
public void setAdapter(Adapter adapter) {
    final Adapter oldAdapter = getAdapter();
    if (oldAdapter != null) {
        oldAdapter.unregisterAdapterDataObserver(observer);
    }
    super.setAdapter(adapter);
    if (adapter != null) {
        adapter.registerAdapterDataObserver(observer);
    }

    checkIfEmpty();
}

您需要在 Cursor 上注册 ContentObserver 并覆盖其中的 onChange 以调用 AsyncTaskLoader 中的 onContentChanged

如果您使用 CursorLoader,您将获得该功能。

通过在我的 AsyncTask Loader extended class 中实施内容观察器使其工作 class。`

public class NewsListLoader extends AsyncTaskLoader<List<News>> {
.....
final ForceLoadContentObserver  mObserver;

public NewsListLoader(Context context, Uri uri, String sortOrder, ContentResolver contentResolver) {
    super(context);
    mContentResolver = contentResolver;
    mUri = uri;
    mSortOrder = sortOrder;
    mObserver = new ForceLoadContentObserver();

}

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

@Override
public List<News> loadInBackground() {
   ......
    mCursor = mContentResolver.query(mUri,
            projection, null, null, mSortOrder);

    if (mCursor != null){

            try {
                // Ensure the cursor window is filled.
                mCursor.getCount();
                mCursor.registerContentObserver(mObserver);
            } catch (RuntimeException ex) {
                mCursor.close();
                throw ex;
            }