请求Android BLE广播数据包示例代码及定向广播实现的带详解示例
Hey there! Let's clear up how to send targeted BLE broadcasts on Android, including the packet structure logic and a fully commented example code. First, a quick key point to wrap your head around:
BLE broadcasts are always sent to all nearby devices by design. "Targeted" broadcasting works by adding unique identifiers to your broadcast packet, so only your intended receiver will recognize and process it.
1. Core Logic: How "Targeted" BLE Broadcasts Work
Instead of sending directly to a specific device (BLE doesn't support native point-to-point broadcasts), we use a two-step approach:
- Embed a unique marker in our broadcast packet (like a custom UUID or vendor-specific data)
- Configure the target receiver to scan only for packets containing this marker
This way, even though every nearby device gets the broadcast, only your target app will act on it—giving the effect of "sending to a specific receiver."
2. BLE Broadcast Packet Structure Basics
Each BLE broadcast packet is made of multiple AD Structures (Advertising Data Structures). Each structure has 3 components:
- Length: 1 byte (total length of the Type + Data fields)
- Type: 1 byte (defines what the data represents, e.g., UUID, vendor-specific data)
- Data: Variable length (the actual content we want to broadcast)
Common Type values we'll use in our example:
0x01: Flags (e.g., indicates if the device is connectable)0x07: Complete 128-bit Service UUID (our unique identification marker)0xFF: Manufacturer Specific Data (custom data we can define for additional targeting)
3. Full Example Code with Detailed Explanations
Step 1: Add Permissions to AndroidManifest.xml
For Android 12+ (API 31+), you need specific runtime permissions. Older versions require location access for BLE scanning:
<!-- Legacy Bluetooth permissions (pre-Android 12) --> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" /> <!-- Android 12+ BLE permissions --> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <!-- Location permission for pre-Android 12 devices --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" /> <!-- Declare BLE hardware support --> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
Step 2: Main Activity Code
This code handles permission checks, Bluetooth initialization, and starting/stopping the targeted broadcast:
import android.Manifest import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothManager import android.bluetooth.le.AdvertiseCallback import android.bluetooth.le.AdvertiseData import android.bluetooth.le.AdvertiseSettings import android.bluetooth.le.BluetoothLeAdvertiser import android.content.Context import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.widget.Button import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import android.os.ParcelUuid class BLEAdvertiserActivity : AppCompatActivity() { // Generate your own unique 128-bit UUID (use an online UUID generator!) private val CUSTOM_SERVICE_UUID = java.util.UUID.fromString("12345678-1234-5678-1234-567812345678") // Vendor ID (use your company's assigned ID, or 0x0000 for testing) private val VENDOR_ID = 0x0000 // Custom target marker (receiver will check for this byte array) private val TARGET_MARKER = byteArrayOf(0xAB, 0xCD, 0xEF) // 3-byte unique identifier private var bluetoothLeAdvertiser: BluetoothLeAdvertiser? = null private var isAdvertising = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val startBtn = findViewById<Button>(R.id.start_advertise_btn) val stopBtn = findViewById<Button>(R.id.stop_advertise_btn) startBtn.setOnClickListener { if (checkPermissions()) { initBluetooth() startAdvertising() } else { requestPermissions() } } stopBtn.setOnClickListener { stopAdvertising() } } // Verify all required permissions are granted private fun checkPermissions(): Boolean { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADVERTISE) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED } else { ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED } } // Request missing permissions from the user private fun requestPermissions() { val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { arrayOf( Manifest.permission.BLUETOOTH_ADVERTISE, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT ) } else { arrayOf( Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.BLUETOOTH, Manifest.permission.ACCESS_FINE_LOCATION ) } ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE) } // Initialize Bluetooth adapter and advertiser private fun initBluetooth() { val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager val bluetoothAdapter = bluetoothManager.adapter // Check if Bluetooth is enabled and supported if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled) { Toast.makeText(this, "Please enable Bluetooth first", Toast.LENGTH_SHORT).show() return } // Get the BLE advertiser instance bluetoothLeAdvertiser = bluetoothAdapter.bluetoothLeAdvertiser } // Start broadcasting our targeted BLE packet private fun startAdvertising() { if (bluetoothLeAdvertiser == null || isAdvertising) return // 1. Configure advertising settings (power, mode, timeout) val advertiseSettings = AdvertiseSettings.Builder() .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) // Battery-friendly for background use .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) // Balanced range and power .setConnectable(false) // We don't need to accept connections (broadcast-only) .setTimeout(0) // Broadcast indefinitely (0 = no timeout) .build() // 2. Build the broadcast packet with our targeting markers val advertiseData = AdvertiseData.Builder() // Add flags: indicate we're a BLE-only, non-connectable device .addFlags(AdvertiseData.FLAG_BREDR_NOT_SUPPORTED) // Add our custom service UUID (receiver will scan for this) .addServiceUuid(ParcelUuid(CUSTOM_SERVICE_UUID)) // Add vendor-specific data with our target marker .addManufacturerData(VENDOR_ID, TARGET_MARKER) .build() // 3. Start advertising with status callbacks bluetoothLeAdvertiser?.startAdvertising(advertiseSettings, advertiseData, advertiseCallback) } // Stop the BLE broadcast private fun stopAdvertising() { if (bluetoothLeAdvertiser != null && isAdvertising) { bluetoothLeAdvertiser?.stopAdvertising(advertiseCallback) isAdvertising = false Toast.makeText(this, "Stopped targeted broadcast", Toast.LENGTH_SHORT).show() } } // Callback to handle advertising success/failure private val advertiseCallback = object : AdvertiseCallback() { override fun onStartSuccess(settingsInEffect: AdvertiseSettings) { super.onStartSuccess(settingsInEffect) isAdvertising = true Toast.makeText(this@BLEAdvertiserActivity, "Started targeted BLE broadcast", Toast.LENGTH_SHORT).show() } override fun onStartFailure(errorCode: Int) { super.onStartFailure(errorCode) isAdvertising = false val errorMsg = when(errorCode) { ADVERTISE_FAILED_ALREADY_STARTED -> "Advertising already running" ADVERTISE_FAILED_DATA_TOO_LARGE -> "Broadcast data exceeds size limit" ADVERTISE_FAILED_FEATURE_UNSUPPORTED -> "BLE advertising not supported on this device" ADVERTISE_FAILED_INTERNAL_ERROR -> "Internal system error" ADVERTISE_FAILED_TOO_MANY_ADVERTISERS -> "Too many BLE advertisers nearby" else -> "Unknown error: $errorCode" } Toast.makeText(this@BLEAdvertiserActivity, "Advertising failed: $errorMsg", Toast.LENGTH_LONG).show() } } override fun onDestroy() { super.onDestroy() stopAdvertising() // Clean up when activity is closed } companion object { private const val PERMISSION_REQUEST_CODE = 1001 } }
4. Key Explanations for the Code
A. Targeting Mechanisms
- Custom Service UUID: This is your app's unique fingerprint. The target receiver will set up a scan filter to only pick up broadcasts with this UUID, ignoring all others.
- Vendor-Specific Data: The
TARGET_MARKERbyte array lets you add extra targeting logic—for example, sending a device ID or message that the receiver can parse to confirm it's the intended recipient.
B. How the Receiver Picks Up the Broadcast
On the receiver side, you'd set up a BLE scanner with a filter for your custom UUID. Here's a quick snippet:
val scanFilter = ScanFilter.Builder() .setServiceUuid(ParcelUuid(CUSTOM_SERVICE_UUID)) .build()
Or, if using vendor data, you'd parse the scan result's manufacturer data to check for your TARGET_MARKER.
C. Important Notes
- Connectable Mode: We set
setConnectable(false)because we're only broadcasting data. If you need to establish a connection later, set this totrue. - Power Modes: Use
ADVERTISE_MODE_LOW_POWERfor background broadcasting to save battery. UseADVERTISE_MODE_HIGH_POWERif you need faster detection. - Device Support: Not all Android devices support BLE advertising. The
onStartFailurecallback will notify you if it's unsupported.
内容的提问来源于stack exchange,提问作者user9738849

