From 80f33daeeb246ee548dc707d3798ae382d137079 Mon Sep 17 00:00:00 2001 From: Stypox Date: Wed, 20 Dec 2023 20:22:45 +0100 Subject: [PATCH] Fix OutOfMemory when fetching feed Reduced memory footprint of FeedUpdateInfo objects. Those objects might stay around for a while and accumulate (up to BUFFER_COUNT_BEFORE_INSERT = 20 at the moment), so in order not to fill up the memory it's better to keep as little data as possible. Previously ChannelInfo data was stored, causing ReadyChannelTabLinkHandler objects to be also stored uselessly (and those channel tabs contain prefetched JSON data which used ~700KB of memory). --- .../feed/notifications/NotificationHelper.kt | 8 ++--- .../local/feed/service/FeedLoadManager.kt | 4 +-- .../local/feed/service/FeedUpdateInfo.kt | 31 +++++++++++++++---- .../local/subscription/SubscriptionManager.kt | 25 ++++++--------- 4 files changed, 41 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index 9f7553e1f34..8ea89368d6b 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -58,7 +58,7 @@ class NotificationHelper(val context: Context) { .setAutoCancel(true) .setCategory(NotificationCompat.CATEGORY_SOCIAL) .setGroupSummary(true) - .setGroup(data.originalInfo.url) + .setGroup(data.url) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) // Build a summary notification for Android versions < 7.0 @@ -73,7 +73,7 @@ class NotificationHelper(val context: Context) { context, data.pseudoId, NavigationHelper - .getChannelIntent(context, data.originalInfo.serviceId, data.originalInfo.url) + .getChannelIntent(context, data.serviceId, data.url) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0, false @@ -88,7 +88,7 @@ class NotificationHelper(val context: Context) { // Show individual stream notifications, set channel icon only if there is actually // one - showStreamNotifications(newStreams, data.originalInfo.serviceId, bitmap) + showStreamNotifications(newStreams, data.serviceId, bitmap) // Show summary notification manager.notify(data.pseudoId, summaryBuilder.build()) @@ -97,7 +97,7 @@ class NotificationHelper(val context: Context) { override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) { // Show individual stream notifications - showStreamNotifications(newStreams, data.originalInfo.serviceId, null) + showStreamNotifications(newStreams, data.serviceId, null) // Show summary notification manager.notify(data.pseudoId, summaryBuilder.build()) iconLoadingTargets.remove(this) // allow it to be garbage-collected diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt index b86c856fc25..0b6a8068c99 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedLoadManager.kt @@ -277,14 +277,14 @@ class FeedLoadManager(private val context: Context) { notification.value!!.newStreams = filterNewStreams(info.streams) feedDatabaseManager.upsertAll(info.uid, info.streams) - subscriptionManager.updateFromInfo(info.uid, info.originalInfo) + subscriptionManager.updateFromInfo(info) if (info.errors.isNotEmpty()) { feedResultsHolder.addErrors( info.errors.map { FeedLoadService.RequestException( info.uid, - "${info.originalInfo.serviceId}:${info.originalInfo.url}", + "${info.serviceId}:${info.url}", it ) } diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt index 12fbe8d4120..84cd8ed59a9 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/service/FeedUpdateInfo.kt @@ -3,29 +3,48 @@ package org.schabi.newpipe.local.feed.service import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.database.subscription.SubscriptionEntity import org.schabi.newpipe.extractor.Info +import org.schabi.newpipe.extractor.channel.ChannelInfo import org.schabi.newpipe.extractor.stream.StreamInfoItem +import org.schabi.newpipe.util.image.ImageStrategy +/** + * Instances of this class might stay around in memory for some time while fetching the feed, + * because of [FeedLoadManager.BUFFER_COUNT_BEFORE_INSERT]. Therefore this class should contain + * as little data as possible to avoid out of memory errors. In particular, avoid storing whole + * [ChannelInfo] objects, as they might contain raw JSON info in ready channel tabs link handlers. + */ data class FeedUpdateInfo( val uid: Long, @NotificationMode val notificationMode: Int, val name: String, val avatarUrl: String, - val originalInfo: Info, + val url: String, + val serviceId: Int, + // description and subscriberCount are null if the constructor info is from the fast feed method + val description: String?, + val subscriberCount: Long?, val streams: List, val errors: List, ) { constructor( subscription: SubscriptionEntity, - originalInfo: Info, + info: Info, streams: List, errors: List, ) : this( uid = subscription.uid, notificationMode = subscription.notificationMode, - name = subscription.name, - avatarUrl = subscription.avatarUrl, - originalInfo = originalInfo, + name = info.name, + avatarUrl = (info as? ChannelInfo)?.avatars?.let { + // if the newly fetched info is not from fast feed, then it contains updated avatars + ImageStrategy.imageListToDbUrl(it) + } ?: subscription.avatarUrl, + url = info.url, + serviceId = info.serviceId, + // there is no description and subscriberCount in the fast feed + description = (info as? ChannelInfo)?.description, + subscriberCount = (info as? ChannelInfo)?.subscriberCount, streams = streams, errors = errors, ) @@ -34,7 +53,7 @@ data class FeedUpdateInfo( * Integer id, can be used as notification id, etc. */ val pseudoId: Int - get() = originalInfo.url.hashCode() + get() = url.hashCode() lateinit var newStreams: List } diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt index bd42bbe4144..488d8b3d28d 100644 --- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt +++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionManager.kt @@ -12,12 +12,11 @@ import org.schabi.newpipe.database.stream.model.StreamEntity import org.schabi.newpipe.database.subscription.NotificationMode import org.schabi.newpipe.database.subscription.SubscriptionDAO import org.schabi.newpipe.database.subscription.SubscriptionEntity -import org.schabi.newpipe.extractor.Info import org.schabi.newpipe.extractor.channel.ChannelInfo import org.schabi.newpipe.extractor.channel.tabs.ChannelTabInfo -import org.schabi.newpipe.extractor.feed.FeedInfo import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.local.feed.FeedDatabaseManager +import org.schabi.newpipe.local.feed.service.FeedUpdateInfo import org.schabi.newpipe.util.ExtractorHelper import org.schabi.newpipe.util.image.ImageStrategy @@ -97,19 +96,15 @@ class SubscriptionManager(context: Context) { } } - fun updateFromInfo(subscriptionId: Long, info: Info) { - val subscriptionEntity = subscriptionTable.getSubscription(subscriptionId) - - if (info is FeedInfo) { - subscriptionEntity.name = info.name - } else if (info is ChannelInfo) { - subscriptionEntity.setData( - info.name, - ImageStrategy.imageListToDbUrl(info.avatars), - info.description, - info.subscriberCount - ) - } + fun updateFromInfo(info: FeedUpdateInfo) { + val subscriptionEntity = subscriptionTable.getSubscription(info.uid) + + subscriptionEntity.name = info.name + subscriptionEntity.avatarUrl = info.avatarUrl + + // these two fields are null if the feed info was fetched using the fast feed method + info.description?.let { subscriptionEntity.description = it } + info.subscriberCount?.let { subscriptionEntity.subscriberCount = it } subscriptionTable.update(subscriptionEntity) }