Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Losing reference to Android service when returning to Activity after screen orientation change

I have an Activity that starts and binds to a Service. I then have another Activity, launched from the first. Upon returning to the first Activity from the second, I need to invoke a method of the Service (saves some data).

While viewing each Activity, my Activity lifecycle methods appear to cope adequately with screen orientation changes, provided I return to the same screen orientation before exiting the Activity.

The problem happens when I return to the first Activity with a different orientation from when I left it. If that happens, I lose my reference to my Service and consequently run into a NullPointerException in onActivityResult(). So if I launch my second Activity in portrait mode, switch to landscape while viewing the second Activity, and return to the first Activity in landscape mode, it'll crash.

What might I be missing? I don't want to use the manifest file to indicate I'll handle configuration changes - strikes me as a somewhat ugly hack which doesn't address the main problem. Unless I'm missing something again...

Here are extracts from my lifecycle methods from the first Activity:

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

// start and bind to service
startService(smsIntent);
connection = new SMServiceConnection();
bindService(smsIntent, connection, Context.BIND_AUTO_CREATE);

}

@Override
protected void onRestart()
{
super.onRestart();
}

@Override
protected void onResume()
{
super.onResume();
}

@Override
protected void onPause()
{
super.onPause();
sms.save(); // autosave
}

@Override
protected void onStop()
{
super.onStop();
unbindService(connection);
// stopService(smsIntent); //doesn't appear to have any effect
}

@Override
protected void onDestroy()
{
super.onDestroy();
}

EDIT: Here are extracts from my SMServiceConnection class, which is a private inner class in my Activity, which extends from a custom ServiceConnection class.

@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
    super.onServiceConnected(name, service);
    msg("Service connected");
    sms = getSMService();
    if (sms != null)
    {
    String s = sms.getStuff(); //etc.; haven't listed every method invoked on sms

        sms.saveSurvey();
    }
    } else
    {
    System.out.println("sms is null!");
    }

}

@Override
public void onServiceDisconnected(ComponentName name)
{
    super.onServiceDisconnected(name);
    msg("Service disconnected");
}

My ServiceConnection superclass is something like:

public class MyServiceConnection implements ServiceConnection
{
    private boolean serviceAvailable = false;
    private SMService sms;

    public void onServiceConnected(ComponentName name, IBinder service)
    {
        serviceAvailable = true;
        LocalBinder b = (LocalBinder) service;
        sms = b.getService();
    }

    public void onServiceDisconnected(ComponentName name)
    {
        serviceAvailable = false;
    }

    public boolean isServiceAvailable()
    {
    return serviceAvailable;
    }

    public SMService getSMService()
    {
    return sms;
    }


    }
like image 665
Spinner Avatar asked Oct 09 '22 15:10

Spinner


1 Answers

Your problem might be that onActivityResult() is called prior to the service being binded again when you return, which happens slightly after the bindcall in onstart.

I would try one of two things:

  1. Try saving the data as pending information:

    if (sms == null) {
        mPendingResultCode = resultCode;
        mPedingResultData = new Bundle(intent.getExtras()); } else {
        handleData(resultCode, intent.getExtras()); }
    

    And then later in onServiceConnected call handleData(mPedingResultCode, mPedingResultData) if for example mPendingResultData != null

    And make sure to unset mPendingResultCode and mPendingResultData or some other indicator when you are done with the data.

  2. I'm unsure of this, but would perhaps try add the data handling to the back of the event queue by doing something like this in onActivityResult:

    final Bundle data = new Bundle(intent.getExtras);
    new Handler().postRunnable(new Runnable() {
        public void run() {
            "do some stuff with data and resultcode (which should be final in the parameter list anyway)"
        }
    }
    

As you said you should not change the manifest to not recreate on orientation changed. It is an ugly hack that does not solve the problem and I don't like when that is suggested. If you do that you still risk the same thing happening if some other configuration has changed such as language or if the activity has been destroyed temporarily by the framework to save resources.

configchanges in manifest must only be used if you want to get callbacks on the change rather than recreate for a good. reason. Laziness is not one.

like image 57
Sebastian Olsson Avatar answered Oct 12 '22 01:10

Sebastian Olsson