Improve conversation list cold start performance.

main
Clark 2023-02-28 11:39:30 -05:00 zatwierdzone przez Greyson Parrelli
rodzic 10e8c6d795
commit f3693c966a
7 zmienionych plików z 92 dodań i 18 usunięć

Wyświetl plik

@ -73,6 +73,7 @@ import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
import org.thoughtcrime.securesms.logging.PersistentLogger;
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.SignalGlideComponents;
import org.thoughtcrime.securesms.mms.SignalGlideModule;
import org.thoughtcrime.securesms.providers.BlobProvider;
@ -176,6 +177,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addBlocking("feature-flags", FeatureFlags::init)
.addBlocking("ring-rtc", this::initializeRingRtc)
.addBlocking("glide", () -> SignalGlideModule.setRegisterGlideComponents(new SignalGlideComponents()))
.addNonBlocking(() -> GlideApp.get(this))
.addNonBlocking(this::checkIsGooglePayReady)
.addNonBlocking(this::cleanAvatarStorage)
.addNonBlocking(this::initializeRevealableMessageManager)
@ -212,6 +214,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addPostRender(PnpInitializeDevicesJob::enqueueIfNecessary)
.addPostRender(() -> ApplicationDependencies.getExoPlayerPool().getPoolStats().getMaxUnreserved())
.addPostRender(() -> SignalDatabase.groupCallRings().removeOldRings())
.addPostRender(() -> ApplicationDependencies.getRecipientCache().warmUp())
.execute();
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
@ -231,7 +234,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
SignalExecutors.BOUNDED.execute(() -> {
FeatureFlags.refreshIfNecessary();
ApplicationDependencies.getRecipientCache().warmUp();
RetrieveProfileJob.enqueueRoutineFetchIfNecessary(this);
executePendingContactSync();
KeyCachingService.onAppForegrounded(this);

Wyświetl plik

@ -6,6 +6,7 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.ViewTreeObserver;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -37,6 +38,8 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
private VoiceNoteMediaController mediaController;
private ConversationListTabsViewModel conversationListTabsViewModel;
private boolean onFirstRender = false;
public static @NonNull Intent clearTop(@NonNull Context context) {
Intent intent = new Intent(context, MainActivity.class);
@ -53,6 +56,21 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
super.onCreate(savedInstanceState, ready);
setContentView(R.layout.main_activity);
final View content = findViewById(android.R.id.content);
content.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
// Use pre draw listener to delay drawing frames till conversation list is ready
if (onFirstRender) {
content.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
} else {
return false;
}
}
});
mediaController = new VoiceNoteMediaController(this, true);
@ -158,6 +176,10 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
}
}
public void onFirstRender() {
onFirstRender = true;
}
@Override
public @NonNull VoiceNoteMediaController getVoiceNoteMediaController() {
return mediaController;

Wyświetl plik

@ -82,6 +82,7 @@ import org.signal.core.util.Stopwatch;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.concurrent.SimpleTask;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.MainActivity;
import org.thoughtcrime.securesms.MainFragment;
import org.thoughtcrime.securesms.MainNavigator;
import org.thoughtcrime.securesms.MuteDialog;
@ -850,7 +851,11 @@ public class ConversationListFragment extends MainFragment implements ActionMode
}
private void updateSearchToolbarHint(@NonNull ConversationFilterRequest conversationFilterRequest) {
requireCallback().getSearchToolbar().get().setSearchInputHint(
Stub<Material3SearchToolbar> searchToolbar = requireCallback().getSearchToolbar();
if (!searchToolbar.resolved()) {
return;
}
searchToolbar.get().setSearchInputHint(
conversationFilterRequest.getFilter() == ConversationFilter.OFF ? R.string.SearchToolbar_search : R.string.SearchToolbar_search_unread_chats
);
}
@ -887,13 +892,14 @@ public class ConversationListFragment extends MainFragment implements ActionMode
startupStopwatch.split("data-set");
SignalLocalMetrics.ColdStart.onConversationListDataLoaded();
defaultAdapter.unregisterAdapterDataObserver(this);
list.post(() -> {
AppStartup.getInstance().onCriticalRenderEventEnd();
startupStopwatch.split("first-render");
startupStopwatch.stop(TAG);
mediaControllerOwner.getVoiceNoteMediaController().finishPostpone();
if (getContext() != null) {
ConversationFragment.prepare(getContext());
if (requireActivity() instanceof MainActivity) {
((MainActivity) requireActivity()).onFirstRender();
}
list.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
list.removeOnLayoutChangeListener(this);
list.post(ConversationListFragment.this::onFirstRender);
}
});
}
@ -968,6 +974,17 @@ public class ConversationListFragment extends MainFragment implements ActionMode
});
}
private void onFirstRender() {
AppStartup.getInstance().onCriticalRenderEventEnd();
startupStopwatch.split("first-render");
startupStopwatch.stop(TAG);
mediaControllerOwner.getVoiceNoteMediaController().finishPostpone();
requireCallback().getSearchToolbar().get();
if (getContext() != null) {
ConversationFragment.prepare(getContext());
}
}
private void onConversationListChanged(@NonNull List<Conversation> conversations) {
LinearLayoutManager layoutManager = (LinearLayoutManager) list.getLayoutManager();
int firstVisibleItem = layoutManager != null ? layoutManager.findFirstCompletelyVisibleItemPosition() : -1;

Wyświetl plik

@ -8,6 +8,7 @@ import android.database.MergeCursor
import android.net.Uri
import androidx.core.content.contentValuesOf
import com.fasterxml.jackson.annotation.JsonProperty
import org.json.JSONObject
import org.jsoup.helper.StringUtil
import org.signal.core.util.CursorUtil
import org.signal.core.util.SqlUtil
@ -59,6 +60,7 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.ConversationUtil
import org.thoughtcrime.securesms.util.JsonUtils
import org.thoughtcrime.securesms.util.JsonUtils.SaneJSONObject
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.isScheduled
import org.whispersystems.signalservice.api.push.ServiceId
@ -1743,9 +1745,21 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
val extraString = cursor.getString(cursor.getColumnIndexOrThrow(SNIPPET_EXTRAS))
val extra: Extra? = if (extraString != null) {
try {
JsonUtils.fromJson(extraString, Extra::class.java)
} catch (e: IOException) {
Log.w(TAG, "Failed to decode extras!")
val jsonObject = SaneJSONObject(JSONObject(extraString))
Extra(
isViewOnce = jsonObject.getBoolean("isRevealable"),
isSticker = jsonObject.getBoolean("isSticker"),
stickerEmoji = jsonObject.getString("stickerEmoji"),
isAlbum = jsonObject.getBoolean("isAlbum"),
isRemoteDelete = jsonObject.getBoolean("isRemoteDelete"),
isMessageRequestAccepted = jsonObject.getBoolean("isMessageRequestAccepted"),
isGv2Invite = jsonObject.getBoolean("isGv2Invite"),
groupAddedBy = jsonObject.getString("groupAddedBy"),
individualRecipientId = jsonObject.getString("individualRecipientId")!!,
bodyRanges = jsonObject.getString("bodyRanges"),
isScheduled = jsonObject.getBoolean("isScheduled")
)
} catch (exception: Exception) {
null
}
} else {

Wyświetl plik

@ -87,6 +87,7 @@ public class ApplicationDependencies {
private static final Object LOCK = new Object();
private static final Object FRAME_RATE_TRACKER_LOCK = new Object();
private static final Object JOB_MANAGER_LOCK = new Object();
private static final Object SIGNAL_HTTP_CLIENT_LOCK = new Object();
private static Application application;
private static Provider provider;
@ -544,7 +545,7 @@ public class ApplicationDependencies {
public static @NonNull OkHttpClient getSignalOkHttpClient() {
if (signalOkHttpClient == null) {
synchronized (LOCK) {
synchronized (SIGNAL_HTTP_CLIENT_LOCK) {
if (signalOkHttpClient == null) {
try {
OkHttpClient baseClient = ApplicationDependencies.getOkHttpClient();

Wyświetl plik

@ -4,6 +4,7 @@ import android.content.Context
import android.net.Uri
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
@ -142,7 +143,6 @@ object EmojiFiles {
private fun getDirectory(context: Context): File = File(context.getEmojiDirectory(), this.uuid.toString()).apply { mkdir() }
companion object {
private val objectMapper = ObjectMapper().registerKotlinModule()
@JvmStatic
@ -150,7 +150,12 @@ object EmojiFiles {
fun readVersion(context: Context, skipValidation: Boolean = false): Version? {
val version = try {
getInputStream(context, context.getVersionFile()).use {
objectMapper.readValue(it, Version::class.java)
val tree: JsonNode = objectMapper.readTree(it)
Version(
version = tree["version"].asInt(),
uuid = objectMapper.convertValue(tree["uuid"], UUID::class.java),
density = tree["density"].asText()
)
}
} catch (e: Exception) {
Log.w(TAG, "Could not read current emoji version from disk.", e)
@ -222,8 +227,15 @@ object EmojiFiles {
@JvmStatic
fun read(context: Context, version: Version): NameCollection {
try {
getInputStream(context, context.getNameFile(version.uuid)).use {
return objectMapper.readValue(it)
getInputStream(context, context.getNameFile(version.uuid)).use { inputStream ->
val tree: JsonNode = objectMapper.readTree(inputStream)
val elements = tree["names"].elements().asSequence().map {
Name(
name = it["name"].asText(),
uuid = objectMapper.convertValue(it["uuid"], UUID::class.java)
)
}.toList()
return NameCollection(objectMapper.convertValue(tree["versionUuid"], UUID::class.java), elements)
}
} catch (e: Exception) {
return NameCollection(version.uuid, listOf())

Wyświetl plik

@ -11,6 +11,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import javax.annotation.Nullable;
public class JsonUtils {
private static final ObjectMapper objectMapper = new ObjectMapper();
@ -54,7 +56,7 @@ public class JsonUtils {
this.delegate = delegate;
}
public String getString(String name) throws JSONException {
public @Nullable String getString(String name) throws JSONException {
if (delegate.isNull(name)) return null;
else return delegate.getString(name);
}
@ -63,6 +65,10 @@ public class JsonUtils {
return delegate.getLong(name);
}
public boolean getBoolean(String name) throws JSONException {
return delegate.getBoolean(name);
}
public boolean isNull(String name) {
return delegate.isNull(name);
}