Skip to content

Commit

Permalink
Fix OutOfMemory when fetching feed
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
Stypox committed Dec 20, 2023
1 parent 35c1dfd commit 80f33da
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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())

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand All @@ -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>
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
}
Expand Down

0 comments on commit 80f33da

Please sign in to comment.