jpsasi
jpsasi

Reputation: 1905

Android Service - issue with BindService when use it along with startService

I am newbie to android platform. Currently working on streaming radio application.

I want to make the Application, starts the audio playback and continuos to play even application goes to background.

I have created a Service using startService() so that play back will continue even though application goes to background. I have also used bindService() to get a serviceConnection reference to perform RPC calls with service.

Application starts the service and playback started correctly and bindService able to get the reference and able to make RPC calls, everything works fine till app goes to background.

I am calling bindService() and unBindService() call in onResume() and onPause() of main activity.

When app goes to background, playback continues with out any issue. unBindservice() call detach the service connection and onUnbind(Intent) method got invoked in Service class. No issue so far.

when application comes to foreground(while service is running and playback continues), I am not getting the service connection reference (onServiceConnected() is not getting invoked), even though bindService() is called in activity onResume() method. i have also verified Service class onBind(Intent) method is also not getting invoked.

I am struggling with this issue for past few days.

What is the recommended approach to use the startService() and bindService() with user defined Android Service.

Any help to resolve this issue, highly appreciated.

Activity onResume/onPause Code.

@Override
public void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "onCreate => " + getIntent());
    super.onCreate(savedInstanceState);

    // set the default network preference, if it is not already set
    setDefaultNetwork();

    // initialize the navigation tab bars
    initializeTabBar();

    mPlayerController = AudioPlayerController.getInstance(this);

    mActionBar.setDisplayShowHomeEnabled(true);
    mActionBar.setDisplayShowTitleEnabled(true);

    mRemoteControlReceiver = new RemoteControlReceiver();
    IntentFilter filter = new IntentFilter(
            "android.intent.action.MEDIA_BUTTON");
    registerReceiver(mRemoteControlReceiver, filter);

    setVolumeControlStream(AudioManager.STREAM_MUSIC);
}

@Override
public void onResume() {
    super.onResume();
    Log.d(TAG,"onResume");
    mPlayerController.bindService();
    restoreAppState();
}

@Override
public void onPause() {
    super.onPause();
    Log.d(TAG,"onPause");
    mPlayerController.unBindService();
    storeAppState();
}

Controller class has service related start/stop/bind/unbind methods

    public void bindService() {
    Intent intent = new Intent();
    intent.setClassName("com.vikkrithik.radio.indradio",
            "com.vikkrithik.radio.indradio.AudioPlayerService");
    Log.d(TAG, "bindService Context " + mPlayerContext
            + " serviceConnection => " + audioServiceConnection);
    mPlayerContext.bindService(intent, audioServiceConnection, 0);
}

public void unBindService() {
    mPlayerContext.unbindService(audioServiceConnection);
    Log.d(TAG, "unBindService done context => " + mPlayerContext
            + " serviceConnection => " + audioServiceConnection);
}

private ServiceConnection audioServiceConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.d(TAG, "onServiceConnected " + name);
        mAudioService = AudioPlayerServiceInterface.Stub
                .asInterface(service);
    }

    public void onServiceDisconnected(ComponentName name) {
        Log.d(TAG, "onServiceDisconnected " + name);
        mAudioService = null;
    }
};
/**
 * Start the AudioService
 * 
 */
public void startAudioService(Station station) {
    Intent intent = new Intent();
    intent.setClassName("com.vikkrithik.radio.indradio",
            "com.vikkrithik.radio.indradio.AudioPlayerService");

    Bundle extras = new Bundle();
    extras.putString("station_url", station.getUrl());
    extras.putString("station_name", station.getStationName());
    intent.putExtras(extras);
    mCurrentStation = station;
    mPlayerContext.startService(intent);
    bindService();
    Log.d(TAG, "startService called");
}

public void stopAudioService() {
    //
    // Check if AudioService is already created
    //
    if (null == mAudioService)
        return;

    Intent intent = new Intent();
    intent.setClassName("com.vikkrithik.radio.indradio",
            "com.vikkrithik.radio.indradio.AudioPlayerService");
    mPlayerContext.stopService(intent);
    Log.d(TAG, "stopAudioService done");
}

Service Class Methods

@Override
public IBinder onBind(Intent arg0) {
    Log.d(TAG, "onBind invoked");
    return audioServiceStub;
}

@Override
public boolean onUnbind(Intent intent) {
    Log.d(TAG,"onUnbind invoked");
    return super.onUnbind(intent);
}

@Override
public void onCreate() {
    super.onCreate();
    Log.d(TAG, "onCreate");
    audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

    mNetworkStatusReceiver = new NetworkStatusReceiver();
    IntentFilter filter = new IntentFilter(
            ConnectivityManager.CONNECTIVITY_ACTION);
    registerReceiver(mNetworkStatusReceiver, filter);

    PreferenceManager.getDefaultSharedPreferences(this)
            .registerOnSharedPreferenceChangeListener(
                    mNetworkStatusReceiver);
}

