Search code examples
javaandroidandroid-activityservicelifecycle

Activity lifecycle with bound service


I am trying to learn Android services, but having a little bit of trouble.

I have a simple service with a MediaPlayer which plays some streams from the internet. I bind the service to my MainActivity, set an URL on the service and start playing the stream. This works fine. The service immediately becomes a foreground service with a notification. I can successfully change URL's from the MainActivity and subsequently start a new stream. However, there are a couple of things I want to implement.

I do not want a notification when the MainActivity is visible to the user, only when the user presses the home button or back button I want the service to start playing in the foreground. When the user clicks on the notification I want the MainActivity to reopen and the stream uninterrupted. Does this mean my MainActivity can never be destroyed?

As of now, when I press the home button the stream keeps playing and clicking the notification makes the MainActivity recreate the service (the stream stops and starts to play again). I actually want the Service to never stop playing unless the user kills the app by swiping it in the multitasking window (like Spotify does it).

My service code is as follows:

public class StreamService extends Service implements
    MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener,
    MediaPlayer.OnCompletionListener {

    private MediaPlayer player;
    private final IBinder musicBind = new StreamBinder();
    private StreamInfo mCurrentStream;
    private static final int NOTIFY_ID = 1;

    public void onCreate() {
        super.onCreate();
        initMusicPlayer();
    }

    public void initMusicPlayer() {
        player = new MediaPlayer();
        player.setWakeMode(getApplicationContext(),
            PowerManager.PARTIAL_WAKE_LOCK);
        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        player.setOnPreparedListener(this);
        player.setOnCompletionListener(this);
        player.setOnErrorListener(this);
    }

    public class StreamBinder extends Binder {
        public StreamService getService() {
            return StreamService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return musicBind;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        player.stop();
        player.release();
        return false;
    }

    public void playStream() {
        player.reset();
        try {
            player.setDataSource(this, mCurrentStream.getUrl());
        } catch (Exception e) {
            Log.e("MUSIC SERVICE", "Error setting data source", e);
        }
        player.prepareAsync();
    }

    public void setStream(StreamInfo stream) {
        mCurrentStream = stream;
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        mp.reset();
        return false;
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        mp.start();
        Intent notIntent = new Intent(this, MainActivity.class);
        notIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendInt = PendingIntent.getActivity(this, 0,
            notIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        Notification.Builder builder = new Notification.Builder(this);

        builder.setContentIntent(pendInt)
            .setSmallIcon(R.drawable.ic_action_navigation_chevron_right)
            .setTicker(mCurrentStream.getTitle())
            .setOngoing(true)
            .setContentTitle("Playing")
            .setContentText(mCurrentStream.getTitle());
        Notification not = builder.build();
        startForeground(NOTIFY_ID, not);
    }

    @Override
    public void onDestroy() {
        stopForeground(true);
    }
}

And my MainActivity:

public class MainActivity extends AppCompatActivity implements ServiceConnection {

    private static final String TAG = MainActivity.class.getSimpleName();

    private StreamService mStreamService;
    private boolean musicBound = false;
    private Intent playIntent;

    @Override
    protected void onStart() {
        super.onStart();
        if (playIntent == null) {
            playIntent = new Intent(this, StreamService.class);
            bindService(playIntent, this, MainActivity.BIND_AUTO_CREATE);
            startService(playIntent);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startStreaming();
    }


    private void startStreaming() {
        mStreamService.setStream(getSelectedStream());
        mStreamService.playStream();
    }

    @Override
    protected void onDestroy() {
        stopService(playIntent);
        mStreamService = null;
        super.onDestroy();
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        StreamService.StreamBinder binder = (StreamService.StreamBinder) service;
        mStreamService = binder.getService();
        musicBound = true;
        startStreaming();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        musicBound = false;
    }

    public StreamInfo getSelectedStream() {
        //Returns some stream from a widget
    }
}

Of course there is a widget in my MainActivity with a listener and when the selection changes the startStreaming() method is called.

Can anyone point me in the right direction?


Solution

  • I do not want a notification when the MainActivity is visible to the user, only when the user presses the home button or back button I want the service to start playing in the foreground.

    Keep a boolean flag in your Service to indicate if something is bound to it. Check the flag before displaying the notification. So for example:

    @Override
    public IBinder onBind(Intent intent) {
        mBound = true;
        hideNotifications();
        return musicBind;
    }
    
    @Override
    public void onRebind(Intent intent) {
        mBound = true;
        hideNotifications();
    }
    

    When the user clicks on the notification I want the MainActivity to reopen and the stream uninterrupted. Does this mean my MainActivity can never be destroyed?

    You need to unbind your activity onStop().

    As of now, when I press the home button the stream keeps playing and clicking the notification makes the MainActivity recreate the service (the stream stops and starts to play again). I actually want the Service to never stop playing unless the user kills the app by swiping it in the multitasking window (like Spotify does it).

    onStart() of your Activity check if service is running and rebind to it instead of recreating it. If it's not running - create and bind.