【问题标题】:RecyclerView is heavy with complex viewRecyclerView 很重,视图很复杂
【发布时间】:2020-06-21 08:07:07
【问题描述】:

对不起,我有一个像whatsapp这样的聊天应用程序,当我进入聊天页面时,它变得卡顿了。 起初,我认为这是因为主线程上的工作量很大,因为聊天页面有 3 个监听器。但是当我做到3线程后,它仍然很重。

这是我的适配器:

package com.teamsekret.tel_me.adapter;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.pdf.PdfRenderer;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.Target;
import com.google.firebase.auth.FirebaseAuth;

import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference;
import com.teamsekret.tel_me.ChatActivity;
import com.teamsekret.tel_me.R;
import com.teamsekret.tel_me.data.ListMessage;
import com.teamsekret.tel_me.data.Message;
import com.teamsekret.tel_me.data.MessageDate;
import com.teamsekret.tel_me.module.GlideApp;
import com.teamsekret.tel_me.module.TimeModule;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class ChatAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
implements ActionMode.Callback {

    private static final int MSG_LEFT = 0;
    private static final int MSG_RIGHT = 1;
    private static final int MSG_LEFT_GROUP = 2;
    private static final int FILE_RQ = 101;

    private TimeModule timeFormattor;
    private FirebaseAuth mAuth;
    private ArrayList<ListMessage> message;
    private ArrayList<Message> messageModel;
    private Context context;
    private String chatUID;
    private String searchText;

    private ActionMode mode;
    private ArrayList<Message> selectedItem = new ArrayList<>();
    private ArrayList<View> selectedViews = new ArrayList<>();

    private FirebaseDatabase mDatabase;

    public ChatAdapter(ArrayList<ListMessage> message, Context context, String chatUID) {
        this.message = message;
        this.context = context;
        this.chatUID = chatUID;
        this.searchText = "";
        timeFormattor = new TimeModule();
        mDatabase = FirebaseDatabase.getInstance();
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == MSG_LEFT) {
            View inflater = LayoutInflater.from(context).inflate(R.layout.chat_baloon_left, parent, false);
            return new MessageViewHolder(inflater);
        } else if (viewType == MSG_LEFT_GROUP){
            View inflater = LayoutInflater.from(context).inflate(R.layout.chat_grup_baloon_left, parent, false);
            return new MessageViewHolder(inflater);
        } else if (viewType == MSG_RIGHT) {
            View inflater = LayoutInflater.from(context).inflate(R.layout.chat_baloon_right, parent, false);
            return new MessageViewHolder(inflater);
        } else {
            View inflater = LayoutInflater.from(context).inflate(R.layout.list_chat_date, parent, false);
            return new HeaderViewHolder(inflater);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
        int type = getItemViewType(position);
        if (type == ListMessage.TYPE_HEADER) {
            HeaderViewHolder holder = (HeaderViewHolder) viewHolder;
            MessageDate date = (MessageDate) message.get(position);
            holder.messageDate.setText(date.getDate());
        } else {
            MessageViewHolder holder = (MessageViewHolder) viewHolder;
            Message msg = (Message) message.get(position);
            holder.bindToListMessage(msg);

            // listener untuk memunculkan action mode
            if (getItemViewType(position) == MSG_RIGHT) {
                holder.itemView.setOnLongClickListener(v -> {
                    ((AppCompatActivity)v.getContext()).startSupportActionMode(ChatAdapter.this).setTag("delete");
                    holder.selectItem(msg, holder.itemView);
                    return true;
                });
                holder.itemView.setOnClickListener(v -> {
                    if (mode != null && mode.getTag().equals("delete")) {
                        holder.selectItem(msg, holder.itemView);
                        if (selectedItem.size() < 1) {
                            mode.finish();
                        }
                    }
                });
            }
        }
    }

    @Override
    public int getItemViewType(int position) {
        mAuth = FirebaseAuth.getInstance();
        if (message.get(position).getListType() == ListMessage.TYPE_HEADER) {
            return message.get(position).getListType();
        } else {
            Message msg = (Message) message.get(position);
            if(msg.getSenderUID().equals(mAuth.getCurrentUser().getUid())) {
                return MSG_RIGHT;
            } else {
                if (msg.getType().equals("group"))
                    return MSG_LEFT_GROUP;
                else
                    return MSG_LEFT;
            }
        }
    }

    @Override
    public int getItemCount() {
        return message.size();
    }

    @Override
    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
        MenuInflater inflater = mode.getMenuInflater();
        inflater.inflate(R.menu.delete_menu, menu);
        this.mode = mode;
        return true;
    }

    @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        return false;
    }

    @Override
    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
        if (item.getItemId() == R.id.menu_delete) {
            DatabaseReference mRef = mDatabase.getReference();
            Map<String, Object> toDelete = new HashMap<>();
            for (Message msg : selectedItem) {
                toDelete.put("message/"+msg.getSenderUID()+"/"+chatUID+"/"+msg.getUid(), null);
                for (String uid : msg.getReceiverUID()) {
                    toDelete.put("message/"+uid+"/"+chatUID+"/"+msg.getUid(), null);
                }
            }
            mRef.updateChildren(toDelete).addOnSuccessListener(aVoid -> {
                mode.finish();
                Toast.makeText(context, R.string.delete_chat_succes, Toast.LENGTH_SHORT).show();
            });

        }
        return false;
    }

    @Override
    public void onDestroyActionMode(ActionMode mode) {
        this.mode = null;
        selectedItem.clear();
        if (selectedViews.size() > 0) {
            for (View v: selectedViews) {
                v.setBackgroundResource(R.color.cardUnselected);
            }
            selectedViews.clear();
        }
        notifyDataSetChanged();
    }

    class HeaderViewHolder extends  RecyclerView.ViewHolder {

        TextView messageDate;

        public HeaderViewHolder(@NonNull View itemView) {
            super(itemView);
            messageDate = itemView.findViewById(R.id.tv_message_date_header);
        }
    }

    class MessageViewHolder extends RecyclerView.ViewHolder implements ActivityCompat.OnRequestPermissionsResultCallback {
        final LinearLayout chatBaloon;
        final LinearLayout layoutFileName;
        final TextView tvFileName;
        final ImageView image;
        final ImageButton download;
        final TextView message;
        final TextView time;
        final TextView sender;

        private Message data;

        private MessageViewHolder(@NonNull View itemView) {
            super(itemView);

            chatBaloon = itemView.findViewById(R.id.chat_baloon);
            layoutFileName = itemView.findViewById(R.id.layout_file_name);
            tvFileName = itemView.findViewById(R.id.tv_file_name);
            sender = itemView.findViewById(R.id.tv_sender);
            image = itemView.findViewById(R.id.baloon_image);
            download = itemView.findViewById(R.id.imageButton);
            message = itemView.findViewById(R.id.baloon_message);
            time = itemView.findViewById(R.id.baloon_time);
        }

        private void bindToListMessage(Message data) {
            this.data = data;

            timeFormattor.setDateString(data.getTime());
            String formattedTime = timeFormattor.getDiff();

            if (data.getType().equals("image")) {
                Bitmap images = null;
                download.setVisibility(View.VISIBLE);
                image.setVisibility(View.VISIBLE);
                message.setVisibility(View.GONE);
                StorageReference reference = FirebaseStorage.getInstance().getReferenceFromUrl(data.getText());
                GlideApp.with(itemView.getContext())
                        .load(reference)
                        .diskCacheStrategy(DiskCacheStrategy.ALL)
                        .placeholder(R.drawable.ic_photo_black_24dp)
                        .into(image);

                download.setOnClickListener(v -> {
                    Toast.makeText(context, "Image Saved", Toast.LENGTH_SHORT).show();
                    try {
                        Log.d("ChatAdapter", "bindToListMessage: download from firebase");
                        GlideApp.with(itemView.getContext())
                                .asBitmap()
                                .load(reference)
                                .apply(new RequestOptions()
                                        .override(150, 150)
                                        .diskCacheStrategy(DiskCacheStrategy.ALL))
                                .listener(new RequestListener<Bitmap>() {
                                    @Override
                                    public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
                                        return false;
                                    }

                                    @Override
                                    public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
                                        Log.d("ChatAdapter", "onResourceReady: download from firebase complete");
                                        saveImageToDevice(resource);
                                        return false;
                                    }
                                }).submit();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            } else if (data.getType().equals("file")) {
                StorageReference fileRef = FirebaseStorage.getInstance().getReferenceFromUrl(data.getText());
                layoutFileName.setVisibility(View.VISIBLE);
                image.setImageResource(R.drawable.pdf_icon);
                image.setVisibility(View.VISIBLE);
                message.setVisibility(View.GONE);

                ViewGroup.LayoutParams params = tvFileName.getLayoutParams();
                params.width = context.getResources().getDimensionPixelSize(R.dimen.chat_file_name_dimen);
                tvFileName.setLayoutParams(params);
                tvFileName.setText(fileRef.getName());
                // show file preview
                File localFile = new File(Environment.getDownloadCacheDirectory(), fileRef.getName());

                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    if (!localFile.exists()) {
                        Thread t = new Thread(new Runnable() {
                            @Override
                            public void run() {
//                                File localFile = new File(Environment.getDownloadCacheDirectory(), File.separator+"Tel-me");
                                fileRef.getFile(localFile).addOnSuccessListener(taskSnapshot -> {
                                    Bitmap bitmap = pdfToBitmap(localFile);
                                    image.setImageBitmap(bitmap);
                                });
                            }
                        });
                        t.start();
                    } else {
                        Bitmap bitmap = pdfToBitmap(localFile);
                        image.setImageBitmap(bitmap);
                    }
                } else {
                    image.setImageResource(R.drawable.pdf_icon);
                }

                // check file
                if (data.getSenderUID().equals(mAuth.getCurrentUser().getUid())) {
                    download.setVisibility(View.GONE);
                    params.width = context.getResources().getDimensionPixelSize(R.dimen.chat_image_dimen);
                    tvFileName.setLayoutParams(params);
                } else {
                    File storageDir = new File(Environment.getExternalStorageDirectory(),
                            File.separator + "Tel-Me" + File.separator + "File");
                    File file = new File(storageDir + File.separator + fileRef.getName());
                    if (file.exists()) {
                        download.setVisibility(View.GONE);
                        params.width = context.getResources().getDimensionPixelSize(R.dimen.chat_image_dimen);
                        tvFileName.setLayoutParams(params);
                    } else {
                        download.setVisibility(View.VISIBLE);
                        download.setOnClickListener(aVoid -> {
                            downloadFile(fileRef);
                        });
                    }
                }

                // open file trigger
                image.setOnClickListener(v -> {
                    File storageDir = new File(Environment.getExternalStorageDirectory(),
                            File.separator + "Tel-Me" + File.separator + "File");
                    File file = new File(storageDir + File.separator + fileRef.getName());
                    if (file.exists()) {
                        intentToFile(file);
                    } else {
                        downloadFile(fileRef);
                    }
                });
            } else {
                layoutFileName.setVisibility(View.INVISIBLE);
                image.setVisibility(View.GONE);
                download.setVisibility(View.GONE);
                message.setVisibility(View.VISIBLE);
                message.setText(data.getText());
            }
            time.setText(formattedTime);
        }

        private void selectItem(Message data, View itemView) {
            if (selectedItem.contains(data)) {
                itemView.setBackgroundResource(R.color.cardUnselected);
                selectedItem.remove(data);
                selectedViews.remove(itemView);
            } else {
                itemView.setBackgroundResource(R.color.cardSelected);
                selectedItem.add(data);
                selectedViews.add(itemView);
            }
        }

        private void saveImageToDevice(Bitmap image) {
            Log.d("ChatAdapter", "saveImageToDevice: start saving");
            String savedImagePath = null;
            String imageFileName = "IMG_"+image.getConfig().name()+".jpeg";
            File storageDir = new File(Environment.getExternalStorageDirectory(), File.separator + "Tel-Me");
            boolean success = true;
            if (!storageDir.exists()) {
                success = storageDir.mkdirs();
                Log.d("ChatAdapter", "directory not created");
            }
            if (success) {
                Log.d("ChatAdapter", "saveImageToDevice: directory has been created");
                File imageFile = new File(storageDir, imageFileName);
                savedImagePath = imageFile.getAbsolutePath();
                try {
                    OutputStream fOut = new FileOutputStream(imageFile);
                    image.compress(Bitmap.CompressFormat.JPEG, 100, fOut);
                    fOut.close();
                    galleryAddPic(savedImagePath);
                } catch (Exception e) {
                    Log.d("ChatAdapter", "saveImageToDevice: "+e);
                }
            }else {
            }
        }

        private void galleryAddPic(String imagePath) {
            Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
            File f = new File(imagePath);
            Uri contentUri = Uri.fromFile(f);
            mediaScanIntent.setData(contentUri);
        }

        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        private Bitmap pdfToBitmap(File pdfFile) {
            Bitmap bitmap = null;
            try {
                PdfRenderer renderer = new PdfRenderer(ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY));
                // get first page
                PdfRenderer.Page page = renderer.openPage(0);

                int width = context.getResources().getDisplayMetrics().densityDpi / 72 * page.getWidth();
                int height = context.getResources().getDisplayMetrics().densityDpi / 72 * page.getHeight();
                bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);

                page.close();
                renderer.close();
            } catch (Exception e) {
                e.printStackTrace();
            }

            return bitmap;
        }

        private void downloadFile(StorageReference ref) {
            if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions((Activity) context, new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, FILE_RQ);
            } else {
                try {
                    File storageDir = new File(Environment.getExternalStorageDirectory(),
                            File.separator + "Tel-Me" + File.separator + "File");
                    if (!storageDir.exists()) {
                        storageDir.mkdirs();
                        Log.d("ChatAdapter", "directory not created");
                    }
                    String filename = ref.getName();
                    File localFile = new File(storageDir, filename);
                    ref.getFile(localFile).addOnSuccessListener(task -> {
                        download.setVisibility(View.GONE);
                        ViewGroup.LayoutParams params = tvFileName.getLayoutParams();
                        params.width = context.getResources().getDimensionPixelSize(R.dimen.chat_image_dimen);
                        tvFileName.setLayoutParams(params);

                        intentToFile(localFile);
                    }).addOnFailureListener(e -> {
                        Toast.makeText(context, "File gagal diunduh.", Toast.LENGTH_SHORT).show();
                        Log.e("ChatAdapter", "error: "+e.getMessage(), e);
                    });
                } catch (Exception e) {
                    Log.e("ChatAdapter", "downloadFile: "+e.getMessage(), e);
                }
            }
        }

        private void intentToFile(File file) {
            Uri fileUri = FileProvider.getUriForFile(context, context.getPackageName()+".provider", file);
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setDataAndType(fileUri, "application/pdf");
            intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            context.startActivity(intent);
        }

        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            if (requestCode == FILE_RQ) {
                StorageReference fileRef = FirebaseStorage.getInstance().getReferenceFromUrl(data.getText());
                downloadFile(fileRef);
            }
        }
    }
}

