From 9a933ce486bc689b68efc5c602bd301b89c9e262 Mon Sep 17 00:00:00 2001 From: Ash Date: Sun, 5 Jun 2022 21:05:13 +0800 Subject: [PATCH] Optimize feed loading and chunk sync --- .../java/me/ash/reader/data/model/Filter.kt | 13 +++ .../data/repository/AbstractRssRepository.kt | 12 ++- .../data/repository/LocalRssRepository.kt | 36 ++++--- .../ash/reader/data/repository/RssHelper.kt | 10 +- .../ash/reader/data/repository/SyncWorker.kt | 6 +- .../reader/ui/page/home/feeds/FeedsPage.kt | 18 ++-- .../ui/page/home/feeds/FeedsViewModel.kt | 99 +++++++++---------- 7 files changed, 113 insertions(+), 81 deletions(-) diff --git a/app/src/main/java/me/ash/reader/data/model/Filter.kt b/app/src/main/java/me/ash/reader/data/model/Filter.kt index 4d1ada5..68f8447 100644 --- a/app/src/main/java/me/ash/reader/data/model/Filter.kt +++ b/app/src/main/java/me/ash/reader/data/model/Filter.kt @@ -7,7 +7,10 @@ import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.StarOutline import androidx.compose.material.icons.rounded.Subject import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import me.ash.reader.R @@ -40,9 +43,19 @@ class Filter( } } +@Stable @Composable fun Filter.getName(): String = when (this) { Filter.Unread -> stringResource(R.string.unread) Filter.Starred -> stringResource(R.string.starred) else -> stringResource(R.string.all) +} + +@OptIn(ExperimentalComposeUiApi::class) +@Stable +@Composable +fun Filter.getDesc(important: Int): String = when (this) { + Filter.Starred -> pluralStringResource(R.plurals.starred_desc, important, important) + Filter.Unread -> pluralStringResource(R.plurals.unread_desc, important, important) + else -> pluralStringResource(R.plurals.all_desc, important, important) } \ No newline at end of file diff --git a/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt b/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt index bfa666d..7f0dddb 100644 --- a/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/AbstractRssRepository.kt @@ -27,6 +27,7 @@ abstract class AbstractRssRepository constructor( private val feedDao: FeedDao, private val workManager: WorkManager, private val dispatcherIO: CoroutineDispatcher, + private val dispatcherDefault: CoroutineDispatcher, ) { abstract suspend fun updateArticleInfo(article: Article) @@ -113,11 +114,18 @@ abstract class AbstractRssRepository constructor( else -> articleDao.queryImportantCountWhenIsAll(accountId) }.mapLatest { mapOf( + // Groups + *(it.groupBy { it.groupId }.map { + it.key to it.value.sumOf { it.important } + }.toTypedArray()), + // Feeds *(it.map { it.feedId to it.important - }.toTypedArray()) + }.toTypedArray()), + // All summary + "sum" to it.sumOf { it.important } ) - }.flowOn(dispatcherIO) + }.flowOn(dispatcherDefault) } suspend fun findFeedById(id: String): Feed? { diff --git a/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt b/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt index ef60fe6..819700f 100644 --- a/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt +++ b/app/src/main/java/me/ash/reader/data/repository/LocalRssRepository.kt @@ -9,7 +9,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.withContext +import kotlinx.coroutines.supervisorScope import me.ash.reader.data.dao.AccountDao import me.ash.reader.data.dao.ArticleDao import me.ash.reader.data.dao.FeedDao @@ -35,14 +35,14 @@ class LocalRssRepository @Inject constructor( private val notificationHelper: NotificationHelper, private val accountDao: AccountDao, private val groupDao: GroupDao, - @DispatcherDefault - private val dispatcherDefault: CoroutineDispatcher, @DispatcherIO private val dispatcherIO: CoroutineDispatcher, + @DispatcherDefault + private val dispatcherDefault: CoroutineDispatcher, workManager: WorkManager, ) : AbstractRssRepository( context, accountDao, articleDao, groupDao, - feedDao, workManager, dispatcherIO + feedDao, workManager, dispatcherIO, dispatcherDefault ) { override suspend fun updateArticleInfo(article: Article) { @@ -71,25 +71,29 @@ class LocalRssRepository @Inject constructor( } override suspend fun sync(coroutineWorker: CoroutineWorker): ListenableWorker.Result { - return withContext(dispatcherDefault) { + return supervisorScope { val preTime = System.currentTimeMillis() val accountId = context.currentAccountId feedDao.queryAll(accountId) .also { coroutineWorker.setProgress(setIsSyncing(true)) } - .map { feed -> async { syncFeed(feed) } } - .awaitAll() + .chunked(16) .forEach { - if (it.isNotify) { - notificationHelper.notify( - FeedWithArticle( - it.feedWithArticle.feed, + it.map { feed -> async { syncFeed(feed) } } + .awaitAll() + .forEach { + if (it.isNotify) { + notificationHelper.notify( + FeedWithArticle( + it.feedWithArticle.feed, + articleDao.insertListIfNotExist(it.feedWithArticle.articles) + ) + ) + } else { articleDao.insertListIfNotExist(it.feedWithArticle.articles) - ) - ) - } else { - articleDao.insertListIfNotExist(it.feedWithArticle.articles) - } + } + } } + Log.i("RlOG", "onCompletion: ${System.currentTimeMillis() - preTime}") accountDao.queryById(accountId)?.let { account -> accountDao.update( diff --git a/app/src/main/java/me/ash/reader/data/repository/RssHelper.kt b/app/src/main/java/me/ash/reader/data/repository/RssHelper.kt index b457ae1..78b3911 100644 --- a/app/src/main/java/me/ash/reader/data/repository/RssHelper.kt +++ b/app/src/main/java/me/ash/reader/data/repository/RssHelper.kt @@ -80,10 +80,12 @@ class RssHelper @Inject constructor( feed: Feed, latestLink: String? = null, ): List
{ - return withContext(dispatcherIO) { - val accountId = context.currentAccountId - val syndFeed = SyndFeedInput().build(XmlReader(inputStream(okHttpClient, feed.url))) - syndFeed.entries.asSequence() + val accountId = context.currentAccountId + return inputStream(okHttpClient, feed.url).use { + SyndFeedInput().apply { isPreserveWireFeed = true } + .build(XmlReader(it)) + .entries + .asSequence() .takeWhile { latestLink == null || latestLink != it.link } .map { article(feed, accountId, it) } .toList() diff --git a/app/src/main/java/me/ash/reader/data/repository/SyncWorker.kt b/app/src/main/java/me/ash/reader/data/repository/SyncWorker.kt index 50cdaf2..36b419d 100644 --- a/app/src/main/java/me/ash/reader/data/repository/SyncWorker.kt +++ b/app/src/main/java/me/ash/reader/data/repository/SyncWorker.kt @@ -6,6 +6,8 @@ import androidx.hilt.work.HiltWorker import androidx.work.* import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.util.* import java.util.concurrent.TimeUnit @@ -18,7 +20,9 @@ class SyncWorker @AssistedInject constructor( override suspend fun doWork(): Result { Log.i("RLog", "doWork: ") - return rssRepository.get().sync(this) + return withContext(Dispatchers.Default) { + rssRepository.get().sync(this@SyncWorker) + } } companion object { diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt index fb9dbc0..8d2c455 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsPage.kt @@ -65,6 +65,10 @@ fun FeedsPage( val feedsUiState = feedsViewModel.feedsUiState.collectAsStateValue() val filterUiState = homeViewModel.filterUiState.collectAsStateValue() + val importantSum = + feedsUiState.importantSum.collectAsStateValue(initial = stringResource(R.string.loading)) + val groupWithFeedList = + feedsUiState.groupWithFeedList.collectAsStateValue(initial = emptyList()) val newVersion = LocalNewVersionNumber.current val skipVersion = LocalSkipVersionNumber.current @@ -107,9 +111,9 @@ fun FeedsPage( } } - val groupsVisible = remember(feedsUiState.groupWithFeedList) { + val groupsVisible = remember(groupWithFeedList) { mutableStateMapOf( - *(feedsUiState.groupWithFeedList.filterIsInstance().map { + *(groupWithFeedList.filterIsInstance().map { it.group.id to groupListExpand.value }.toTypedArray()) ) @@ -121,7 +125,7 @@ fun FeedsPage( LaunchedEffect(filterUiState) { snapshotFlow { filterUiState }.collect { - feedsViewModel.fetchData(it) + feedsViewModel.pullFeeds(it) } } @@ -180,7 +184,7 @@ fun FeedsPage( item { Banner( title = filterUiState.filter.getName(), - desc = feedsUiState.importantSum.ifEmpty { stringResource(R.string.loading) }, + desc = importantSum, icon = filterUiState.filter.iconOutline, action = { Icon( @@ -207,7 +211,7 @@ fun FeedsPage( ) Spacer(modifier = Modifier.height(8.dp)) } - itemsIndexed(feedsUiState.groupWithFeedList) { index, groupWithFeed -> + itemsIndexed(groupWithFeedList) { index, groupWithFeed -> when (groupWithFeed) { is GroupFeedsView.Group -> { if (index != 0) { @@ -218,7 +222,7 @@ fun FeedsPage( group = groupWithFeed.group, alpha = groupAlpha, indicatorAlpha = groupIndicatorAlpha, - isEnded = { index == feedsUiState.groupWithFeedList.lastIndex }, + isEnded = { index == groupWithFeedList.lastIndex }, onExpanded = { groupsVisible[groupWithFeed.group.id] = !(groupsVisible[groupWithFeed.group.id] ?: false) @@ -239,7 +243,7 @@ fun FeedsPage( feed = groupWithFeed.feed, alpha = groupAlpha, badgeAlpha = feedBadgeAlpha, - isEnded = { index == feedsUiState.groupWithFeedList.lastIndex || feedsUiState.groupWithFeedList[index + 1] is GroupFeedsView.Group }, + isEnded = { index == groupWithFeedList.lastIndex || groupWithFeedList[index + 1] is GroupFeedsView.Group }, isExpanded = { groupsVisible[groupWithFeed.feed.groupId] ?: false }, ) { filterChange( diff --git a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt index 08d4e5a..d885ad3 100644 --- a/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt +++ b/app/src/main/java/me/ash/reader/ui/page/home/feeds/FeedsViewModel.kt @@ -2,7 +2,6 @@ package me.ash.reader.ui.page.home.feeds import android.util.Log import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.ui.util.fastForEach import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -54,52 +53,52 @@ class FeedsViewModel @Inject constructor( } } - fun fetchData(filterState: FilterState) { - viewModelScope.launch(dispatcherIO) { - pullFeeds( - isStarred = filterState.filter.isStarred(), - isUnread = filterState.filter.isUnread(), - ) - } - } - - private suspend fun pullFeeds(isStarred: Boolean, isUnread: Boolean) { - combine( - rssRepository.get().pullFeeds(), - rssRepository.get().pullImportant(isStarred, isUnread), - ) { groupWithFeedList, importantMap -> - groupWithFeedList.fastForEach { - var groupImportant = 0 - it.feeds.fastForEach { - it.important = importantMap[it.id] - groupImportant += it.important ?: 0 - } - it.group.important = groupImportant - } - groupWithFeedList - }.mapLatest { groupWithFeedList -> - _feedsUiState.update { - it.copy( - importantSum = groupWithFeedList.sumOf { it.group.important ?: 0 }.run { - when { - isStarred -> stringsRepository.getQuantityString( - R.plurals.starred_desc, - this, - this - ) - isUnread -> stringsRepository.getQuantityString( - R.plurals.unread_desc, - this, - this - ) - else -> stringsRepository.getQuantityString( - R.plurals.all_desc, + fun pullFeeds(filterState: FilterState) { + val isStarred = filterState.filter.isStarred() + val isUnread = filterState.filter.isUnread() + _feedsUiState.update { + it.copy( + importantSum = rssRepository.get().pullImportant(isStarred, isUnread) + .mapLatest { + (it["sum"] ?: 0).run { + stringsRepository.getQuantityString( + when { + isStarred -> R.plurals.starred_desc + isUnread -> R.plurals.unread_desc + else -> R.plurals.all_desc + }, this, this ) } - }, - groupWithFeedList = groupWithFeedList.map { + }.flowOn(dispatcherDefault), + groupWithFeedList = combine( + rssRepository.get().pullImportant(isStarred, isUnread), + rssRepository.get().pullFeeds() + ) { importantMap, groupWithFeedList -> + val groupIterator = groupWithFeedList.iterator() + while (groupIterator.hasNext()) { + val groupWithFeed = groupIterator.next() + val groupImportant = importantMap[groupWithFeed.group.id] ?: 0 + if ((isStarred || isUnread) && groupImportant == 0) { + groupIterator.remove() + continue + } + groupWithFeed.group.important = groupImportant + val feedIterator = groupWithFeed.feeds.iterator() + while (feedIterator.hasNext()) { + val feed = feedIterator.next() + val feedImportant = importantMap[feed.id] ?: 0 + if ((isStarred || isUnread) && feedImportant == 0) { + feedIterator.remove() + continue + } + feed.important = feedImportant + } + } + groupWithFeedList + }.mapLatest { groupWithFeedList -> + groupWithFeedList.map { mutableListOf(GroupFeedsView.Group(it.group)).apply { addAll( it.feeds.map { @@ -107,19 +106,17 @@ class FeedsViewModel @Inject constructor( } ) } - }.flatten(), - ) - } - }.catch { - Log.e("RLog", "catch in articleRepository.pullFeeds(): ${it.message}") - }.flowOn(dispatcherDefault).collect() + }.flatten() + }.flowOn(dispatcherDefault), + ) + } } } data class FeedsUiState( val account: Account? = null, - val importantSum: String = "", - val groupWithFeedList: List = emptyList(), + val importantSum: Flow = emptyFlow(), + val groupWithFeedList: Flow> = emptyFlow(), val listState: LazyListState = LazyListState(), val groupsVisible: Boolean = true, )