Fix memory leak with PlaybackViewModel

Fix a memory leak involving a stray context that PlaybackViewModel would store.
This commit is contained in:
OxygenCobalt 2020-10-26 18:39:39 -06:00
parent 85475b5c61
commit 3251b84e23
13 changed files with 29 additions and 57 deletions

View file

@ -23,9 +23,10 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<service android:name=".playback.PlaybackService" <service
android:name=".playback.PlaybackService"
android:icon="@drawable/ic_launcher_foreground" android:icon="@drawable/ic_launcher_foreground"
android:description="@string/description_service_playback" android:description="@string/label_service_playback"
android:stopWithTask="false" /> android:stopWithTask="false" />
</application> </application>
</manifest> </manifest>

View file

@ -1,5 +1,6 @@
package org.oxycblt.auxio package org.oxycblt.auxio
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -15,6 +16,7 @@ import com.google.android.material.tabs.TabLayoutMediator
import org.oxycblt.auxio.databinding.FragmentMainBinding import org.oxycblt.auxio.databinding.FragmentMainBinding
import org.oxycblt.auxio.library.LibraryFragment import org.oxycblt.auxio.library.LibraryFragment
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
import org.oxycblt.auxio.playback.PlaybackService
import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.songs.SongsFragment import org.oxycblt.auxio.songs.SongsFragment
import org.oxycblt.auxio.theme.accent import org.oxycblt.auxio.theme.accent
@ -23,9 +25,7 @@ import org.oxycblt.auxio.theme.getTransparentAccent
import org.oxycblt.auxio.theme.toColor import org.oxycblt.auxio.theme.toColor
class MainFragment : Fragment() { class MainFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels { private val playbackModel: PlaybackViewModel by activityViewModels()
PlaybackViewModel.Factory(requireContext())
}
private val shownFragments = listOf(0, 1) private val shownFragments = listOf(0, 1)
private val tabIcons = listOf( private val tabIcons = listOf(
@ -105,6 +105,11 @@ class MainFragment : Fragment() {
} }
} }
// Start the playback service.
Intent(requireContext(), PlaybackService::class.java).also {
requireContext().startService(it)
}
Log.d(this::class.simpleName, "Fragment Created.") Log.d(this::class.simpleName, "Fragment Created.")
return binding.root return binding.root

View file

