7

Question:
How can I manage (Connect, Read, Write, Disconnect) a bluetooth connection that persists through configuration changes?

Prefer solutions that are compatible with device version 2.2 "Froyo" using ActionBarSherlock.

Problems...

  • Neither BluetoothDevice nor BluetoothSocket can be retained in onSaveState.

  • In order to keep my app responsive, the 12 second blocking call BluetoothSocket.connect() must be made on a separate thread. Starting a Runnable is the recommended way to thread long tasks, but it's a nightmare trying to recover on a configuration change. The official docs point to three different solutions.

    • Use getLastNonConfigurationInstance(), which is deprecated (seriously?!).

    • Set android:configChanges="keyboardHidden|orientation" like the BluetoothChat Sample. However, this does not account for all types of configuration changes.

    • Cancel & restart tasks like the Shelves Example. In this case, this could potentially waste another 12 seconds.

Update 1

  • Further research led me to asyncTaskLoader, but it seems like this can only update the UI on completion, and cannot provide updates.

  • The BluetoothHDP sample uses a service. Services seem focused on inter-process communication and the need to persist beyond the activity life-cycle. I don't need either of these features.

Update 2

As pointed out by Reuben, Fragment.setRetainInstance(bool) has replaced the deprecated getLastNonConfigurationInstance(). At this point, it seems like the best option is to make a persistent non-UI fragment using setRetainInstance(true).

Community
  • 1
  • 1
firyice
  • 527
  • 7
  • 24

4 Answers4

8

I would solve this problem by using a Service that would handle your bluetooth connection, and make that Service talk back to your Activity as described in this answer.

Then, you could use the ASyncTask to simply show/hide a dialog, and cancel/restart ASyncTasks on rotation, which is done by Shelves Example as you mentioned.

Services are not really that horrible and might be the best tool for your problem.

Community
  • 1
  • 1
Axarydax
  • 16,353
  • 21
  • 92
  • 151
  • This seems like a safe solution. However, this also strikes me as the equivalent of driving in a nail with a jackhammer. Services really seem like they're meant for communicating with multiple processes. Yes, they could be used for some simple tasks within a process, but it seems like too much heavy-lifting for something that should be relatively simple. – firyice Mar 31 '13 at 16:54
  • 4
    I disagree. I wanted my application to handle background backup to server - a simple 20-line IntentService was the easiest solution I could find. – Axarydax Mar 31 '13 at 17:35
4

Never place App Model (logic or data) in Visual/UI components. As you see, they come and go, and change.

Places to keep non UI related stuff like data collections, live connections, threads etc:

  • The Application class. Envelopes all other component's life cycle. Almost like a global singleton. You can use this for temporary storage.

example:

public class App extends Application {

  private static Beer sBeer;

  public static void brbHoldMyBeer(Beer b){
    sBeer = b;
  }

  public static Beer imBackWheresMyBeer(){
    return sBeer;
  }

}

Also, having a static thread Executor service in Application class, will help retaining running tasks.

  • A running background Service. To which Activities, Fragments etc can bind/unbind, and post commands/requests. This is recommended place for App wide running processes and data.

  • A non visual Fragment with setRetainInstance(true). Non visual here means that its a dummy fragment that's attached to Activity but doesn't shows any view. It's just used as a retain-able object holder for an Activity. This is recommended for Activity wide processes and data.

S.D.
  • 29,290
  • 3
  • 79
  • 130
  • drink = imBackWheresMyBeer(); – Luis Apr 07 '13 at 04:14
  • [Android documentation suggests to retain objects during configuration changes by attaching them to a non-perishing fragment.](http://developer.android.com/guide/topics/resources/runtime-changes.html#RetainingAnObject) (Just piling on references.) – Nick Alexeev Feb 03 '16 at 02:49
2

You could try with the singleton pattern that could handle everything and call the main activity when it's necessary.

So it's one static method to get an instance of MySingleton object, the same instance every time you call getInstance. You can "store" all bluetooth objects in it, it will not be destroyed and accessible from each activity.

public class MySingleton { 
    private static MySingleton instance; 
    public static MySingleton getInstance() { 
        if (null == instance) { 
            instance = new MySingleton(); 
        } 
    return instance; 
} 

    private MySingleton() { 
    } 
}
Stephane Mathis
  • 6,542
  • 6
  • 43
  • 69
  • It is my understanding that the Application class, which is a singleton provided by android for every app, has its own lifecycle. It too, in rare cases, can be destroyed without the whole app being destroyed. Do you mean a separate singleton class? Could you provide a code example of what you're talking about here? – firyice Mar 31 '13 at 16:32
  • I didn't really try it for Bluetooth connection, but a singleton class shouldn't be destroyed. Sorry for the formatting, I'm not a SO export, but it's just a regular singleton class in which you would have everything related to the bluetooth connection. ` public class Singleton { private static Singleton instance; publicstatic Singleton getInstance() { if (null == instance) { instance = new Singleton(); } return instance; } private Singleton() { } } ` – Stephane Mathis Mar 31 '13 at 20:49
  • Thanks for the clarification! Could you update your answer with the code. You can indent every line with 4 spaces to make it formatted like code. – firyice Mar 31 '13 at 21:52
1

There's a bunch of solutions to this. If you're not using Fragments then the simplest option is to override onRetainNonConfigurationInstance(). Never mind that the API is deprecated, that's only because they want you to use Fragments (where, to be fair, Fragment.setRetainInstance() makes this whole issue the no-brainer it should always have been). This API won't be going anywhere for a long time.

What I'd do is override onRetainNonConfiguration() to return 'this', i.e. a reference to the dead Activity instance, and then copy over the object refs you need from onCreate(), i.e. :

Object obj = getLastNonConfigurationInstance();
if (obj != null) {
    MyActivity deadActivity = (MyActivity)obj;
    this.foo = deadActivity.foo;
    this.bar = deadActivity.bar;
    ...
}

Alternatively you could use a Service, but I personally dislike them. Useful for doing cross-process stuff, no doubt, but otherwise they're a solution in search of a problem.

Lastly, as a general point of order you can safely stuff 'global' data into your Application context. To do this you subclass android.app.Application and use the <application android:name="MyApp"> attribute in your manifest. It's useful to also keep a ref to the Application object in global static data here, so AsyncTasks and other context-less code can always get to the Application context without having to pass Context parameters around for no reason.

class MyApp extends Application {
...

    public static MyApp context;
    ...

    @Override
    public void onCreate() {
        super.onCreate();

        context = this;

        ... set up global data here ...
    }
...
}
Reuben Scratton
  • 38,595
  • 9
  • 77
  • 86
  • Is it worth creating a single fragment for this activity just so I can use `setRetainInstance`? Will the "retained" fragment be able to rotate & resize itself on an orientation change? Also, [this SO question](http://stackoverflow.com/questions/4585627) taught me that a subclass of Application is NOT a safe way to store globals. In fact, it can lead to bugs that are nearly impossible to recreate. – firyice Mar 31 '13 at 16:44
  • Re. Fragment, yes. It's worth embracing the fragment way of doing things earlier rather than later. Re subclassing Application, someone's confused. Yes task state can be frozen by the OS while process is killed, but so what? Application.onCreate() still runs when the process/task restarts/resumes. – Reuben Scratton Mar 31 '13 at 23:15
  • So what? If the process restarts and Application.onCreate() is called, you've lost the data in those global variables. The only way that could be okay is for constants. – firyice Apr 01 '13 at 00:39