@Override
public void onDestroy() {
    Log.d(TAG, "onDestory");
    stopPlayback();

    PreferenceManager.getDefaultSharedPreferences(this)
            .unregisterOnSharedPreferenceChangeListener(
                    mNetworkStatusReceiver);
    unregisterReceiver(mNetworkStatusReceiver);
    cancelNotification();
    audioServiceStub = null;
    super.onDestroy();
}

Logcat output

when app launches

TabbarMainActivity(15432): onResume AudioPlayerController(15432): bindService Context com.vikkrithik.radio.indradio.TabbarMainActivity@4150fdb8 serviceConnection => com.vikkrithik.radio.indradio.AudioPlayerController$1@418a2d30

When play button is pressed

AudioPlayerController(15432): bindService Context com.vikkrithik.radio.indradio.TabbarMainActivity@4150fdb8 serviceConnection => com.vikkrithik.radio.indradio.AudioPlayerController$1@418a2d30

AudioPlayerController(15432): startService called

AudioPlayerService(15432): onCreate

AudioPlayerService(15432): onBind invoked onBind() invoked

AudioPlayerService(15432): onStartCommand with IndetntIntent { cmp=xxxx (has extras) }

AudioPlayerController(15432): onServiceConnected ComponentInfo{com.vikkrithik.radio.indradio/com.vikkrithik.radio.indradio.AudioPlayerService} onServiceConnected invoked

when back button is pressed, app goes to background

TabbarMainActivity(15432): finish called

TabbarMainActivity(15432): onPause

AudioPlayerController(15432): unBindService done context => com.vikkrithik.radio.indradio.TabbarMainActivity@4150fdb8 serviceConnection => com.vikkrithik.radio.indradio.AudioPlayerController$1@418a2d30 onPause() calls unBindService()

AudioPlayerService(15432): onUnbind invoked unBind Got invoked in Service

TabbarMainActivity(15432): onDestroy

App comes to forground

TabbarMainActivity(15432): onCreate => Intent { cmp=xxxx }

TabbarMainActivity(15432): onResume bindService is called from onResume

AudioPlayerController(15432): bindService Context com.vikkrithik.radio.indradio.TabbarMainActivity@4150fdb8 serviceConnection => com.vikkrithik.radio.indradio.AudioPlayerController$1@418a2d30

After playback is stopped

AudioPlayerService(15432): stop

AudioPlayerService(15432): reset

AudioPlayerController(15432): stopAudioService done

AudioPlayerFragment(15432): removeVisualizer

AudioPlayerService(15432): onDestory

Upvotes: 1

Views: 5441

Answers (3)

Matyas
Matyas

Reputation: 13702

To be able to rebind, make sure you return true from onUnbind and in that case onRebind will be called in the Service when you call bindService from the Activity.

Service.java

@Override
public boolean onUnbind(Intent intent) {
        super.onUnbind(intent);
        /* logic */
        return true; // future binds from activity will call onRebind
}

@Override
public void onRebind(Intent intent) {
    // Is called when activity issues a `bindService` after an `undbindService`
    super.onRebind(intent);
    /*logic*/
}

Upvotes: 0

I see these problems with an app that runs a service that records vehicle trips with GPS. At the press of a button the service is started and goes on with its recording that can continue for hours if so needed. At the press of a stop button the service terminates. During recording there is an interface to communicate certain live data that display on the UI so the bind/unbind handles the interruption/reconnect cycles due to rotation, backgrounding/resuming the app and so on.

As long as the service runs there is no issues. But when I stop the recording meaning I stop the service but continue to maintain the binding/unbinding solution with the service class, the service leak messages appear. What I might try to do is to redesign things so that I either (1) care to bind unbind only while the service is running, or (2) run the service at all times during the app lifetime and add ways to tell it to begin or stop recording and let it "idle" at other times. Maybe (1) is the way to go and the approach to maintain a service connection also to an non-running service might not be the way to do it.

Upvotes: 0

hack_on
hack_on

Reputation: 2520

I have not been able to just "see" the problem, so I have created an app that does most of the things you need as a basis of a solution. It does not do networking or use the real MediaPlayer, but the service has a background thread that does output audio. You can start and stop the audio from the two buttons. I was able to hit the back button, and keep hearing the service, then start the app from the icon and stop and re-start the audio playing. I thought you may be able to follow my steps, and if it works with your device, then start adding the MediaPlayer and networking code into a functional service.

I used the following tutorial as a basis for the service code which uses messages and not Stub/interfaces to the service.

