From a1220c77dad9fb6f85dacf3e08091c8fd305935c Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Wed, 28 Feb 2018 17:47:12 -0800 Subject: [PATCH] -Added serialized cache for transferring serializable objects too large for intent transactions. -Fixed potential transaction too large exceptions for player intents. --- .../org/schabi/newpipe/player/BasePlayer.java | 12 +- .../playlist/AbstractInfoPlayQueue.java | 1 + .../schabi/newpipe/playlist/PlayQueue.java | 1 + .../schabi/newpipe/util/NavigationHelper.java | 38 +++--- .../schabi/newpipe/util/SerializedCache.java | 112 ++++++++++++++++++ 5 files changed, 141 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/util/SerializedCache.java diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index a449b4a36..6a867110a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -65,8 +65,8 @@ import org.schabi.newpipe.player.playback.PlaybackListener; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueueAdapter; import org.schabi.newpipe.playlist.PlayQueueItem; +import org.schabi.newpipe.util.SerializedCache; -import java.io.Serializable; import java.util.concurrent.TimeUnit; import io.reactivex.Observable; @@ -106,7 +106,7 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen public static final String PLAYBACK_PITCH = "playback_pitch"; public static final String PLAYBACK_SPEED = "playback_speed"; public static final String PLAYBACK_QUALITY = "playback_quality"; - public static final String PLAY_QUEUE = "play_queue"; + public static final String PLAY_QUEUE_KEY = "play_queue_key"; public static final String APPEND_ONLY = "append_only"; public static final String SELECT_ON_APPEND = "select_on_append"; @@ -207,10 +207,10 @@ public abstract class BasePlayer implements Player.EventListener, PlaybackListen if (intent == null) return; // Resolve play queue - if (!intent.hasExtra(PLAY_QUEUE)) return; - final Serializable playQueueCandidate = intent.getSerializableExtra(PLAY_QUEUE); - if (!(playQueueCandidate instanceof PlayQueue)) return; - final PlayQueue queue = (PlayQueue) playQueueCandidate; + if (!intent.hasExtra(PLAY_QUEUE_KEY)) return; + final String intentCacheKey = intent.getStringExtra(PLAY_QUEUE_KEY); + final PlayQueue queue = SerializedCache.getInstance().take(intentCacheKey, PlayQueue.class); + if (queue == null) return; // Resolve append intents if (intent.getBooleanExtra(APPEND_ONLY, false) && playQueue != null) { diff --git a/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java index fc1fabfc4..6e63a3aaa 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java @@ -118,6 +118,7 @@ abstract class AbstractInfoPlayQueue ext public void dispose() { super.dispose(); if (fetchReactor != null) fetchReactor.dispose(); + fetchReactor = null; } private static List extractListItems(final List infos) { diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java index cd507c2bf..9005ef8d2 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueue.java @@ -84,6 +84,7 @@ public abstract class PlayQueue implements Serializable { if (eventBroadcast != null) eventBroadcast.onComplete(); if (reportingReactor != null) reportingReactor.cancel(); + eventBroadcast = null; broadcastReceiver = null; reportingReactor = null; } diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 2039dcc8e..da1019f99 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -7,6 +7,8 @@ import android.content.Intent; import android.net.Uri; import android.os.Build; import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v7.app.AlertDialog; @@ -33,9 +35,9 @@ import org.schabi.newpipe.fragments.list.feed.FeedFragment; import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment; import org.schabi.newpipe.fragments.list.search.SearchFragment; +import org.schabi.newpipe.fragments.local.bookmark.LastPlayedFragment; import org.schabi.newpipe.fragments.local.bookmark.LocalPlaylistFragment; import org.schabi.newpipe.fragments.local.bookmark.MostPlayedFragment; -import org.schabi.newpipe.fragments.local.bookmark.LastPlayedFragment; import org.schabi.newpipe.history.HistoryActivity; import org.schabi.newpipe.player.BackgroundPlayer; import org.schabi.newpipe.player.BackgroundPlayerActivity; @@ -59,39 +61,41 @@ public class NavigationHelper { // Players //////////////////////////////////////////////////////////////////////////*/ - public static Intent getPlayerIntent(final Context context, - final Class targetClazz, - final PlayQueue playQueue, - final String quality) { - Intent intent = new Intent(context, targetClazz) - .putExtra(VideoPlayer.PLAY_QUEUE, playQueue); + public static Intent getPlayerIntent(@NonNull final Context context, + @NonNull final Class targetClazz, + @NonNull final PlayQueue playQueue, + @Nullable final String quality) { + Intent intent = new Intent(context, targetClazz); + + final String cacheKey = SerializedCache.getInstance().put(playQueue, PlayQueue.class); + if (cacheKey != null) intent.putExtra(VideoPlayer.PLAY_QUEUE_KEY, cacheKey); if (quality != null) intent.putExtra(VideoPlayer.PLAYBACK_QUALITY, quality); return intent; } - public static Intent getPlayerIntent(final Context context, - final Class targetClazz, - final PlayQueue playQueue) { + public static Intent getPlayerIntent(@NonNull final Context context, + @NonNull final Class targetClazz, + @NonNull final PlayQueue playQueue) { return getPlayerIntent(context, targetClazz, playQueue, null); } - public static Intent getPlayerEnqueueIntent(final Context context, - final Class targetClazz, - final PlayQueue playQueue, + public static Intent getPlayerEnqueueIntent(@NonNull final Context context, + @NonNull final Class targetClazz, + @NonNull final PlayQueue playQueue, final boolean selectOnAppend) { return getPlayerIntent(context, targetClazz, playQueue) .putExtra(BasePlayer.APPEND_ONLY, true) .putExtra(BasePlayer.SELECT_ON_APPEND, selectOnAppend); } - public static Intent getPlayerIntent(final Context context, - final Class targetClazz, - final PlayQueue playQueue, + public static Intent getPlayerIntent(@NonNull final Context context, + @NonNull final Class targetClazz, + @NonNull final PlayQueue playQueue, final int repeatMode, final float playbackSpeed, final float playbackPitch, - final String playbackQuality) { + @Nullable final String playbackQuality) { return getPlayerIntent(context, targetClazz, playQueue, playbackQuality) .putExtra(BasePlayer.REPEAT_MODE, repeatMode) .putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed) diff --git a/app/src/main/java/org/schabi/newpipe/util/SerializedCache.java b/app/src/main/java/org/schabi/newpipe/util/SerializedCache.java new file mode 100644 index 000000000..02871aff5 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/SerializedCache.java @@ -0,0 +1,112 @@ +package org.schabi.newpipe.util; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.util.LruCache; +import android.util.Log; + +import org.schabi.newpipe.MainActivity; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.UUID; + +public class SerializedCache { + private static final boolean DEBUG = MainActivity.DEBUG; + private final String TAG = getClass().getSimpleName(); + + private static final SerializedCache instance = new SerializedCache(); + private static final int MAX_ITEMS_ON_CACHE = 5; + + private static final LruCache lruCache = + new LruCache<>(MAX_ITEMS_ON_CACHE); + + private SerializedCache() { + //no instance + } + + public static SerializedCache getInstance() { + return instance; + } + + @Nullable + public T take(@NonNull final String key, @NonNull final Class type) { + if (DEBUG) Log.d(TAG, "take() called with: key = [" + key + "]"); + synchronized (lruCache) { + return lruCache.get(key) != null ? getItem(lruCache.remove(key), type) : null; + } + } + + @Nullable + public T get(@NonNull final String key, @NonNull final Class type) { + if (DEBUG) Log.d(TAG, "get() called with: key = [" + key + "]"); + synchronized (lruCache) { + final CacheData data = lruCache.get(key); + return data != null ? getItem(data, type) : null; + } + } + + @Nullable + public String put(@NonNull T item, @NonNull final Class type) { + final String key = UUID.randomUUID().toString(); + return put(key, item, type) ? key : null; + } + + public boolean put(@NonNull final String key, @NonNull T item, + @NonNull final Class type) { + if (DEBUG) Log.d(TAG, "put() called with: key = [" + key + "], item = [" + item + "]"); + synchronized (lruCache) { + try { + lruCache.put(key, new CacheData<>(clone(item, type), type)); + return true; + } catch (final Exception error) { + Log.e(TAG, "Serialization failed for: ", error); + } + } + return false; + } + + public void clear() { + if (DEBUG) Log.d(TAG, "clear() called"); + synchronized (lruCache) { + lruCache.evictAll(); + } + } + + public long size() { + synchronized (lruCache) { + return lruCache.size(); + } + } + + @Nullable + private T getItem(@NonNull final CacheData data, @NonNull final Class type) { + return type.isAssignableFrom(data.type) ? type.cast(data.item) : null; + } + + @NonNull + private T clone(@NonNull T item, + @NonNull final Class type) throws Exception { + final ByteArrayOutputStream bytesOutput = new ByteArrayOutputStream(); + try (final ObjectOutputStream objectOutput = new ObjectOutputStream(bytesOutput)) { + objectOutput.writeObject(item); + objectOutput.flush(); + } + final Object clone = new ObjectInputStream( + new ByteArrayInputStream(bytesOutput.toByteArray())).readObject(); + return type.cast(clone); + } + + final private static class CacheData { + private final T item; + private final Class type; + + private CacheData(@NonNull final T item, @NonNull Class type) { + this.item = item; + this.type = type; + } + } +}