mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-09 13:08:33 +00:00
Add CachedLayoutInflater to improve conversation render performance.
This commit is contained in:
parent
7fd3bfa30c
commit
ed33e048ad
@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
|||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
|
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||||
import org.thoughtcrime.securesms.util.Conversions;
|
import org.thoughtcrime.securesms.util.Conversions;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||||
@ -171,8 +172,7 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
|
|||||||
case MESSAGE_TYPE_UPDATE:
|
case MESSAGE_TYPE_UPDATE:
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
|
V itemView = CachedInflater.from(parent.getContext()).inflate(getLayoutForViewType(viewType), parent, false);
|
||||||
V itemView = ViewUtil.inflate(inflater, parent, getLayoutForViewType(viewType));
|
|
||||||
|
|
||||||
itemView.setOnClickListener(view -> {
|
itemView.setOnClickListener(view -> {
|
||||||
if (clickListener != null) {
|
if (clickListener != null) {
|
||||||
@ -197,7 +197,7 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
|
|||||||
return new PlaceholderViewHolder(v);
|
return new PlaceholderViewHolder(v);
|
||||||
case MESSAGE_TYPE_HEADER:
|
case MESSAGE_TYPE_HEADER:
|
||||||
case MESSAGE_TYPE_FOOTER:
|
case MESSAGE_TYPE_FOOTER:
|
||||||
return new HeaderFooterViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.cursor_adapter_header_footer_view, parent, false));
|
return new HeaderFooterViewHolder(CachedInflater.from(parent.getContext()).inflate(R.layout.cursor_adapter_header_footer_view, parent, false));
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Cannot create viewholder for type: " + viewType);
|
throw new IllegalStateException("Cannot create viewholder for type: " + viewType);
|
||||||
}
|
}
|
||||||
@ -417,7 +417,19 @@ public class ConversationAdapter<V extends View & BindableConversationItem>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provided a pool, this will initialize it with view counts that make sense.
|
||||||
|
*/
|
||||||
@MainThread
|
@MainThread
|
||||||
|
static void initializePool(@NonNull RecyclerView.RecycledViewPool pool) {
|
||||||
|
pool.setMaxRecycledViews(MESSAGE_TYPE_INCOMING, 15);
|
||||||
|
pool.setMaxRecycledViews(MESSAGE_TYPE_OUTGOING, 15);
|
||||||
|
pool.setMaxRecycledViews(MESSAGE_TYPE_PLACEHOLDER, 15);
|
||||||
|
pool.setMaxRecycledViews(MESSAGE_TYPE_HEADER, 1);
|
||||||
|
pool.setMaxRecycledViews(MESSAGE_TYPE_FOOTER, 1);
|
||||||
|
pool.setMaxRecycledViews(MESSAGE_TYPE_UPDATE, 5);
|
||||||
|
}
|
||||||
|
|
||||||
private void cleanFastRecords() {
|
private void cleanFastRecords() {
|
||||||
synchronized (releasedFastRecords) {
|
synchronized (releasedFastRecords) {
|
||||||
Iterator<MessageRecord> recordIterator = fastRecords.iterator();
|
Iterator<MessageRecord> recordIterator = fastRecords.iterator();
|
||||||
|
@ -36,6 +36,7 @@ import android.view.ViewGroup;
|
|||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import android.widget.ViewSwitcher;
|
import android.widget.ViewSwitcher;
|
||||||
@ -105,6 +106,7 @@ import org.thoughtcrime.securesms.sms.MessageSender;
|
|||||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity;
|
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity;
|
||||||
|
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.HtmlUtil;
|
import org.thoughtcrime.securesms.util.HtmlUtil;
|
||||||
@ -161,6 +163,16 @@ public class ConversationFragment extends Fragment {
|
|||||||
private MessageRequestViewModel messageRequestViewModel;
|
private MessageRequestViewModel messageRequestViewModel;
|
||||||
private ConversationViewModel conversationViewModel;
|
private ConversationViewModel conversationViewModel;
|
||||||
|
|
||||||
|
public static void prepare(@NonNull Context context) {
|
||||||
|
FrameLayout parent = new FrameLayout(context);
|
||||||
|
parent.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT));
|
||||||
|
|
||||||
|
CachedInflater.from(context).cacheUntilLimit(R.layout.conversation_item_received, parent, 10);
|
||||||
|
CachedInflater.from(context).cacheUntilLimit(R.layout.conversation_item_sent, parent, 10);
|
||||||
|
CachedInflater.from(context).cacheUntilLimit(R.layout.conversation_item_update, parent, 5);
|
||||||
|
CachedInflater.from(context).cacheUntilLimit(R.layout.cursor_adapter_header_footer_view, parent, 2);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
super.onCreate(icicle);
|
super.onCreate(icicle);
|
||||||
@ -413,6 +425,7 @@ public class ConversationFragment extends Fragment {
|
|||||||
ConversationAdapter adapter = new ConversationAdapter(GlideApp.with(this), locale, selectionClickListener, this.recipient.get());
|
ConversationAdapter adapter = new ConversationAdapter(GlideApp.with(this), locale, selectionClickListener, this.recipient.get());
|
||||||
list.setAdapter(adapter);
|
list.setAdapter(adapter);
|
||||||
list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false));
|
list.addItemDecoration(new StickyHeaderDecoration(adapter, false, false));
|
||||||
|
ConversationAdapter.initializePool(list.getRecycledViewPool());
|
||||||
|
|
||||||
setLastSeen(conversationViewModel.getLastSeen());
|
setLastSeen(conversationViewModel.getLastSeen());
|
||||||
|
|
||||||
|
@ -236,8 +236,6 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
|||||||
|
|
||||||
bodyText.setOnLongClickListener(passthroughClickListener);
|
bodyText.setOnLongClickListener(passthroughClickListener);
|
||||||
bodyText.setOnClickListener(passthroughClickListener);
|
bodyText.setOnClickListener(passthroughClickListener);
|
||||||
|
|
||||||
bodyText.setMovementMethod(LongClickMovementMethod.getInstance(getContext()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -522,6 +520,7 @@ public class ConversationItem extends LinearLayout implements BindableConversati
|
|||||||
bodyText.setClickable(false);
|
bodyText.setClickable(false);
|
||||||
bodyText.setFocusable(false);
|
bodyText.setFocusable(false);
|
||||||
bodyText.setTextSize(TypedValue.COMPLEX_UNIT_SP, TextSecurePreferences.getMessageBodyTextSize(context));
|
bodyText.setTextSize(TypedValue.COMPLEX_UNIT_SP, TextSecurePreferences.getMessageBodyTextSize(context));
|
||||||
|
bodyText.setMovementMethod(LongClickMovementMethod.getInstance(getContext()));
|
||||||
|
|
||||||
if (messageRecord.isRemoteDelete()) {
|
if (messageRecord.isRemoteDelete()) {
|
||||||
String deletedMessage = context.getString(R.string.ConversationItem_this_message_was_deleted);
|
String deletedMessage = context.getString(R.string.ConversationItem_this_message_was_deleted);
|
||||||
|
@ -90,6 +90,7 @@ import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder;
|
|||||||
import org.thoughtcrime.securesms.components.reminder.ShareReminder;
|
import org.thoughtcrime.securesms.components.reminder.ShareReminder;
|
||||||
import org.thoughtcrime.securesms.components.reminder.SystemSmsImportReminder;
|
import org.thoughtcrime.securesms.components.reminder.SystemSmsImportReminder;
|
||||||
import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder;
|
import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder;
|
||||||
|
import org.thoughtcrime.securesms.conversation.ConversationFragment;
|
||||||
import org.thoughtcrime.securesms.conversationlist.ConversationListAdapter.ItemClickListener;
|
import org.thoughtcrime.securesms.conversationlist.ConversationListAdapter.ItemClickListener;
|
||||||
import org.thoughtcrime.securesms.conversationlist.model.MessageResult;
|
import org.thoughtcrime.securesms.conversationlist.model.MessageResult;
|
||||||
import org.thoughtcrime.securesms.conversationlist.model.SearchResult;
|
import org.thoughtcrime.securesms.conversationlist.model.SearchResult;
|
||||||
@ -118,6 +119,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
@ -264,6 +266,12 @@ public class ConversationListFragment extends MainFragment implements LoaderMana
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
ConversationFragment.prepare(requireContext());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
@ -0,0 +1,133 @@
|
|||||||
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.LayoutRes;
|
||||||
|
import androidx.annotation.MainThread;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class that can be used to pre-cache layouts. Usage flow:
|
||||||
|
*
|
||||||
|
* - At some point before you want to use the views, call {@link #cacheUntilLimit(int, ViewGroup, int)}.
|
||||||
|
* - Later, use {@link #inflate(int, ViewGroup, boolean)}, which will prefer using cached views
|
||||||
|
* before inflating new ones.
|
||||||
|
*/
|
||||||
|
public class CachedInflater {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(CachedInflater.class);
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does *not* work with the application context.
|
||||||
|
*/
|
||||||
|
public static CachedInflater from(@NonNull Context context) {
|
||||||
|
return new CachedInflater(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CachedInflater(@NonNull Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identical to {@link LayoutInflater#inflate(int, ViewGroup, boolean)}, but will prioritize
|
||||||
|
* pulling a cached view first.
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <V extends View> V inflate(@LayoutRes int layoutRes, @Nullable ViewGroup parent, boolean attachToRoot) {
|
||||||
|
View cached = ViewCache.getInstance().pull(layoutRes);
|
||||||
|
if (cached != null) {
|
||||||
|
if (parent != null && attachToRoot) {
|
||||||
|
parent.addView(cached);
|
||||||
|
}
|
||||||
|
return (V) cached;
|
||||||
|
} else {
|
||||||
|
return (V) LayoutInflater.from(context).inflate(layoutRes, parent, attachToRoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will inflate as many views as necessary until the cache holds the amount you specify.
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
public void cacheUntilLimit(@LayoutRes int layoutRes, @Nullable ViewGroup parent, int limit) {
|
||||||
|
ViewCache.getInstance().cacheUntilLimit(context, layoutRes, parent, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all cached views. This should be done if, for instance, the theme changes.
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
public void clear() {
|
||||||
|
Log.d(TAG, "Clearing view cache.");
|
||||||
|
ViewCache.getInstance().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ViewCache {
|
||||||
|
|
||||||
|
private static final ViewCache INSTANCE = new ViewCache();
|
||||||
|
|
||||||
|
private final Map<Integer, List<View>> cache = new HashMap<>();
|
||||||
|
|
||||||
|
private long lastClearTime;
|
||||||
|
|
||||||
|
static ViewCache getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
void cacheUntilLimit(Context context, @LayoutRes int layoutRes, @Nullable ViewGroup parent, int limit) {
|
||||||
|
AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
|
||||||
|
|
||||||
|
int existingCount = Util.getOrDefault(cache, layoutRes, Collections.emptyList()).size();
|
||||||
|
int inflateCount = Math.max(limit - existingCount, 0);
|
||||||
|
|
||||||
|
for (int i = 0; i < inflateCount; i++) {
|
||||||
|
final long enqueueTime = System.currentTimeMillis();
|
||||||
|
inflater.inflate(layoutRes, parent, (view, resId, p) -> {
|
||||||
|
Util.assertMainThread();
|
||||||
|
if (enqueueTime < lastClearTime) {
|
||||||
|
Log.d(TAG, "Prefetch is no longer valid. Ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<View> views = cache.get(resId);
|
||||||
|
|
||||||
|
views = views == null ? new LinkedList<>() : views;
|
||||||
|
views.add(view);
|
||||||
|
|
||||||
|
cache.put(resId, views);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
@Nullable View pull(@LayoutRes int layoutRes) {
|
||||||
|
List<View> views = cache.get(layoutRes);
|
||||||
|
return views != null && !views.isEmpty() ? views.remove(0)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
void clear() {
|
||||||
|
lastClearTime = System.currentTimeMillis();
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,7 @@ public class DynamicTheme {
|
|||||||
OverridePendingTransition.invoke(activity);
|
OverridePendingTransition.invoke(activity);
|
||||||
activity.startActivity(intent);
|
activity.startActivity(intent);
|
||||||
OverridePendingTransition.invoke(activity);
|
OverridePendingTransition.invoke(activity);
|
||||||
|
CachedInflater.from(activity).clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user