【发布时间】: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