admin管理员组

文章数量:1336113

Description

I am using the AltBeacon library in my project to scan for iBeacon devices, but it only detects one device and does not update the `LiveData` object when there are multiple iBeacon devices nearby.

I have tested this behavior with the official Kotlin reference app (`BeaconReferenceApplication`) from the AltBeacon library, and the issue persists there as well. The ranging observer callback is supposed to fire every second, but it does not correctly identify or update the list of iBeacon devices when there are more than one.

Environment:

- Android Device Model: Google Pixel 9 Pro (Android 15), Samsung A50 (Android 11).

- Android Beacon Library Versions: 2.20 and 2.20.6 (same results).

Notes:

- My project uses an adapter to display the data, but this is the only difference from the official reference app.

- I have noticed dependencies in the library that might require specific versions. Where can I find the required versions of dependencies for the library?

Any insights on resolving this issue or ensuring the library detects and updates data for all nearby iBeacon devices would be appreciated!

1. BeaconReferenceApplication.kt

package .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 .altbeacon.beacon.*
import .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<Int> { state ->
        if (state == MonitorNotifier.OUTSIDE) {
            Log.d(TAG, "outside beacon region: "+region)
        }
        else {
            Log.d(TAG, "inside beacon region: "+region)
            sendNotification()
        }
    }

    val centralRangingObserver = Observer<Collection<Beacon>> { 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"
    }

}

2. ListDevices.kt

package .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 .altbeacon.beacon.Beacon
import .altbeacon.beacon.BeaconManager
import .altbeacon.beacon.MonitorNotifier
import .altbeacon.adapters.BleDevicesAdapter
import .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<Int> {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<Collection<Beacon>> {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()

    }
}

3. BleDevicesAdapter

package .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 .altbeacon.beaconreference.IBeacon
import .altbeacon.beaconreference.DataServices
import .altbeacon.beaconreference.R

class BleDevicesAdapter (private val context: Context, private val devices: MutableList<IBeacon> = mutableListOf(), private val onItemClick: (IBeacon) ->Unit) : RecyclerView.Adapter<BleDevicesAdapter.ViewHolder>() {
    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<IBeacon>){
        val previousSize = devices.size
        devices.clear()
        devices.addAll(newDevices)
        if (previousSize <devices.size) {
            notifyItemRangeInserted(previousSize, devices.size - previousSize)
        } else if (previousSize > devices.size) {
            notifyItemRangeRemoved(devices.size, previousSize - devices.size)
        } else {
//            notifyDataSetChanged()
        }
    }

}

NOTE: I am visually impaired and working on this alone, which makes it quite challenging for me. This is why I am reaching out for help.

Steps to reproduce:

1. Integrate the AltBeacon library into a project.

2. Start scanning for iBeacon devices using a foreground service.

3. Observe the `LiveData` updates or use the example app provided in the library with multiple iBeacon devices nearby.

Expected Behavior:

- The library should detect all nearby iBeacon devices and update the `LiveData` object with each detected device.

Actual Behavior:

- The library detects only one iBeacon device and does not update `LiveData` when additional devices are present.

本文标签: androidAltBeacon library not updating LiveData with multiple iBeacon devicesStack Overflow