It is based on a new Android project in Eclipse, then I just added two buttons and gave them new text and Ids. The rest is here:

First the content I put in the MainActivity that eclipse created:

private final static String TAG = "MainActivity";
// Service stuff
Messenger myService = null;
boolean isBound;
private Button startBut;
private Button stopBut;

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

    startBut = (Button) findViewById(R.id.startAudio);
    startBut.setOnClickListener(new Button.OnClickListener(){
        public void onClick(View v){
            try
            {
                if (myService != null && isBound)
                {
                    Log.d(TAG, "Service Connected! sending start request");
                    Message msg = Message.obtain();
                    Bundle bundle = new Bundle();
                    bundle.putString("MyString", "start");

                    msg.setData(bundle);
                    myService.send(msg);
                }
            }
            catch (Throwable th)
            {
                Log.d(TAG, "error sending start", th);
            }
        }
    });

    stopBut = (Button) findViewById(R.id.stopAudio);
    stopBut.setOnClickListener(new Button.OnClickListener(){
        public void onClick(View v){
            try
            {
                Log.d(TAG, "Service Connected! sending stop request");

                Message msg = Message.obtain();
                Bundle bundle = new Bundle();
                bundle.putString("MyString", "stop");

                msg.setData(bundle);
                myService.send(msg);
            }
            catch (Throwable th)
            {
                Log.d(TAG, "error sending start", th);
            }
        }
    });
}

@Override
public boolean onCreateOptionsMenu(Menu menu)
{
    getMenuInflater().inflate(R.menu.activity_main, menu);
    return true;
}

@Override
protected void onPause()
{
    super.onPause();

    unBindService();
}

@Override
protected void onResume()
{
    super.onResume();
    Log.d(TAG, "Resuming: binding to service");
    Intent intent = new Intent("com.example.audioservice.AudioPlayerService");

    // Start the service ourselves because we don't want it to stop when the activity/application does.
    startService(intent);
    bindService(intent, myConnection, 0);
}

private ServiceConnection myConnection = new ServiceConnection() 
{
    public void onServiceConnected(ComponentName className, IBinder service) {
        myService = new Messenger(service);
        isBound = true;
        Log.d(TAG, "Service Connected!");
    }

    public void onServiceDisconnected(ComponentName className) {
        myService = null;
        isBound = false;
    }
};

public void unBindService() 
{
    this.unbindService(myConnection);
    Log.d(TAG, "unBindService done context => " 
            + " serviceConnection => " + myConnection);
}

Next, a small insert to the Manifest file:

<service android:enabled="true"  android:name="com.example.audioservice.AudioPlayerService"
         android:process=":my_process" >
         <intent-filter>
            <action android:name="com.example.audioservice.AudioPlayerService" >
         </action>
      </intent-filter>
    </service>

Finally the content of the AudioPlayerService class:

public class AudioPlayerService extends Service
{
private static final String TAG = "AudioPlayerService";
AudioManager audioManager;
private boolean playing = false;
private Player player;

class IncomingHandler extends Handler
{
    @Override
    public void handleMessage(Message msg)
    {
        Bundle data = msg.getData();
        String dataString = data.getString("MyString");
        if ("start".equalsIgnoreCase(dataString))
        {
            Log.d(TAG, "Got a start request");
            // Start playing
            playing = true;
        }
        else if ("stop".equalsIgnoreCase(dataString))
        {
            Log.d(TAG, "Got a stop request");
            // Stop playing
            playing = false;
        }
    }
}

@SuppressLint("HandlerLeak")
final Messenger myMessenger = new Messenger(new IncomingHandler());

@Override
public IBinder onBind(Intent arg0)
{
    Log.d(TAG, "onBind invoked");
    return myMessenger.getBinder();
}

@Override
public boolean onUnbind(Intent intent)
{
    Log.d(TAG, "onUnbind invoked");
    return super.onUnbind(intent);
}

@Override
public void onCreate()
{
    super.onCreate();
    Log.d(TAG, "onCreate");
    audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    audioManager.loadSoundEffects();
    player = new Player();
    player.start();
}

@Override
public void onDestroy()
{
    Log.d(TAG, "onDestory");
    stopPlayback();

    super.onDestroy();
}

private void stopPlayback()
{
    playing = false;
}

// Pointless Thread to let us know the service is still doing its thing
class Player extends Thread
{
    public void run()
    {
        int x = 7;
        while (1 < x)
        {
            try
            {
                if (playing)
                {
                    Thread.sleep(10000);
                    Log.d(TAG, ".");
                    audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
                }
            }
            catch (Throwable th)
            {
                Log.d(TAG, "error in Player", th);
            }
        }
    }
}
}

Upvotes: 4

Related Questions