如何使用 AsyncTask 更新 RecyclerView 项目
Posted
技术标签:
【中文标题】如何使用 AsyncTask 更新 RecyclerView 项目【英文标题】:How to update the RecyclerView Items Using AsyncTask 【发布时间】:2019-05-31 17:27:41 【问题描述】:我正在尝试从SQLite
数据库中的数据更新recyclerview
项目,而这些数据又会不时使用从 API 获取的数据进行更新。
我有一个TabLayout
,其中包含三个fragments
。
现在在一个片段中,我以recyclerView
的形式展示了用户玩的游戏。 recyclerView
的每一项都包含用户名、他的高分和编号。击败他的高分的其他用户。
现在在片段中,我调用 API 来更新 AsyncTask
内的数据库,然后再次使用 RecyclerView
适配器类中的同一数据库检索另一个 AsyncTask
中的数据。使用TimerTask
和处理程序定期调用AsyncTask
。
@TargetApi(Build.VERSION_CODES.CUPCAKE)
@RequiresApi(api = Build.VERSION_CODES.CUPCAKE)
public class PerformBackgroundTask extends AsyncTask<String, Void, String>
@Override
protected String doInBackground(String... gameID)
try
SQLiteDatabase challengeDatabase = mContext.openOrCreateDatabase("Challenge", Context.MODE_PRIVATE, null);
challengeDatabase.execSQL("CREATE TABLE IF NOT EXISTS DISPLAY_MESSAGE (gameID VARCHAR, highestScorer VARCHAR, count INT)");
Cursor d = challengeDatabase.rawQuery("SELECT * FROM DISPLAY_MESSAGE WHERE gameID", null);
int gameCode = d.getColumnIndex("gameID");
int highestScorer = d.getColumnIndex("highestScorer");
int count = d.getColumnIndex("count");
d.moveToFirst();
while(d!=null&&d.getCount()>0)
if((d.getString(gameCode)).equals(gameID))
int k = Integer.parseInt(d.getString(count));
if(k>0)
return d.getString(highestScorer) + " and " + k + " others have beaten your highscore.";
else if(k==0)
return d.getString(highestScorer) + " has beaten your highscore.";
else
return "Yay! you have beaten all your opponents!";
d.moveToNext();
challengeDatabase.close();
catch (Exception e)
e.printStackTrace();
return null;
public GridAdapterChallenge(Context c, List<CardContainer> listCards, Vector<Object> cardDetails, String sname)
//selectedCards = new HashSet<Integer>();
inflater = (LayoutInflater) c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mContext = c;
this.listCards = listCards;
this.sname = sname;
cards = cardDetails;
//notifyDataSetChanged();
public class ViewHolder extends RecyclerView.ViewHolder
private int viewType;
private ImageView imageView;
private Button increaseScore, challengeFriend;
private TextView challengeDetails, points, name, challengeCode;
private ImageView nativeAdIcon;
private TextView nativeAdTitle;
private TextView nativeAdBody;
private MediaView nativeAdMedia;
private TextView nativeAdSocialContext;
private Button nativeAdCallToAction;
private ProgressBar detailProgress;
public ViewHolder(@NonNull View itemView, int viewType)
super(itemView);
view = itemView;
this.viewType = viewType;
if (viewType == 1)
this.imageView = (ImageView) itemView.findViewById(R.id.thumb);
this.name = (TextView) itemView.findViewById(R.id.gameName);
this.challengeCode = itemView.findViewById(R.id.challengeCode);
this.points = itemView.findViewById(R.id.points);
this.challengeDetails = itemView.findViewById(R.id.challengeDetail);
this.increaseScore = itemView.findViewById(R.id.increaseScore);
this.challengeFriend = itemView.findViewById(R.id.challengeFriend);
this.challengeDetails = itemView.findViewById(R.id.challengeDetail);
this.detailProgress = itemView.findViewById(R.id.detailProgressBar);
else
this.nativeAdIcon = (ImageView) itemView.findViewById(R.id.native_ad_icon);
this.nativeAdTitle = (TextView) itemView.findViewById(R.id.native_ad_title);
this.nativeAdBody = (TextView) itemView.findViewById(R.id.native_ad_body);
this.nativeAdMedia = (MediaView) itemView.findViewById(R.id.native_ad_media);
this.nativeAdSocialContext = (TextView) itemView.findViewById(R.id.native_ad_social_context);
this.nativeAdCallToAction = (Button) itemView.findViewById(R.id.native_ad_call_to_action);
public void updateDetails(final TextView challengeDetails, final String gameId)
final Handler handler;
handler = new Handler();
Timer timer = new Timer();
doAsynchronousTask = new TimerTask()
@Override
public void run()
handler.post(new Runnable()
public void run()
try
PerformBackgroundTask performBackgroundTask = new PerformBackgroundTask();
challengeDetails.setText((CharSequence) performBackgroundTask.execute(gameId));
catch (Exception e)
);
;
timer.schedule(doAsynchronousTask, 4000, 5000); //execute in every 1000 ms
@Override
public void onBindViewHolder(@NonNull final ViewHolder viewHolder, final int i)
if (viewHolder.viewType == 0)
if (adList != null && adList.size() > (i+1)/5 - 1 && adList.get((i+1)/5 - 1) != null)
NativeAd ad = adList.get((i+1)/5 - 1);
viewHolder.nativeAdSocialContext.setText(ad.getAdSocialContext());
viewHolder.nativeAdCallToAction.setText(ad.getAdCallToAction());
viewHolder.nativeAdCallToAction.setVisibility(View.VISIBLE);
viewHolder.nativeAdTitle.setText(ad.getAdHeadline());
viewHolder.nativeAdBody.setText(ad.getAdBodyText());
ad.registerViewForInteraction(viewHolder.itemView, viewHolder.nativeAdMedia, viewHolder.nativeAdIcon, Arrays.asList(viewHolder.nativeAdCallToAction, viewHolder.nativeAdMedia));
NativeAd.Image adCoverImage = ad.getAdCoverImage();
int bannerWidth = adCoverImage.getWidth();
int bannerHeight = adCoverImage.getHeight();
DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
int mediaWidth = viewHolder.itemView.getWidth() > 0 ? viewHolder.itemView.getWidth() : metrics.widthPixels;
viewHolder.nativeAdMedia.setLayoutParams(new LinearLayout.LayoutParams(mediaWidth, Math.min(
(int) (((double) mediaWidth / (double) bannerWidth) * bannerHeight), metrics.heightPixels / 3)));
ad.registerViewForInteraction(viewHolder.itemView, viewHolder.nativeAdMedia, Arrays.asList(viewHolder.nativeAdCallToAction, viewHolder.nativeAdMedia));
ad.registerViewForInteraction(viewHolder.itemView, viewHolder.nativeAdMedia);
else
viewHolder.name.setText(((CardContainer)cards.get(i)).name);
challengeCode = getChallengeCode(i);
viewHolder.challengeCode.setText(challengeCode);
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity) mContext).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
if(getHighScore(((CardContainer)cards.get(i)).gameId)>0)
PerformBackgroundTask performBackgroundTask = new PerformBackgroundTask();
viewHolder.challengeDetails.setText(performBackgroundTask.execute(gameId).toString());
//updateDetails(viewHolder.challengeDetails, ((CardContainer)cards.get(i)).gameId);
else
viewHolder.challengeDetails.setText("Press Increase Score To Play the Game");
viewHolder.detailProgress.setVisibility(View.GONE);
int width = displayMetrics.widthPixels;
try
final String uri = ((CardContainer) cards.get(i)).imageurl;
if (uri != null)
Picasso.get().load(uri)
.placeholder(R.mipmap.place)
.error(R.mipmap.place)
.resize(width / 2, width / 2)
.centerCrop()
.into(viewHolder.imageView);
catch (Exception e)
final String uri = null;
viewHolder.points.setText(String.valueOf(getHighScore(((CardContainer)cards.get(i)).gameId)));
viewHolder.increaseScore.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View arg0)
if (((CardContainer) cards.get(i)).link.contains("play.google.com"))
openAppRating(mContext,((CardContainer) cards.get(i)).link.split("id=", 2)[1]);
else
name = ((CardContainer) cards.get(i)).name;
link = ((CardContainer) cards.get(i)).link;
imageurl = ((CardContainer) cards.get(i)).imageurl;
type = ((CardContainer) cards.get(i)).type;
packageName = ((CardContainer) cards.get(i)).packageName;
gameId = ((CardContainer)cards.get(i)).gameId;
challengeNames.add(name);
updateDatabase(name, gameId, Config.uid+"@"+gameId, 0, viewHolder.points);
//String gameName, String gameId, String mychCode, int myHighScore
);
viewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener()
String name = ((CardContainer) cards.get(i)).name;
String link = ((CardContainer) cards.get(i)).link;
String imageurl = ((CardContainer) cards.get(i)).imageurl;
String type = ((CardContainer) cards.get(i)).type;
String packageName = ((CardContainer) cards.get(i)).packageName;
Bitmap bitmap = null;
Integer xyz = android.os.Build.VERSION.SDK_INT;
@Override
public boolean onLongClick(View view)
bitmap = ((BitmapDrawable) viewHolder.imageView.getDrawable()).getBitmap();
if (sname.equals("fav"))
// Toast.makeText(mContext, "in fav", Toast.LENGTH_SHORT).show();
final Dialog dialog = new Dialog(mContext);
dialog.setContentView(R.layout.custom_dialog);
TextView dialogText=dialog.findViewById(R.id.txt_dia);
dialogText.setText("Do you want to remove "+name+" from your favorites?");
dialog.show();
Button yes_button = (Button) dialog.findViewById(R.id.btn_yes);
yes_button.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
favf = new FavFunction(mContext);
int resultFav=favf.removeFromFavorite(link);
if (resultFav>=0)
cards.remove(resultFav);
notifyDataSetChanged();
dialog.dismiss();
);
Button no_button = (Button) dialog.findViewById(R.id.btn_no);
no_button.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
dialog.dismiss();
);
return true;
else
final Dialog dialog = new Dialog(mContext);
dialog.setContentView(R.layout.custom_dialog);
TextView dialogText=dialog.findViewById(R.id.txt_dia);
dialogText.setText("Adding "+name+" to favorites... Do you also want to create it's homescreen shortcut?");
dialog.show();
Button yes_button = (Button) dialog.findViewById(R.id.btn_yes);
yes_button.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
favf = new FavFunction(mContext);
Boolean favstatus=favf.addToFavorite(imageurl, link ,name,type,packageName);
favf.addShortcut(name, link , imageurl,type,packageName,bitmap);
if(favstatus)
((Activity) mContext).recreate();
dialog.dismiss();
);
Button no_button = (Button) dialog.findViewById(R.id.btn_no);
no_button.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
favf = new FavFunction(mContext);
Boolean favstatus=favf.addToFavorite(imageurl, link, name,type,packageName);
if(favstatus)
((Activity) mContext).recreate();
dialog.dismiss();
);
return true;
);
viewHolder.challengeFriend.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View view)
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, "Hey! I challenge you to beat my highscore " + getHighScore(gameId) + " in this awesome game. Enter my challenge code " + challengeCode + " in the \"Challenges\" Tab of 101 Game Store App. You can download the app from bit.ly/101gamestore");
sendIntent.setType("text/plain");
mContext.startActivity(sendIntent);
);
我想更新号码。 recyclerView
的每个项目中的高分者。然而,正在发生的事情是,一旦TimerTask
启动,UI 就会冻结。正在成功获取数据。任何比我更好的解决方案都将不胜感激。
【问题讨论】:
如果您使用 ROOM 然后公开LiveData<List<T>>
并将其放入 ViewModel 然后观察它,您不应该在修改后使用第二个 AsyncTask 从数据库中读取新数据从您的 Activity/Fragment 中,然后将新收到的列表传递给 ListAdapter,然后大部分可以删除
@EpicPandaForce 您能否进一步详细说明或为我提供资源。这些术语对我来说似乎很陌生。
见codelabs.developers.google.com/codelabs/android-persistence/#5
@EpicPandaForce 谢谢伙计!非常有用的链接。
【参考方案1】:
要更新您的适配器,您应该在主线程中进行。 AsyncTask 提供 onPostExecute 方法。
不要替换适配器,而是应该替换适配器中的数据。
class MyAsyncTask extends AsyncTask<Void, Void, List<String>>
MyAdapter myAdapter;
ArrayList<String> values = new ArrayList<>();
public MyAsyncTask(MyAdapter adapter)
this.myAdapter = myAdapter;
@Override
protected List<String> doInBackground(String... params)
ArrayList<String> result = new ArrayList<>();
// long operation, for example: get results from url
return result;
@Override
protected void onPostExecute(List<String> list)
myAdapter.setNewList(list);
class MyAdapter
private List<String> list;
............
void setNewList(List<String> list)
this.list = list;
notifyDataSetChanged();
............
【讨论】:
数据库不包含要在 ItemView 中显示的完整数据。 ItemView 包含两种类型的信息:静态和动态。我需要更新动态的,数据库中只有那么多信息。 在 doInBackground 方法中,您应该从数据库中收集数据并将其映射到适配器数据结构。然后在 onPostExecute 方法中,您应该将数据列表设置为适配器。适配器应检查不等于列表项并调用 notifyItemChanged。【参考方案2】:看看这里....
class MyAsyncTask extends AsyncTask<String, Void, String>
Context mContext;
ArrayList<String> values= new ArrayList<String>();
public MyAsyncTask(Context context)
mContext = context;
@Override
protected String doInBackground(String... params)
// do your all database operation here and try to stroe in arraylist
values.add("Data comes from database");
return "";
@Override
protected void onPostExecute(String list)
// Do your View update here.
// All your Recycle View here
yourRecycleView.setAdapter(new MyAdapter(context,values))
注意:- 在 AsynTask 中,在 onPreExecute()
方法中完成所有绑定工作,在 doInBackground()
方法中完成所有数据库或长线程工作(例如从数据库中获取数据,下载文件或上传文件等)并更新您的视图在onPostExecute()
方法中。
如何使用 AsyncTask 更新 RecyclerView 项目?
按照相同的过程在doInBackground()
方法中获取您的数据,并在onPostExecute()
方法中通知您的适配器。在其他 AsynTask 中执行此操作
【讨论】:
当您直接在线程中的适配器中更新数据时,任何人都可以通知数据集已更改。适配器将处于无效状态。在 onPostExecute 中需要从 recyclerView 获取适配器,转换为 MyAdapter 然后使用列表参数调用更新方法。更新方法调用 notifyDataSetChanged 或 itemChanged/inserted/removed。 如果您在onPostExecute()
方法中执行 adapter.notifyDataSetChanged()
。数据不会变吗?
onPostExecute()
是UI thread
,我认为在onPostExecute()
方法中更改您的视图或适配器不会给您带来任何问题。
我知道在主线程中调用了 onPostExecute。我只是澄清一下,因为这是一个常见问题。
@SushilKumar 数据库不包含要在 ItemView 中显示的完整数据。 ItemView 包含两种类型的信息:静态和动态。我需要更新动态的,数据库中只有那么多信息。以上是关于如何使用 AsyncTask 更新 RecyclerView 项目的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 AsyncTask 的 onPostExecute 方法更新 Android 中的变量
重新创建活动时如何从 AsyncTask 更新 TextView
如何在 AsyncTask 中更新 ArrayList 的内容?