Я использую библиотеку AltBeacon в своем проекте для сканирования устройств iBeacon, но она обнаруживает только одно устройство и не обновляет `LiveData `объект, когда поблизости находится несколько устройств iBeacon.
Я протестировал это поведение с помощью официального эталонного приложения Kotlin («BeaconReferenceApplication») из библиотеки AltBeacon, и там проблема также не устранена. Обратный вызов наблюдателя диапазона должен срабатывать каждую секунду, но он неправильно идентифицирует или обновляет список устройств iBeacon, если их несколько.
Среда :
- Модель устройства Android: Google Pixel 9 Pro (Android 15), Samsung A50 (Android 11).
- Версии библиотеки Android Beacon Library: 2.20 и 2.20.6 (те же результаты).
Примечания:
- Мой проект использует адаптер для отображения данных, но это единственное отличие от официального справочного приложения.
- Я заметил зависимости в библиотеке, которые могут потребовать конкретные версии. Где я могу найти необходимые версии зависимостей для библиотеки?
Будем благодарны за любые идеи по решению этой проблемы или обеспечению того, чтобы библиотека обнаруживала и обновляла данные для всех близлежащих устройств iBeacon!
1. BeaconReferenceApplication.kt
Код: Выделить всё
package org.altbeacon.beaconreference
import android.app.*
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.lifecycle.Observer
import org.altbeacon.beacon.*
import org.altbeacon.bluetooth.BluetoothMedic
class BeaconReferenceApplication: Application() {
// the region definition is a wildcard that matches all beacons regardless of identifiers.
// if you only want to detect beacons with a specific UUID, change the id1 paremeter to
// a UUID like Identifier.parse("2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6")
private val bleUUID = "A134D0B2-1DA2-1BA7-C94C-E8E00C9F7A2D" //"2D7A9F0C-E0E8-4CC9-A71B-A21DB2D034A1"
var region = Region("all-beacons", Identifier.parse(bleUUID), null, null)
override fun onCreate() {
super.onCreate()
val beaconManager = BeaconManager.getInstanceForApplication(this)
BeaconManager.setDebug(true)
// By default the AndroidBeaconLibrary will only find AltBeacons. If you wish to make it
// find a different type of beacon, you must specify the byte layout for that beacon's
// advertisement with a line like below. The example shows how to find a beacon with the
// same byte layout as AltBeacon but with a beaconTypeCode of 0xaabb. To find the proper
// layout expression for other beacon types, do a web search for "setBeaconLayout"
// including the quotes.
//
//beaconManager.getBeaconParsers().clear();
//beaconManager.getBeaconParsers().add(new BeaconParser().
// setBeaconLayout("m:0-1=4c00,i:2-24v,p:24-24"));
// By default the AndroidBeaconLibrary will only find AltBeacons. If you wish to make it
// find a different type of beacon like Eddystone or iBeacon, you must specify the byte layout
// for that beacon's advertisement with a line like below.
//
// If you don't care about AltBeacon, you can clear it from the defaults:
//beaconManager.getBeaconParsers().clear()
// Uncomment if you want to block the library from updating its distance model database
//BeaconManager.setDistanceModelUpdateUrl("")
// The example shows how to find iBeacon.
val parser = BeaconParser().
setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24")
parser.setHardwareAssistManufacturerCodes(arrayOf(0x004c).toIntArray())
beaconManager.getBeaconParsers().add(
parser)
// enabling debugging will send lots of verbose debug information from the library to Logcat
// this is useful for troubleshooting problmes
// BeaconManager.setDebug(true)
// The BluetoothMedic code here, if included, will watch for problems with the bluetooth
// stack and optionally:
// - power cycle bluetooth to recover on bluetooth problems
// - periodically do a proactive scan or transmission to verify the bluetooth stack is OK
// BluetoothMedic.getInstance().legacyEnablePowerCycleOnFailures(this) // Android 4-12 only
// BluetoothMedic.getInstance().enablePeriodicTests(this, BluetoothMedic.SCAN_TEST + BluetoothMedic.TRANSMIT_TEST)
//setupBeaconScanning()
}
fun setupBeaconScanning() {
val beaconManager = BeaconManager.getInstanceForApplication(this)
// By default, the library will scan in the background every 5 minutes on Android 4-7,
// which will be limited to scan jobs scheduled every ~15 minutes on Android 8+
// If you want more frequent scanning (requires a foreground service on Android 8+),
// configure that here.
// If you want to continuously range beacons in the background more often than every 15 mintues,
// you can use the library's built-in foreground service to unlock this behavior on Android
// 8+. the method below shows how you set that up.
try {
setupForegroundService()
}
catch (e: SecurityException) {
// On Android TIRAMUSU + this security exception will happen
// if location permission has not been granted when we start
// a foreground service. In this case, wait to set this up
// until after that permission is granted
Log.d(TAG, "Not setting up foreground service scanning until location permission granted by user")
return
}
//beaconManager.setEnableScheduledScanJobs(false);
//beaconManager.setBackgroundBetweenScanPeriod(0);
//beaconManager.setBackgroundScanPeriod(1100);
// Ranging callbacks will drop out if no beacons are detected
// Monitoring callbacks will be delayed by up to 25 minutes on region exit
// beaconManager.setIntentScanningStrategyEnabled(true)
// The code below will start "monitoring" for beacons matching the region definition at the top of this file
beaconManager.startMonitoring(region)
beaconManager.startRangingBeacons(region)
// These two lines set up a Live Data observer so this Activity can get beacon data from the Application class
val regionViewModel = BeaconManager.getInstanceForApplication(this).getRegionViewModel(region)
// observer will be called each time the monitored regionState changes (inside vs. outside region)
regionViewModel.regionState.observeForever( centralMonitoringObserver)
// observer will be called each time a new list of beacons is ranged (typically ~1 second in the foreground)
regionViewModel.rangedBeacons.observeForever( centralRangingObserver)
}
fun setupForegroundService() {
val builder = Notification.Builder(this, "BeaconReferenceApp")
builder.setSmallIcon(R.drawable.ic_launcher_background)
builder.setContentTitle("Scanning for Beacons")
val intent = Intent(this, ListDevices::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE
)
builder.setContentIntent(pendingIntent);
val channel = NotificationChannel("beacon-ref-notification-id",
"My Notification Name", NotificationManager.IMPORTANCE_DEFAULT)
channel.setDescription("My Notification Channel Description")
val notificationManager = getSystemService(
Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel);
builder.setChannelId(channel.getId());
Log.d(TAG, "Calling enableForegroundServiceScanning")
BeaconManager.getInstanceForApplication(this).enableForegroundServiceScanning(builder.build(), 456);
Log.d(TAG, "Back from enableForegroundServiceScanning")
}
val centralMonitoringObserver = Observer { state ->
if (state == MonitorNotifier.OUTSIDE) {
Log.d(TAG, "outside beacon region: "+region)
}
else {
Log.d(TAG, "inside beacon region: "+region)
sendNotification()
}
}
val centralRangingObserver = Observer { beacons ->
val rangeAgeMillis = System.currentTimeMillis() - (beacons.firstOrNull()?.lastCycleDetectionTimestamp ?: 0)
if (rangeAgeMillis < 10000) {
Log.d("BeaconReferenceApplication", "Ranged: ${beacons.count()} beacons")
for (beacon: Beacon in beacons) {
Log.d(TAG, "$beacon about ${beacon.distance} meters away")
}
}
else {
Log.d("BeaconReferenceApplication", "Ignoring stale ranged beacons from $rangeAgeMillis millis ago")
}
}
private fun sendNotification() {
val builder = NotificationCompat.Builder(this, "beacon-ref-notification-id")
.setContentTitle("Beacon Reference Application")
.setContentText("A beacon is nearby.")
.setSmallIcon(R.drawable.ic_launcher_background)
val stackBuilder = TaskStackBuilder.create(this)
stackBuilder.addNextIntent(Intent(this, ListDevices::class.java))
val resultPendingIntent = stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT + PendingIntent.FLAG_IMMUTABLE
)
builder.setContentIntent(resultPendingIntent)
val channel = NotificationChannel("beacon-ref-notification-id",
"My Notification Name", NotificationManager.IMPORTANCE_DEFAULT)
channel.setDescription("My Notification Channel Description")
val notificationManager = getSystemService(
Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel);
builder.setChannelId(channel.getId());
notificationManager.notify(1, builder.build())
}
companion object {
val TAG = "BeaconReference"
}
}
Код: Выделить всё
package org.altbeacon.beaconreference
import android.annotation.SuppressLint
import androidx.appcompat.app.AppCompatActivity
import androidx.activity.OnBackPressedCallback
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.Observer
import org.altbeacon.beacon.Beacon
import org.altbeacon.beacon.BeaconManager
import org.altbeacon.beacon.MonitorNotifier
import org.altbeacon.adapters.BleDevicesAdapter
import org.altbeacon.beacon.permissions.BeaconScanPermissionsActivity
class ListDevices : AppCompatActivity() {
//private lateinit var bluetoothServices: BluetoothServices
private lateinit var beaconReferenceApplication: BeaconReferenceApplication
private lateinit var recyclerView: RecyclerView
private lateinit var bleDevicesAdapter: BleDevicesAdapter
//private lateinit var iBeaconsView: IBeaconsView
@SuppressLint("CheckResult")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list_devices)
//bluetoothServices = BluetoothServices(this)
recyclerView = findViewById(R.id.resultScannerDevices)
recyclerView.layoutManager = LinearLayoutManager (this)
bleDevicesAdapter = BleDevicesAdapter (this) { device ->
onDeviceClick(device)
}
recyclerView.adapter = bleDevicesAdapter
//iBeaconsView = ViewModelProvider(this)[IBeaconsView::class.java]
//iBeaconsView.beacons.observe(this) {deviceList ->
// bleDevicesAdapter.updateDevices(deviceList)
// }
beaconReferenceApplication = application as BeaconReferenceApplication
//I set up a Live Data observer for the signaling data
val regionViewModel = BeaconManager.getInstanceForApplication(this).getRegionViewModel(beaconReferenceApplication.region)
regionViewModel.regionState.observe(this, monitoringObserver)
regionViewModel.rangedBeacons.observe(this, rangingObserver)
//check if all permissions are accepted
if (!BeaconScanPermissionsActivity.allPermissionsGranted(this, true)){
// permissions are not supported and prompt the user
val intent = Intent(this, BeaconScanPermissionsActivity::class.java)
intent.putExtra("backgroundAccessRequested", true)
startActivity(intent)
} else {
//permissions are accepted and start foreground service and scan
if (BeaconManager.getInstanceForApplication(this).monitoredRegions.size == 0){
(application as BeaconReferenceApplication).setupBeaconScanning()
val beaconManager = BeaconManager.getInstanceForApplication(this)
beaconManager.startMonitoring(beaconReferenceApplication.region)
beaconManager.startRangingBeacons(beaconReferenceApplication.region)
}
if (BeaconManager.getInstanceForApplication(this).rangedRegions.size == 0){
val beaconManager = BeaconManager.getInstanceForApplication(this)
beaconManager.startRangingBeacons(beaconReferenceApplication.region)
beaconManager.startMonitoring(beaconReferenceApplication.region)
}
}
val stopScaning: Button = findViewById(R.id.stopScaning)
stopScaning.setOnClickListener {
val beaconManager = BeaconManager.getInstanceForApplication(this)
beaconManager.stopRangingBeacons(beaconReferenceApplication.region)
beaconManager.stopMonitoring(beaconReferenceApplication.region)
startActivity(Intent(this, MainActivity::class.java))
finishAffinity()
}
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val beaconManager = BeaconManager.getInstanceForApplication(this@ListDevices)
beaconManager.stopRangingBeacons(beaconReferenceApplication.region)
beaconManager.stopMonitoring(beaconReferenceApplication.region)
finish()
}
})
}
private val monitoringObserver = Observer {state ->
if (state == MonitorNotifier.OUTSIDE){
Log.d("RESULT_SCAN", "nu este nimic în jur")
} else {
Log.d("RESULT_SCAN", "ceva este înapropriere")
}
}
private val rangingObserver = Observer {beacons ->
if (BeaconManager.getInstanceForApplication(this).rangedRegions.size > 0){
//beacons.sortedBy { it.distance }
beacons .forEach {
Log.d("RESULT_SCAN", "Nume ${it.bluetoothName} mac adresa ${it.bluetoothAddress}")
//val iBeaconDevice
val iBeacon = IBeacon(it.bluetoothAddress)
iBeacon.uuid = it.id1.toString()
iBeacon.major = it.id2.toInt()
iBeacon.minor = it.id3.toInt()
iBeacon.rssi = it.rssi
bleDevicesAdapter.updateDevices(listOf(iBeacon))
}
}
}
private fun onDeviceClick(device: IBeacon) {
val beaconManager = BeaconManager.getInstanceForApplication(this)
beaconManager.stopRangingBeacons(beaconReferenceApplication.region)
beaconManager.stopMonitoring(beaconReferenceApplication.region)
val intentConnecting = Intent(applicationContext, CommunicationWithTheDevice::class.java)
val selectedDevice = device.macAddress
intentConnecting.putExtra("connectingTo", selectedDevice)
startActivity(intentConnecting)
finishAffinity()
}
}
Код: Выделить всё
package org.altbeacon.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import android.widget.Button
import org.altbeacon.beaconreference.IBeacon
import org.altbeacon.beaconreference.DataServices
import org.altbeacon.beaconreference.R
class BleDevicesAdapter (private val context: Context, private val devices: MutableList = mutableListOf(), private val onItemClick: (IBeacon) ->Unit) : RecyclerView.Adapter() {
private val dataServices = DataServices()
inner class ViewHolder (itemView: View) : RecyclerView.ViewHolder (itemView) {
private val resultScannerDevices: Button = itemView.findViewById(R.id.resultScannerDevicesButton)
//private val resultScannerDevices: View = itemView.findViewById(R.id.resultScannerDevices)
fun bind(resultBleDevice: IBeacon) {
val categoryTextWidgets = dataServices.getNameByMajor(context, resultBleDevice.major.toString())
val numberTextWidgets = dataServices.getNumberByMinor(context, resultBleDevice.major.toString(), resultBleDevice.minor.toString())
val informationWidgets = dataServices.getInformationByNumber(context, resultBleDevice.major.toString(), resultBleDevice.minor.toString())
resultScannerDevices.text = context.getString(R.string.DeviceWidgetName, categoryTextWidgets, numberTextWidgets, informationWidgets)
resultScannerDevices.setOnClickListener{
onItemClick(resultBleDevice)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_device, parent, false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val device = devices[position]
holder.bind(device)
}
override fun getItemCount() = devices.size
fun updateDevices (newDevices: List){
val previousSize = devices.size
devices.clear()
devices.addAll(newDevices)
if (previousSize devices.size) {
notifyItemRangeRemoved(devices.size, previousSize - devices.size)
} else {
// notifyDataSetChanged()
}
}
}
#### Шаги по воспроизведению:
1. Интегрируйте библиотеку AltBeacon в проект.
2. Запустите сканирование устройств iBeacon с помощью службы переднего плана.
3. Наблюдайте за обновлениями LiveData или используйте пример приложения, представленный в библиотеке, с несколькими устройствами iBeacon поблизости.
#### Ожидаемое поведение:
- Библиотека должна обнаруживать все близлежащие устройства iBeacon и обновлять объект LiveData для каждого обнаруженного устройства.
#### Фактическое поведение:
- Библиотека обнаруживает только один iBeacon и не обновляет LiveData при наличии дополнительных устройств.
Подробнее здесь: https://stackoverflow.com/questions/792 ... on-devices