Perf: timeline should reuse one background looper thread

This commit is contained in:
ganfra 2019-07-02 19:12:20 +02:00
parent 37199da52f
commit 5b102485bc
1 changed files with 32 additions and 23 deletions

View File

@ -30,7 +30,12 @@ import im.vector.matrix.android.api.util.addTo
import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.* import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.findIncludingEvent import im.vector.matrix.android.internal.database.query.findIncludingEvent
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
@ -38,7 +43,13 @@ import im.vector.matrix.android.internal.database.query.whereInRoom
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.Debouncer import im.vector.matrix.android.internal.util.Debouncer
import io.realm.* import io.realm.OrderedCollectionChangeSet
import io.realm.OrderedRealmCollectionChangeListener
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
import timber.log.Timber import timber.log.Timber
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -50,7 +61,6 @@ import kotlin.collections.HashMap
private const val INITIAL_LOAD_SIZE = 20 private const val INITIAL_LOAD_SIZE = 20
private const val MIN_FETCHING_COUNT = 30 private const val MIN_FETCHING_COUNT = 30
private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE private const val DISPLAY_INDEX_UNKNOWN = Int.MIN_VALUE
private const val THREAD_NAME = "TIMELINE_DB_THREAD"


internal class DefaultTimeline( internal class DefaultTimeline(
private val roomId: String, private val roomId: String,
@ -64,18 +74,22 @@ internal class DefaultTimeline(
private val allowedTypes: List<String>? private val allowedTypes: List<String>?
) : Timeline { ) : Timeline {


private companion object {
val BACKGROUND_HANDLER = Handler(
HandlerThread("TIMELINE_DB_THREAD").apply { start() }.looper
)
}

override var listener: Timeline.Listener? = null override var listener: Timeline.Listener? = null
set(value) { set(value) {
field = value field = value
backgroundHandler.get()?.post { BACKGROUND_HANDLER.post {
postSnapshot() postSnapshot()
} }
} }


private val isStarted = AtomicBoolean(false) private val isStarted = AtomicBoolean(false)
private val isReady = AtomicBoolean(false) private val isReady = AtomicBoolean(false)
private val backgroundHandlerThread = AtomicReference<HandlerThread>()
private val backgroundHandler = AtomicReference<Handler>()
private val mainHandler = Handler(Looper.getMainLooper()) private val mainHandler = Handler(Looper.getMainLooper())
private val backgroundRealm = AtomicReference<Realm>() private val backgroundRealm = AtomicReference<Realm>()
private val cancelableBag = CancelableBag() private val cancelableBag = CancelableBag()
@ -168,14 +182,14 @@ internal class DefaultTimeline(
override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
if (roomId == this@DefaultTimeline.roomId) { if (roomId == this@DefaultTimeline.roomId) {
Timber.v("New session id detected for this room") Timber.v("New session id detected for this room")
backgroundHandler.get()?.post { BACKGROUND_HANDLER.post {
val realm = backgroundRealm.get() val realm = backgroundRealm.get()
var hasChange = false var hasChange = false
builtEvents.forEachIndexed { index, timelineEvent -> builtEvents.forEachIndexed { index, timelineEvent ->
if (timelineEvent.isEncrypted()) { if (timelineEvent.isEncrypted()) {
val eventContent = timelineEvent.root.content.toModel<EncryptedEventContent>() val eventContent = timelineEvent.root.content.toModel<EncryptedEventContent>()
if (eventContent?.sessionId == sessionId if (eventContent?.sessionId == sessionId
&& (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) { && (timelineEvent.root.mClearEvent == null || timelineEvent.root.mCryptoError != null)) {
//we need to rebuild this event //we need to rebuild this event
EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let { EventEntity.where(realm, eventId = timelineEvent.root.eventId!!).findFirst()?.let {
builtEvents[index] = timelineEventFactory.create(it, realm) builtEvents[index] = timelineEventFactory.create(it, realm)
@ -194,7 +208,7 @@ internal class DefaultTimeline(
// Public methods ****************************************************************************** // Public methods ******************************************************************************


override fun paginate(direction: Timeline.Direction, count: Int) { override fun paginate(direction: Timeline.Direction, count: Int) {
backgroundHandler.get()?.post { BACKGROUND_HANDLER.post {
if (!canPaginate(direction)) { if (!canPaginate(direction)) {
return@post return@post
} }
@ -211,13 +225,8 @@ internal class DefaultTimeline(
override fun start() { override fun start() {
if (isStarted.compareAndSet(false, true)) { if (isStarted.compareAndSet(false, true)) {
Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId") Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId")
val handlerThread = HandlerThread(THREAD_NAME + hashCode())
handlerThread.start()
val handler = Handler(handlerThread.looper)
this.backgroundHandlerThread.set(handlerThread)
this.backgroundHandler.set(handler)
cryptoService.addNewSessionListener(newSessionListener) cryptoService.addNewSessionListener(newSessionListener)
handler.post { BACKGROUND_HANDLER.post {
val realm = Realm.getInstance(realmConfiguration) val realm = Realm.getInstance(realmConfiguration)
backgroundRealm.set(realm) backgroundRealm.set(realm)
clearUnlinkedEvents(realm) clearUnlinkedEvents(realm)
@ -246,14 +255,12 @@ internal class DefaultTimeline(
if (isStarted.compareAndSet(true, false)) { if (isStarted.compareAndSet(true, false)) {
cryptoService.removeSessionListener(newSessionListener) cryptoService.removeSessionListener(newSessionListener)
Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId") Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId")
backgroundHandler.get()?.post { BACKGROUND_HANDLER.post {
cancelableBag.cancel() cancelableBag.cancel()
liveEvents.removeAllChangeListeners() liveEvents.removeAllChangeListeners()
backgroundRealm.getAndSet(null).also { backgroundRealm.getAndSet(null).also {
it.close() it.close()
} }
backgroundHandler.set(null)
backgroundHandlerThread.getAndSet(null)?.quit()
} }
} }
} }
@ -387,9 +394,9 @@ internal class DefaultTimeline(
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) { private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
val token = getTokenLive(direction) ?: return val token = getTokenLive(direction) ?: return
val params = PaginationTask.Params(roomId = roomId, val params = PaginationTask.Params(roomId = roomId,
from = token, from = token,
direction = direction.toPaginationDirection(), direction = direction.toPaginationDirection(),
limit = limit) limit = limit)


Timber.v("Should fetch $limit items $direction") Timber.v("Should fetch $limit items $direction")
paginationTask.configureWith(params) paginationTask.configureWith(params)
@ -400,7 +407,7 @@ internal class DefaultTimeline(
Timber.v("Success fetching $limit items $direction from pagination request") Timber.v("Success fetching $limit items $direction from pagination request")
} else { } else {
// Database won't be updated, so we force pagination request // Database won't be updated, so we force pagination request
backgroundHandler.get()?.post { BACKGROUND_HANDLER.post {
executePaginationTask(direction, limit) executePaginationTask(direction, limit)
} }
} }
@ -441,6 +448,7 @@ internal class DefaultTimeline(
if (count < 1) { if (count < 1) {
return 0 return 0
} }
val start = System.currentTimeMillis()
val offsetResults = getOffsetResults(startDisplayIndex, direction, count) val offsetResults = getOffsetResults(startDisplayIndex, direction, count)
if (offsetResults.isEmpty()) { if (offsetResults.isEmpty()) {
return 0 return 0
@ -459,7 +467,8 @@ internal class DefaultTimeline(
builtEventsIdMap.entries.filter { it.value >= position }.forEach { it.setValue(it.value + 1) } builtEventsIdMap.entries.filter { it.value >= position }.forEach { it.setValue(it.value + 1) }
builtEventsIdMap[eventEntity.eventId] = position builtEventsIdMap[eventEntity.eventId] = position
} }
Timber.v("Built ${offsetResults.size} items from db") val time = System.currentTimeMillis() - start
Timber.v("Built ${offsetResults.size} items from db in $time ms")
return offsetResults.size return offsetResults.size
} }