简体   繁体   English

如何通过配置更改在Android上管理蓝牙连接?

[英]How to manage a Bluetooth connection on Android through configuration changes?

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 . 首选使用ActionBarSherlock与设备版本2.2“Froyo”兼容的解决方案。

Problems... 问题...

  • Neither BluetoothDevice nor BluetoothSocket can be retained in onSaveState . BluetoothDeviceBluetoothSocket都不能保留在onSaveState

  • In order to keep my app responsive , the 12 second blocking call BluetoothSocket.connect() must be made on a separate thread. 为了保持我的应用程序响应 ,12秒阻止调用BluetoothSocket.connect()必须在单独的线程上进行。 Starting a Runnable is the recommended way to thread long tasks, but it's a nightmare trying to recover on a configuration change. 启动Runnable是线程长任务的推荐方法,但是尝试恢复配置更改是一场噩梦。 The official docs point to three different solutions. 官方文档指出了三种不同的解决方案。

    • Use getLastNonConfigurationInstance() , which is deprecated (seriously?!). 使用不推荐使用的getLastNonConfigurationInstance() (严重?!)。

    • Set android:configChanges="keyboardHidden|orientation" like the BluetoothChat Sample . BluetoothChat Sample一样设置android:configChanges="keyboardHidden|orientation" 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. 在这种情况下,这可能会浪费另外12秒。

Update 1 更新1

  • Further research led me to asyncTaskLoader , but it seems like this can only update the UI on completion, and cannot provide updates. 进一步的研究使我得到了asyncTaskLoader ,但似乎这只能在完成时更新UI,并且无法提供更新。

  • The BluetoothHDP sample uses a service. BluetoothHDP示例使用服务。 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 更新2

As pointed out by Reuben , Fragment.setRetainInstance(bool) has replaced the deprecated getLastNonConfigurationInstance() . 正如Reuben所指出的Fragment.setRetainInstance(bool)已经取代了不推荐使用的getLastNonConfigurationInstance() At this point, it seems like the best option is to make a persistent non-UI fragment using setRetainInstance(true) . 在这一点上,似乎最好的选择是使用setRetainInstance(true)创建一个持久的非UI片段。

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. 我会通过使用可以处理您的蓝牙连接的Service来解决此问题,并使该服务与您的活动对话,如本答案中所述。

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. 然后,您可以使用ASyncTask简单地显示/隐藏对话框,并在轮换时取消/重新启动ASyncTasks,这由您提到的Shelves Example完成。

Services are not really that horrible and might be the best tool for your problem. 服务并不是那么可怕,可能是解决问题的最佳工具。

Never place App Model (logic or data) in Visual/UI components. 切勿将App Model(逻辑或数据)放在Visual / UI组件中。 As you see, they come and go, and change. 如你所见,他们来去匆匆而且改变。

Places to keep non UI related stuff like data collections, live connections, threads etc: 保留非UI相关内容的地方,如数据集合,实时连接,线程等:

  • The Application class. Application类。 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. 此外,在Application类中使用静态线程Executor服务将有助于保留正在运行的任务。

  • 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. 这是App广泛运行的进程和数据的推荐位置。

  • A non visual Fragment with setRetainInstance(true) . 具有setRetainInstance(true)非可视片段。 Non visual here means that its a dummy fragment that's attached to Activity but doesn't shows any view. 这里的非可视化意味着它是一个虚拟片段,它附加到Activity但不显示任何视图。 It's just used as a retain-able object holder for an Activity. 它仅用作Activity的可保留对象持有者。 This is recommended for Activity wide processes and data. 建议用于活动范围的流程和数据。

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. 因此,获取MySingleton对象的实例是一种静态方法,每次调用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() { 
    } 
}

There's a bunch of solutions to this. 这有很多解决方案。 If you're not using Fragments then the simplest option is to override onRetainNonConfigurationInstance(). 如果您没有使用Fragments,那么最简单的选项是覆盖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). 不要紧,API已被弃用,这只是因为他们希望你使用Fragments(公平地说,Fragment.setRetainInstance()使整个问题成为应该总是这样的问题)。 This API won't be going anywhere for a long time. 这个API很长一段时间都不会去。

What I'd do is override onRetainNonConfiguration() to return 'this', ie a reference to the dead Activity instance, and then copy over the object refs you need from onCreate(), ie : 我要做的是覆盖onRetainNonConfiguration()以返回'this',即对死活动实例的引用,然后从onCreate()复制你需要的对象引用,即:

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. 为此,您将android.app.Application子类化,并在清单中使用<application android:name =“MyApp”>属性。 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. 在这里保留对全局静态数据中的Application对象的引用是有用的,因此AsyncTasks和其他无上下文代码总是可以到达Application上下文而不必无缘无故地传递Context参数。

class MyApp extends Application {
...

    public static MyApp context;
    ...

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

        context = this;

        ... set up global data here ...
    }
...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM