为什么我的RxJava设置阻止我的UI线程? 使用BluetoothAdapter.startLeScan回调

我正在努力寻找阻止我的UI线程的具体行动,我已经尝试了几个调度运算符,但我不知道如何使其工作。

我有一个用户界面的按钮,这onClicked是开始蓝牙扫描和更新textView字符串像一个日志(它显示了在这一刻发生的事情)。

所以这是我的MainActivity:

lateinit var disposable: Disposable val textDataService = TextDataService() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_scan_test) buttonScanTestStart.setOnClickListener { if (isBluetoothEnabled()) { textViewLog.text = "" buttonScanTestStop.visibility = View.VISIBLE buttonExportScanTestRaportSummary.visibility = View.GONE buttonExportScanTestRaportFull.visibility = View.GONE buttonScanTestStart.visibility = View.GONE disposable= Scanner() .discoverSingleDevice(this, " ", textViewLog) .doOnError { setText("General error: ${it.message ?: it::class.java}", textViewLog) setLogText("General error: ${it.message ?: it::class.java}") } .repeat(1) .doOnComplete { buttonScanTestStop.visibility = View.GONE } .doOnDispose { log("TEST DISPOSED") } .subscribe() } else { Toast.makeText(this, "Please, enable bluetooth to start test.", Toast.LENGTH_SHORT).show() } } buttonScanTestStop.setOnClickListener { disposable.dispose() buttonScanTestStop.visibility = View.GONE buttonScanTestStart.visibility = View.VISIBLE buttonExportScanTestRaportSummary.visibility = View.VISIBLE buttonExportScanTestRaportFull.visibility = View.VISIBLE textDataService.generateScanGeneralStatisticsLogText(textViewLog) } 