@ -22,9 +22,7 @@ class AlbumDetailFragment : Fragment() {
private val args: AlbumDetailFragmentArgs by navArgs() private val args: AlbumDetailFragmentArgs by navArgs()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels { private val playbackModel: PlaybackViewModel by activityViewModels()
PlaybackViewModel.Factory(requireContext())
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,

View file

@ -20,9 +20,7 @@ import org.oxycblt.auxio.theme.disable
class ArtistDetailFragment : Fragment() { class ArtistDetailFragment : Fragment() {
private val args: ArtistDetailFragmentArgs by navArgs() private val args: ArtistDetailFragmentArgs by navArgs()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels { private val playbackModel: PlaybackViewModel by activityViewModels()
PlaybackViewModel.Factory(requireContext())
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,

View file

@ -21,9 +21,7 @@ class GenreDetailFragment : Fragment() {
private val args: GenreDetailFragmentArgs by navArgs() private val args: GenreDetailFragmentArgs by navArgs()
private val detailModel: DetailViewModel by activityViewModels() private val detailModel: DetailViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels { private val playbackModel: PlaybackViewModel by activityViewModels()
PlaybackViewModel.Factory(requireContext())
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,

View file

@ -35,9 +35,7 @@ import org.oxycblt.auxio.theme.resolveAttr
class LibraryFragment : Fragment(), SearchView.OnQueryTextListener { class LibraryFragment : Fragment(), SearchView.OnQueryTextListener {
private val libraryModel: LibraryViewModel by activityViewModels() private val libraryModel: LibraryViewModel by activityViewModels()
private val playbackModel: PlaybackViewModel by activityViewModels { private val playbackModel: PlaybackViewModel by activityViewModels()
PlaybackViewModel.Factory(requireContext())
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,

View file

@ -16,9 +16,7 @@ import org.oxycblt.auxio.databinding.FragmentCompactPlaybackBinding
import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.MusicStore
class CompactPlaybackFragment : Fragment() { class CompactPlaybackFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels { private val playbackModel: PlaybackViewModel by activityViewModels()
PlaybackViewModel.Factory(requireContext())
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,

View file

@ -22,9 +22,7 @@ import org.oxycblt.auxio.theme.toColor
// TODO: Add a swipe-to-next-track function using a ViewPager // TODO: Add a swipe-to-next-track function using a ViewPager
class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener { class PlaybackFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
private val playbackModel: PlaybackViewModel by activityViewModels { private val playbackModel: PlaybackViewModel by activityViewModels()
PlaybackViewModel.Factory(requireActivity().application)
}
// TODO: Implement nav to artists/albums // TODO: Implement nav to artists/albums
override fun onCreateView( override fun onCreateView(

View file

@ -139,12 +139,13 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
} }
private fun createNotification(): Notification { private fun createNotification(): Notification {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel( val channel = NotificationChannel(
CHANNEL_ID, CHANNEL_ID,
getString(R.string.description_playback), getString(R.string.label_notif_playback),
NotificationManager.IMPORTANCE_DEFAULT NotificationManager.IMPORTANCE_DEFAULT
) )
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
@ -156,7 +157,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
) )
.setSmallIcon(R.drawable.ic_song) .setSmallIcon(R.drawable.ic_song)
.setContentTitle(getString(R.string.app_name)) .setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.description_playback)) .setContentText(getString(R.string.label_is_playing))
.setPriority(NotificationCompat.PRIORITY_DEFAULT) .setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setChannelId(CHANNEL_ID) .setChannelId(CHANNEL_ID)
.build() .build()

View file

@ -1,13 +1,10 @@
package org.oxycblt.auxio.playback package org.oxycblt.auxio.playback
import android.content.Context
import android.content.Intent
import android.util.Log import android.util.Log
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Genre
@ -21,7 +18,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
// TODO: Implement Looping Modes // TODO: Implement Looping Modes
// TODO: Implement User Queue // TODO: Implement User Queue
// TODO: Implement Persistence through Bundles/Databases/Idk // TODO: Implement Persistence through Bundles/Databases/Idk
class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackStateCallback { class PlaybackViewModel() : ViewModel(), PlaybackStateCallback {
// Playback // Playback
private val mSong = MutableLiveData<Song>() private val mSong = MutableLiveData<Song>()
val song: LiveData<Song> get() = mSong val song: LiveData<Song> get() = mSong
@ -66,13 +63,6 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
private val playbackManager = PlaybackStateManager.getInstance() private val playbackManager = PlaybackStateManager.getInstance()
init { init {
// Start the service from the ViewModel.
// Yes, I know ViewModels aren't supposed to deal with this stuff but for some
// reason it only works here.
Intent(context, PlaybackService::class.java).also {
context.startService(it)
}
playbackManager.addCallback(this) playbackManager.addCallback(this)
// If the PlaybackViewModel was cleared [signified by the PlaybackStateManager having a // If the PlaybackViewModel was cleared [signified by the PlaybackStateManager having a
@ -233,6 +223,8 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
} }
private fun restorePlaybackState() { private fun restorePlaybackState() {
Log.d(this::class.simpleName, "Attempting to restore playback state.")
mSong.value = playbackManager.song mSong.value = playbackManager.song
mPosition.value = playbackManager.position mPosition.value = playbackManager.position
mQueue.value = playbackManager.queue mQueue.value = playbackManager.queue
@ -240,15 +232,4 @@ class PlaybackViewModel(private val context: Context) : ViewModel(), PlaybackSta
mIsPlaying.value = playbackManager.isPlaying mIsPlaying.value = playbackManager.isPlaying
mIsShuffling.value = playbackManager.isShuffling mIsShuffling.value = playbackManager.isShuffling
} }
class Factory(private val context: Context) : ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(PlaybackViewModel::class.java)) {
return PlaybackViewModel(context) as T
}
throw IllegalArgumentException("Unknown ViewModel class.")
}
}
} }

View file

@ -16,9 +16,7 @@ import org.oxycblt.auxio.theme.applyDivider
import org.oxycblt.auxio.theme.toColor import org.oxycblt.auxio.theme.toColor
class QueueFragment : BottomSheetDialogFragment() { class QueueFragment : BottomSheetDialogFragment() {
private val playbackModel: PlaybackViewModel by activityViewModels { private val playbackModel: PlaybackViewModel by activityViewModels()
PlaybackViewModel.Factory(requireActivity().application)
}
override fun getTheme(): Int = R.style.Theme_BottomSheetFix override fun getTheme(): Int = R.style.Theme_BottomSheetFix

View file

@ -15,9 +15,7 @@ import org.oxycblt.auxio.playback.state.PlaybackMode
import org.oxycblt.auxio.theme.applyDivider import org.oxycblt.auxio.theme.applyDivider
class SongsFragment : Fragment() { class SongsFragment : Fragment() {
private val playbackModel: PlaybackViewModel by activityViewModels { private val playbackModel: PlaybackViewModel by activityViewModels()
PlaybackViewModel.Factory(requireContext())
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,

View file

@ -27,6 +27,9 @@
<string name="label_play">Play</string> <string name="label_play">Play</string>
<string name="label_queue">Queue</string> <string name="label_queue">Queue</string>
<string name="label_queue_add">Add to queue</string> <string name="label_queue_add">Add to queue</string>
<string name="label_notif_playback">Music Playback</string>
<string name="label_service_playback">The music playback service for Auxio.</string>
<string name="label_is_playing">Auxio is playing music</string>
<!-- Hint Namespace | EditText Hints --> <!-- Hint Namespace | EditText Hints -->
<string name="hint_search_library">Search Library…</string> <string name="hint_search_library">Search Library…</string>
@ -47,9 +50,6 @@
<string name="description_skip_prev">Skip to last song</string> <string name="description_skip_prev">Skip to last song</string>
<string name="description_shuffle_on">Turn shuffle on</string> <string name="description_shuffle_on">Turn shuffle on</string>
<string name="description_shuffle_off">Turn shuffle off</string> <string name="description_shuffle_off">Turn shuffle off</string>
<string name="description_service_playback">The music playback service for Auxio</string>
<string name="description_notif_playback">Music Playback</string>
<string name="description_playback">Auxio is playing music</string>
<!-- Placeholder Namespace | Placeholder values --> <!-- Placeholder Namespace | Placeholder values -->
<string name="placeholder_genre">Unknown Genre</string> <string name="placeholder_genre">Unknown Genre</string>