You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 19:35:17