Improve cold start performance.

This commit is contained in:
Cody Henthorne
2026-05-29 09:12:35 -04:00
committed by Michelle Tang
parent de2a5ea440
commit 89bffe39ae
6 changed files with 97 additions and 20 deletions
@@ -79,6 +79,7 @@ import org.thoughtcrime.securesms.jobs.GroupV2UpdateSelfProfileKeyJob;
import org.thoughtcrime.securesms.jobs.InAppPaymentAuthCheckJob;
import org.thoughtcrime.securesms.jobs.InAppPaymentKeepAliveJob;
import org.thoughtcrime.securesms.jobs.LinkedDeviceInactiveCheckJob;
import org.thoughtcrime.securesms.jobs.MessageSendLogCleanupJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob;
import org.thoughtcrime.securesms.jobs.ProfileUploadJob;
@@ -229,7 +230,7 @@ public class ApplicationContext extends Application implements AppForegroundObse
.addPostRender(RefreshSvrCredentialsJob::enqueueIfNecessary)
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
.addPostRender(EmojiSearchIndexDownloadJob::scheduleIfNecessary)
.addPostRender(() -> SignalDatabase.messageLog().trimOldMessages(System.currentTimeMillis(), RemoteConfig.retryRespondMaxAge()))
.addPostRender(MessageSendLogCleanupJob::enqueue)
.addPostRender(() -> JumboEmoji.updateCurrentVersion(this))
.addPostRender(RetrieveRemoteAnnouncementsJob::enqueue)
.addPostRender(AndroidTelecomUtil::registerPhoneAccount)
@@ -1465,6 +1465,7 @@ object BackupRepository {
}
SignalDatabase.remappedRecords.clearCache()
SignalDatabase.remappedRecords.trimStaleMappings()
AppDependencies.recipientCache.clear()
AppDependencies.recipientCache.warmUp()
SignalDatabase.threads.clearCache()
@@ -56,16 +56,11 @@ class RemappedRecordTables internal constructor(context: Context?, databaseHelpe
fun getAllRecipientMappings(): Map<RecipientId, RecipientId> {
val recipientMap: MutableMap<RecipientId, RecipientId> = HashMap()
readableDatabase.withinTransaction { db ->
trimInvalidRecipientEntries(db)
trimInvalidThreadEntries(db)
val mappings = getAllMappings(db, Recipients.TABLE_NAME)
for (mapping in mappings) {
val oldId = RecipientId.from(mapping.oldId)
val newId = RecipientId.from(mapping.newId)
recipientMap[oldId] = newId
}
val mappings = getAllMappings(readableDatabase, Recipients.TABLE_NAME)
for (mapping in mappings) {
val oldId = RecipientId.from(mapping.oldId)
val newId = RecipientId.from(mapping.newId)
recipientMap[oldId] = newId
}
return recipientMap
@@ -74,16 +69,21 @@ class RemappedRecordTables internal constructor(context: Context?, databaseHelpe
fun getAllThreadMappings(): Map<Long, Long> {
val threadMap: MutableMap<Long, Long> = HashMap()
readableDatabase.withinTransaction { db ->
val mappings = getAllMappings(db, Threads.TABLE_NAME)
for (mapping in mappings) {
threadMap[mapping.oldId] = mapping.newId
}
val mappings = getAllMappings(readableDatabase, Threads.TABLE_NAME)
for (mapping in mappings) {
threadMap[mapping.oldId] = mapping.newId
}
return threadMap
}
fun trimStaleMappings() {
writableDatabase.withinTransaction { db ->
trimInvalidRecipientEntries(db)
trimInvalidThreadEntries(db)
}
}
fun addRecipientMapping(oldId: RecipientId, newId: RecipientId) {
addMapping(Recipients.TABLE_NAME, Mapping(oldId.toLong(), newId.toLong()))
}
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.database;
import androidx.annotation.NonNull;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.signal.network.util.Preconditions;
@@ -11,6 +12,7 @@ import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Merging together recipients and threads is messy business. We can easily replace *almost* all of
@@ -30,8 +32,10 @@ class RemappedRecords {
private static final RemappedRecords INSTANCE = new RemappedRecords();
private Map<RecipientId, RecipientId> recipientMap;
private Map<Long, Long> threadMap;
private volatile Map<RecipientId, RecipientId> recipientMap;
private volatile Map<Long, Long> threadMap;
private final AtomicBoolean staleTrimScheduled = new AtomicBoolean(false);
private RemappedRecords() {}
@@ -106,13 +110,31 @@ class RemappedRecords {
private void ensureRecipientMapIsPopulated() {
if (recipientMap == null) {
recipientMap = SignalDatabase.remappedRecords().getAllRecipientMappings();
Map<RecipientId, RecipientId> loaded = SignalDatabase.remappedRecords().getAllRecipientMappings();
synchronized (this) {
if (recipientMap == null) {
recipientMap = loaded;
}
}
scheduleStaleTrimIfNeeded(loaded.isEmpty());
}
}
private void ensureThreadMapIsPopulated() {
if (threadMap == null) {
threadMap = SignalDatabase.remappedRecords().getAllThreadMappings();
Map<Long, Long> loaded = SignalDatabase.remappedRecords().getAllThreadMappings();
synchronized (this) {
if (threadMap == null) {
threadMap = loaded;
}
}
scheduleStaleTrimIfNeeded(loaded.isEmpty());
}
}
private void scheduleStaleTrimIfNeeded(boolean loadedMapWasEmpty) {
if (!loadedMapWasEmpty && staleTrimScheduled.compareAndSet(false, true)) {
SignalExecutors.BOUNDED.execute(() -> SignalDatabase.remappedRecords().trimStaleMappings());
}
}
@@ -205,6 +205,7 @@ public final class JobManagerFactories {
put(LocalPlaintextArchiveJob.KEY, new LocalPlaintextArchiveJob.Factory());
put(LocalBackupJobApi29.KEY, new LocalBackupJobApi29.Factory());
put(MarkerJob.KEY, new MarkerJob.Factory());
put(MessageSendLogCleanupJob.KEY, new MessageSendLogCleanupJob.Factory());
put(MultiDeviceAttachmentBackfillMissingJob.KEY, new MultiDeviceAttachmentBackfillMissingJob.Factory());
put(MultiDeviceAttachmentBackfillUpdateJob.KEY, new MultiDeviceAttachmentBackfillUpdateJob.Factory());
put(MultiDeviceBlockedUpdateJob.KEY, new MultiDeviceBlockedUpdateJob.Factory());
@@ -0,0 +1,52 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.jobs
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.util.RemoteConfig
import java.util.concurrent.TimeUnit
/**
* Trims expired entries out of the message send log after a delay.
*/
class MessageSendLogCleanupJob private constructor(parameters: Parameters) : Job(parameters) {
companion object {
const val KEY = "MessageSendLogCleanupJob"
@JvmStatic
fun enqueue() {
AppDependencies.jobManager.add(
MessageSendLogCleanupJob(
Parameters.Builder()
.setInitialDelay(TimeUnit.MINUTES.toMillis(1))
.setLifespan(TimeUnit.HOURS.toMillis(1))
.setMaxInstancesForFactory(1)
.setQueue(KEY)
.build()
)
)
}
}
override fun serialize(): ByteArray? = null
override fun getFactoryKey(): String = KEY
override fun onFailure() = Unit
override fun run(): Result {
SignalDatabase.messageLog.trimOldMessages(System.currentTimeMillis(), RemoteConfig.retryRespondMaxAge)
return Result.success()
}
class Factory : Job.Factory<MessageSendLogCleanupJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): MessageSendLogCleanupJob {
return MessageSendLogCleanupJob(parameters)
}
}
}