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).
This commit is contained in:
Stypox 2023-12-20 20:22:45 +01:00
parent 35c1dfd145
commit 80f33daeeb
No known key found for this signature in database
GPG Key ID: 4BDF1B40A49FDD23
4 changed files with 40 additions and 26 deletions

View File

@ -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

View File

@ -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
)
}

View File

@ -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<StreamInfoItem>,
val errors: List<Throwable>,
) {
constructor(
subscription: SubscriptionEntity,
originalInfo: Info,
info: Info,
streams: List<StreamInfoItem>,
errors: List<Throwable>,
) : 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<StreamInfoItem>
}

View File

@ -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)
fun updateFromInfo(info: FeedUpdateInfo) {
val subscriptionEntity = subscriptionTable.getSubscription(info.uid)
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
)
}
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)
}