这是扫描仪类:

 class Scanner { private fun scan(context: Context, textView: TextView) = Observable.create { emitter -> val bluetoothAdapter = context.getBluetoothAdapter() if (bluetoothAdapter.isEnabled) { val scanCallback = BluetoothAdapter.LeScanCallback { bluetoothDevice, rssi, _ -> if(bluetoothDevice.name == null){ bluetoothRawLog("Scanned Item -> ${bluetoothDevice.logText()} | rssi = $rssi | time: = ${getCurrentTime()}") scannedOtherDevices.add(bluetoothDevice.address) } else{ if(!scannedDevices.contains(bluetoothDevice.name)){ setText("Scanned Item -> ${bluetoothDevice.logText()} | rssi = $rssi | time: = ${getCurrentTime()}\n", textView) setLogText("Scanned Item -> ${bluetoothDevice.logText()} | rssi = $rssi | time: = ${getCurrentTime()}\n") } scannedDevices.add(bluetoothDevice.name) } bluetoothRawLog("Scanned Item -> ${bluetoothDevice.logText()} | rssi = $rssi") bluetoothDevice.name?.let { emitter.onNext(ScannedItem(it, bluetoothDevice.address)) } } emitter.setCancellable { bluetoothAdapter.stopLeScan(scanCallback) } bluetoothAdapter.startLeScan(scanCallback) } else emitter.onError(IllegalStateException("Bluetooth turned off")) } .doOnNext { log("Scanned -> $it") } .timeout(12, TimeUnit.SECONDS) .observeOn(AndroidSchedulers.mainThread()) .doOnError { if (it is TimeoutException) setLogText("Scanner -> no named devices for 12 seconds: resetting...") } .retry { t -> t is TimeoutException } fun discoverSingleDevice(context: Context, searchName: String, textView: TextView): Observable = scan(context, textView) .filter { it.name.contains(searchName) } .take(1) 

这里是我的Kotlin扩展function,我正在使用设置文本:

 fun setLogText(text: String) { logBuffer.append(text) } fun setText(text: String, textView: TextView) { textView.append(text) } 

这里也是我的布局:

  

  

所以文本出现在我的textView相当流畅,一切都OK。 但是,当有更多的可用设备扫描时,我不能通过单击按钮停止测试。 当我尝试点击它时,它只是被冻结和响应太晚了。

discoverSingleDevice函数将永远运行,因为我筛选从我的editText(我用硬编码与“”)的搜索名称,使其运行永久,我只想通过单击我的布局中的停止按钮来停止。 作为我传递给它的第二个参数textViewLog从我的布局(Kotlin是足够聪明,通过IDfind它)

那么如何防止用这个设置阻止UI呢?

更新:

正如文超建议的,我做了这样的事情:

  val observable = Scanner() .discoverSingleDevice(this, " ", textViewLog) .doOnError { nexoSetText("General error: ${it.message ?: it::class.java}", textViewLog) nexoSetLogText("General error: ${it.message ?: it::class.java}") } .repeat(1) .doOnComplete { buttonScanTestStop.visibility = View.GONE } .doOnDispose { nexoLog("TEST DISPOSED") } disposable = observable .subscribeOn(Schedulers.newThread()).subscribe() 

然而,这没有帮助,并不是那么简单。 再次,我已经尝试了几个与其他运营商的事情。 请具体说明我的情况 – 我提供了代码。

这就像扫描2分钟后应用程序相当冷静。 在当然出现的日志/编舞:跳过了54帧! 应用程序可能在其主线程上做了太多的工作。

更新2:

正如PhoenixWang建议的那样,我已经记录下来检查BluetoothAdapter运行的线程。 所以我在Scanner类的扫描方法中这样做了:

  private fun scan(context: Context, textView: TextView) = Observable.create { emitter -> val bluetoothAdapter = context.getBluetoothAdapter() if (bluetoothAdapter.isEnabled) { val scanCallback = BluetoothAdapter.LeScanCallback { bluetoothDevice, rssi, _ -> Log.v("BluetoothThread", "Running on: " + Thread.currentThread().name) ///the rest of the code 

那么如何解决这个问题? 我以为它会实际上运行在单独的线程(不主要),因为我已经做了天真的修复,这是我的第一个更新中可见。 你能帮我吗?

更新3:

所以问题是与BluetoothAdapter.leScan – 它在主线程上运行,即使我做这样的事情:

 Observable.fromCallable{ bluetoothAdapter.startLeScan(scanCallback)}.subscribeOn(Schedulers.newThread()).subscribe() 

正如PhoenixWang建议的那样,BluetoothAdapter.LeScanCallback因为执行主线程而运行。 Observable / RxJava不能改变它。 那么如何解决我的问题?

RxJava默认是同步的。 在你的情况下,它运行在Android 主线程的调用者线程上

你必须指定你想要订阅的线程。

 Observable.fromCallable(whatever()) .subscribeOn(Schedulers.newThread()).subscribe() 

在这里阅读更多。

  .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) 

像上面一样,添加.subscribeOn(Schedulers.io())和.observeOn(AndroidSchedulers.mainThread())。 尝试这个。 应该管用。

要更清楚。 我做了一个答案,但不能解决你的问题。 你的代码:

  Observable.fromCallable{ bluetoothAdapter.startLeScan(scanCallback)}.subscribeOn(Sche‌​dulers.newThread()).‌​subscribe(). 

意味着您的bluetoothAdapter.startLeScan(scanCallback)在newThread中运行。 但不是如何bluetoothAdapter回电给你。

所以在执行你的startLeScan。 它可以启动一个新的线程或运行在其他线程取决于它的实现。

与你的Observable.create(/ /你的东西)相同的想法

它们保证在指定的调度程序上运行。 但是它也可以在其中启动一个新的线程。 这就是为什么我在评论中提到检查你的BluetoothAdapter的实现。

更新

更清楚的是,BluetoothAdapter的一个示例实现可以阻止你的UI线程。

 class BluetoothAdapter { fun startLeScan(callback: LeScanCallback) { val handler = Handler(Looper.getMainLooper()) handler.post { // here you back to your UI Thread. callback.onSuccess(1) } } interface LeScanCallback { fun onSuccess(result: Int) } } 

所以如果你的BluetoothAdapter的实现将线程更改回UI线程。 无论Observable的线程是什么,你最终都会阻塞你的UI线程。 我的意思是检查你的BluetoothAdapter的实现

    Interesting Posts