这是我的 recyclerview 项目

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:id="@+id/chat_baloon"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.cardview.widget.CardView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="right"
            android:layout_margin="8dp"
            app:cardBackgroundColor="#FDC3C1"
            app:cardCornerRadius="15dp">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:padding="8dp">

                <ImageView
                    android:id="@+id/baloon_image"
                    android:layout_width="200dp"
                    android:layout_height="200dp"
                    android:adjustViewBounds="true"
                    android:scaleType="centerCrop"
                    android:src="@drawable/ic_photo_black_24dp"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />

                <LinearLayout
                    android:id="@+id/layout_file_name"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:background="#80000000"
                    android:orientation="horizontal"
                    app:layout_constraintBottom_toBottomOf="@+id/baloon_image"
                    app:layout_constraintEnd_toEndOf="@+id/baloon_image"
                    app:layout_constraintStart_toStartOf="@+id/baloon_image">

                    <TextView
                        android:id="@+id/tv_file_name"
                        android:layout_width="160dp"
                        android:layout_height="wrap_content"
                        android:ellipsize="end"
                        android:maxLines="1"
                        android:padding="8dp"
                        android:textColor="@android:color/white"
                        tools:text="File Name" />

                </LinearLayout>

                <TextView
                    android:id="@+id/baloon_message"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="4dp"
                    android:fontFamily="@font/roboto"
                    android:textColor="@android:color/black"
                    android:textSize="16sp"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/baloon_image"
                    tools:text="Message" />

                <TextView
                    android:id="@+id/baloon_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginEnd="2dp"
                    android:textSize="10sp"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/baloon_message"
                    tools:text="20.40" />

                <ImageButton
                    android:id="@+id/imageButton"
                    android:layout_width="30dp"
                    android:layout_height="30dp"
                    android:layout_gravity="end"
                    android:layout_marginEnd="6dp"
                    android:layout_marginBottom="3dp"
                    android:background="@drawable/circle_grey"
                    android:contentDescription="downloadButton"
                    android:src="@drawable/ic_file_download_white_24dp"
                    app:layout_constraintBottom_toBottomOf="@+id/baloon_image"
                    app:layout_constraintEnd_toEndOf="@+id/baloon_image" />

            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.cardview.widget.CardView>
    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

感谢您的宝贵时间。

【问题讨论】:

  • 尝试实现分页概念。看看这个blog.iamsuleiman.com/…。
  • 如果您遇到问题,最好在发布问题时创建MCVE。您为此问题发布了近 600 行代码。人们需要解析和尝试在线调试的内容很多。请编辑您的问题并隔离问题,这样可以增加获得帮助的机会。
  • 如果您考虑在某个时间点尝试使用Cloud Firestore,您可以在此处找到有关如何创建完整且实用的Firestore Chat App 的教程。
  • @SachinVarma 好的,谢谢
  • @AlexMamo 这是我第一次在 Stackoverflow 上发帖,所以我现在才知道,谢谢您的建议。

标签: java android firebase android-recyclerview


【解决方案1】:

约束布局很重;尝试将其更改为更轻的组件,如线性布局或旧版本的约束布局

【讨论】:

  • 我只知道约束布局很重,我会尝试转换它。谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-26
  • 2015-11-29
  • 1970-01-01
  • 2020-10-11
  • 2016-09-18
  • 1970-01-01
相关资源
最近更新 更多