Firebase聊天应用上传图片后RecyclerView滚动崩溃问题
FirebaseRecyclerAdapter图片上传后滚动RecyclerView崩溃问题
我开发聊天应用时,使用FirebaseRecyclerAdapter展示消息列表。从相册选择图片上传至Firebase后,立即滚动RecyclerView会触发java.lang.IndexOutOfBoundsException异常导致应用崩溃,自定义LinearLayoutManager捕获异常的方案也未能解决问题。
报错日志
Error getting package info: com.videocall.newhost 2022-11-16 11:59:23.173 23361-23361 AndroidRuntime pid-23361 E FATAL EXCEPTION: main Process: com.videocall.newhost, PID: 23361 java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionMessageView{c4fbbee position=4 id=-1, oldPos=-1, pLpos:-1 no parent} androidx.recyclerview.widget.RecyclerView{60b9448 VFED..... ........ 0,112-1080,1686 #7f0a01c3 app:id/recyMessage}, adapter:com.videocall.newhost.Adapter.AdapterMessages@9239bff, layout:com.videocall.newhost.Helper.WraperLinearLayout@e8d0fe7, context:com.videocall.newhost.Activity.MessageActivity@8e8b3d4 at androidx.recyclerview.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:6544) at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6727) at androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:288) at androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:345) at androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:361) at androidx.recyclerview.widget.GapWorker.prefetch(GapWorker.java:368) at androidx.recyclerview.widget.GapWorker.run(GapWorker.java:399) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:233) at android.os.Looper.loop(Looper.java:344) at android.app.ActivityThread.main(ActivityThread.java:8212) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034)
相关代码
FirebaseRecyclerAdapter代码
public class AdapterMessages extends FirebaseRecyclerAdapter<PojoMessages, AdapterMessages.MessageView> { public AdapterMessages(@NonNull FirebaseRecyclerOptions<PojoMessages> options) { super(options); } @Override protected void onBindViewHolder(@NonNull MessageView viewHolder, int position, @NonNull PojoMessages pojoMessages) { if (pojoMessages.isSender()) { viewHolder.layReciver.setVisibility(View.GONE); viewHolder.laySender.setVisibility(View.VISIBLE); switch (pojoMessages.getType()) { case "text": { viewHolder.laySText.setVisibility(View.VISIBLE); viewHolder.laySImage.setVisibility(View.GONE); viewHolder.laySVoice.setVisibility(View.GONE); viewHolder.txtSMessage.setText(pojoMessages.getMessage()); break; } case "image": { viewHolder.laySText.setVisibility(View.GONE); viewHolder.laySImage.setVisibility(View.VISIBLE); viewHolder.laySVoice.setVisibility(View.GONE); Picasso.get().load(pojoMessages.getUrl()).into(viewHolder.imgSData); break; } case "voice": { viewHolder.laySText.setVisibility(View.GONE); viewHolder.laySImage.setVisibility(View.GONE); viewHolder.laySVoice.setVisibility(View.VISIBLE); break; } } } else { viewHolder.layReciver.setVisibility(View.VISIBLE); viewHolder.laySender.setVisibility(View.GONE); switch (pojoMessages.getType()) { case "text": { viewHolder.layRText.setVisibility(View.VISIBLE); viewHolder.layRImage.setVisibility(View.GONE); viewHolder.layRVoice.setVisibility(View.GONE); viewHolder.txtRMessage.setText(pojoMessages.getMessage()); break; } case "image": { viewHolder.layRText.setVisibility(View.GONE); viewHolder.layRImage.setVisibility(View.VISIBLE); viewHolder.layRVoice.setVisibility(View.GONE); Picasso.get().load(pojoMessages.getUrl()).into(viewHolder.imgRData); break; } case "voice": { viewHolder.layRText.setVisibility(View.GONE); viewHolder.layRImage.setVisibility(View.GONE); viewHolder.layRVoice.setVisibility(View.VISIBLE); // viewHolder.voiceRPlayerView.setAudio("https://www2.cs.uic.edu/~i101/SoundFiles/BabyElephantWalk60.wav"); break; } } } } @NonNull @Override public MessageView onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new MessageView(LayoutInflater.from(parent.getContext()).inflate(R.layout.single_message_view, parent, false)); } public class MessageView extends RecyclerView.ViewHolder { CircleImageView imgRProfile; RelativeLayout layRText, layRImage, layRVoice, laySText, laySImage, laySVoice, laySender, layReciver; TextView txtRMessage, txtSMessage; ImageView imgRData, imgSData; public MessageView(@NonNull View itemView) { super(itemView); layReciver = itemView.findViewById(R.id.layReciver); laySender = itemView.findViewById(R.id.laySender); imgRProfile = itemView.findViewById(R.id.imgRProfile); layRText = itemView.findViewById(R.id.layRText); layRVoice = itemView.findViewById(R.id.layRVoice); layRImage = itemView.findViewById(R.id.layRImage); txtRMessage = itemView.findViewById(R.id.txtRMessage); imgRData = itemView.findViewById(R.id.imgRData); laySText = itemView.findViewById(R.id.laySText); laySVoice = itemView.findViewById(R.id.laySVoice); laySImage = itemView.findViewById(R.id.laySImage); txtSMessage = itemView.findViewById(R.id.txtSMessage); imgSData = itemView.findViewById(R.id.imgSData); } } }
自定义LinearLayoutManager代码
public class WraperLinearLayout extends LinearLayoutManager { public WraperLinearLayout(Context context) { super(context); } public WraperLinearLayout(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } public WraperLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { try { super.onLayoutChildren(recycler, state); } catch (Exception e) { Log.d("onLayoutChildren", "onLayoutChildren: "+e.getMessage()); } } @Override public boolean supportsPredictiveItemAnimations() { return false; } }
Activity代码
public class MessageActivity extends AppCompatActivity { private static final int SELECT_PICTURE = 2121; RecyclerView recySuggestion, recyMessage; AdapterSuggestion adapterSuggestion; DatabaseReference userDb, myMsgRef, otherMsgRef; Query query; AdapterMessages adapterMessages; EditText edMessage; ImageView imgSendMsg, imgImageDoc; WraperLinearLayout layoutManager; StorageReference imgRef; ActivityResultLauncher<String> mGetContent = registerForActivityResult(new ActivityResultContracts.GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri result) { sendImage(result); } }); @SuppressLint("ClickableViewAccessibility") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_message); initialization(); adapterMessages.startListening(); adapterMessages.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { @Override public void onItemRangeInserted(int positionStart, int itemCount) { super.onItemRangeInserted(positionStart, itemCount); int friendlyMessageCount = adapterMessages.getItemCount(); int lastVisiblePosition = layoutManager.findLastCompletelyVisibleItemPosition(); // If the recycler view is initially being loaded or the // user is at the bottom of the list, scroll to the bottom // of the list to show the newly added message. if (lastVisiblePosition == -1 || positionStart >= (friendlyMessageCount - 1)) { recyMessage.scrollToPosition(positionStart); } } }); onClick(); } @Override protected void onResume() { super.onResume(); } private void onClick() { imgSendMsg.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendTextMessage(edMessage.getText().toString()); edMessage.setText(""); } }); imgImageDoc.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mGetContent.launch("image/*"); } }); } @Override protected void onStop() { super.onStop(); adapterMessages.stopListening(); } private void sendImage(Uri file) { imgRef.child(file.getLastPathSegment()).putFile(file).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() { @Override public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { taskSnapshot.getStorage().getDownloadUrl().addOnCompleteListener( new OnCompleteListener<Uri>() { @Override public void onComplete(@NonNull Task<Uri> task) { String fileLink = task.getResult().toString(); //next work with URL PojoMessages pojoMessages = new PojoMessages(); pojoMessages.setUrl(fileLink); pojoMessages.setType("image"); pojoMessages.setSender(true); myMsgRef.push().setValue(pojoMessages); pojoMessages.setSender(false); otherMsgRef.push().setValue(pojoMessages); } }); } }); } private void sendTextMessage(String text) { PojoMessages pojoMessages = new PojoMessages(); pojoMessages.setMessage(text); pojoMessages.setType("text"); pojoMessages.setSender(true); myMsgRef.push().setValue(pojoMessages); pojoMessages.setSender(false); otherMsgRef.push().setValue(pojoMessages); } private void initialization() { layoutManager = new WraperLinearLayout(this, LinearLayoutManager.VERTICAL, false); userDb = FirebaseDatabase.getInstance().getReference().child(USER); imgRef = FirebaseStorage.getInstance().getReference().child("image/"); myMsgRef = userDb.child("987654321").child(MESSAGE).child("8385958058"); otherMsgRef = userDb.child("8385958058").child(MESSAGE).child("987654321"); query = userDb.child("987654321").child(MESSAGE).child("8385958058"); FirebaseRecyclerOptions<PojoMessages> options = new FirebaseRecyclerOptions.Builder<PojoMessages>() .setLifecycleOwner(this).setQuery(query, PojoMessages.class).build(); adapterMessages = new AdapterMessages(options); imgImageDoc = findViewById(R.id.imgImageDoc); edMessage = findViewById(R.id.edMessage); imgSendMsg = findViewById(R.id.imgSendMsg); recySuggestion = findViewById(R.id.recySuggestion); recyMessage = findViewById(R.id.recyMessage); recyMessage.setLayoutManager(layoutManager); recyMessage.setAdapter(adapterMessages); adapterSuggestion = new AdapterSuggestion(); recySuggestion.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); recySuggestion.setAdapter(adapterSuggestion); } }
解决方案
1. 拦截预取逻辑中的异常
报错来自RecyclerView的GapWorker预取机制,可在自定义LinearLayoutManager中覆盖tryGetViewHolderForPositionByDeadline方法,直接捕获该位置的索引越界异常:
@Override public RecyclerView.ViewHolder tryGetViewHolderForPositionByDeadline(int position, long deadline, int flags) { try { return super.tryGetViewHolderForPositionByDeadline(position, deadline, flags); } catch (IndexOutOfBoundsException e) { Log.e("WraperLinearLayout", "忽略预取时的索引越界异常"); return null; } }
2. 延迟执行滚动操作
数据插入后立即滚动可能存在数据同步延迟,使用post方法延迟执行滚动逻辑,确保Adapter已完成数据更新:
adapterMessages.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { @Override public void onItemRangeInserted(int positionStart, int itemCount) { super.onItemRangeInserted(positionStart, itemCount); recyMessage.post(() -> { int totalCount = adapterMessages.getItemCount(); int lastVisiblePos = layoutManager.findLastCompletelyVisibleItemPosition(); if (lastVisiblePos == -1 || positionStart >= totalCount -1) { recyMessage.scrollToPosition(totalCount -1); } }); } });
3. 禁用RecyclerView预取功能
如果上述方法无效,可直接关闭预取功能避免冲突,在Activity的初始化方法中添加:
recyMessage.setItemViewCacheSize(0); recyMessage.setHasFixedSize(true); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { recyMessage.setItemPrefetchEnabled(false); }
内容的提问来源于stack exchange,提问作者NTW OFFICE
相关产品推荐
相关产品推荐

