Intereting Posts
Как проверить состояние, как i <J in for loop kotlin Обработка Enter Key на EditText (Kotlin, Android) В Котлине, как сделать свойство доступным только для определенного типа версия kotlin, которая используется для построения с градиентом (1.1.2-5), отличается от той, что включена в плагин IDE (1.1.2-4) kotlin, используя тип генериков класса данных Как уменьшить размер изображения перед загрузкой, чтобы ускорить приложение? Как делегировать реализацию собственности в Котлин? Почему я не могу получить класс общего параметра? Матч скобки kotlin способ Удаление из списка вызывает IndexOutOfBoundsException при повторении: Java-> Kotlin Котлин – Проблемы с while () Kotlin – Продолжить Coroutine после исключения Использование метода получения Anko Ошибка несоответствия AnkoContext <ViewGroup> Найдено AnkoContext <Context> статический эквивалент в Android для kotlin, чтобы избежать утечек памяти обработчика Использование FirebaseListAdapter в Котлине

Почему моя установка RxJava блокирует мой поток пользовательского интерфейса? Работа с обратным вызовом BluetoothAdapter.startLeScan

Я пытаюсь найти конкретное действие, которое блокирует мой поток пользовательского интерфейса, я пробовал несколько операторов планировщика, но я не уверен, как заставить его работать.

У меня есть пользовательский интерфейс с кнопкой, который onClicked запускает сканирование bluetooth и обновляет 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) } 

Вот класс Scanner:

 class Scanner { private fun scan(context: Context, textView: TextView) = Observable.create<ScannedItem> { 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<ScannedItem> = scan(context, textView) .filter { it.name.contains(searchName) } .take(1) 

И вот мои функции расширения Kotlin, которые я использую для установки Text:

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

Вот мой макет:

 <?xml version="1.0" encoding="utf-8"?> 

 <LinearLayout android:layout_margin="16dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="horizontal"> <Button android:layout_weight="1" android:id="@+id/buttonScanTestStart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Start Test" /> <Button android:layout_weight="1" android:visibility="gone" android:id="@+id/buttonScanTestStop" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Stop Test" /> <Button android:layout_weight="1" android:id="@+id/buttonExportScanTestRaportFull" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Export raport" android:visibility="gone" /> <Button android:layout_weight="1" android:id="@+id/buttonExportScanTestRaportSummary" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="Export summary" android:visibility="gone" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:visibility="gone" android:id="@+id/scanTestStartedLinearLayout" android:gravity="center" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:text="Test running..." android:layout_width="wrap_content" android:layout_height="wrap_content" /> <ProgressBar android:id="@+id/scanTestProgressBar" android:layout_width="16dp" android:layout_height="16dp" /> </LinearLayout> <TextView android:id="@+id/textViewScanTestLog" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp" android:gravity="center" /> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="16dp" android:gravity="center"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/textViewLog" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> </ScrollView> </LinearLayout> 

Таким образом, текст появляется в моем текстовом экране довольно гладко, все в порядке. Однако, когда есть больше доступных устройств для сканирования, я не могу остановить тест, нажав кнопку. Он просто зависает и реагирует как слишком поздно, когда я пытаюсь щелкнуть его.

Функция discoverSingleDevice будет выполняться вечно, потому что я фильтрую, чтобы иметь имя поиска из моего editText (который я жестко закодировал с помощью «»), чтобы он выполнялся навсегда, и я хочу только остановить это, нажав кнопку остановки в моем макете. В качестве второго параметра я передал ему textViewLog из моего макета (Kotlin достаточно умен, чтобы найти его по id)

Итак, как предотвратить блокировку пользовательского интерфейса с помощью этой настройки?

ОБНОВИТЬ:

Как предположил WenChao, я сделал что-то вроде этого:

  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 минуты сканирования приложение довольно замерзает. В бревнах, конечно, появляется I / хореограф: пропущено 54 кадра! Приложение может делать слишком много работы над своей основной нитью.

ОБНОВЛЕНИЕ 2:

Как предположил PhoenixWang, я зарегистрировался, чтобы проверить, в каком потоке работает BluetoothAdapter. Поэтому я сделал это так, как в методе сканирования в классе Scanner:

  private fun scan(context: Context, textView: TextView) = Observable.create<ScannedItem> { 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 запускается в mainthread из-за его реализации. 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, который может блокировать ваш поток пользовательского интерфейса.

 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 изменит поток обратно на поток пользовательского интерфейса. В конечном итоге вы заблокируете поток пользовательского интерфейса независимо от того, какой поток вашего наблюдаемого. Что я имею в виду Проверка реализации вашего BluetoothAdapter