Android 中的 BLE 广告

Posted

技术标签:

【中文标题】Android 中的 BLE 广告【英文标题】:BLE Advertisement in Android 【发布时间】:2016-04-20 17:37:52 【问题描述】:

我正在开发和应用程序以在 android 中发送 BLE 广告数据包。我已经使用 AdvertiseData 和 AdverstiseSettings 类来生成广告数据包。但是当我执行 StartAdvertising 时,它总是给我一个错误代码“2”,“ADVERTISE_FAILED_TOO_MANY_ADVERTISERS”,“无法开始广告,因为没有可用的广告实例。”

下面是我的 MainActivity.JAVA 代码

package rockwellcollins.blutooth_advertise;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.os.Bundle;
import android.os.ParcelUuid;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import java.util.List;
import java.util.UUID;

public class MainActivity extends AppCompatActivity 
    private BluetoothLeScanner mBluetoothLeScanner;
    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() 
            @Override
            public void onClick(View view) 
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            
        );

        textView = (TextView) findViewById(R.id.txtv);
        mBluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();

        if( !BluetoothAdapter.getDefaultAdapter().isMultipleAdvertisementSupported() ) 
            Toast.makeText(this, "Multiple advertisement not supported", Toast.LENGTH_SHORT).show();
        
        advertise();
        BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner().startScan(scanCallback);
    

    private void advertise() 
        BluetoothLeAdvertiser advertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();

        AdvertiseSettings settings = new AdvertiseSettings.Builder()
                .setAdvertiseMode( AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY )
                .setTxPowerLevel( AdvertiseSettings.ADVERTISE_TX_POWER_HIGH )
                .setConnectable(false)
                .build();
        Log.i("BLE","start of advertise data after settings");
        ParcelUuid pUuid = new ParcelUuid( UUID.fromString("b161c53c-0715-11e6-b512-3e1d05defe78"));

        AdvertiseData data = new AdvertiseData.Builder()
                .setIncludeDeviceName( true )
                .setIncludeTxPowerLevel(true)
                .addServiceUuid( pUuid )
                //.addServiceData( pUuid, "Data".getBytes(Charset.forName("UTF-8") ) )
                .build();
        Log.i("BLE","before callback");
        AdvertiseCallback advertisingCallback = new AdvertiseCallback() 
            @Override
            public void onStartSuccess(AdvertiseSettings settingsInEffect) 
                super.onStartSuccess(settingsInEffect);
                Log.i("BLE", "LE Advertise success.");

            

            @Override
            public void onStartFailure(int errorCode) 
                Log.e("BLE", "Advertising onStartFailure: " + errorCode);
                super.onStartFailure(errorCode);
            
        ;

        advertiser.startAdvertising( settings, data, advertisingCallback );
        Log.i("BLE", "start advertising");
    

    private final ScanCallback scanCallback = new ScanCallback() 
        @Override
        public void onScanResult(int callbackType, ScanResult result) 
            printScanResult(result);
        

        @Override
        public void onBatchScanResults(List<ScanResult> results) 
            textView.append("Received " + results.size() + " batch results:\n");
            for (ScanResult r : results) 
                printScanResult(r);
            
        

        @Override
        public void onScanFailed(int errorCode) 
            switch (errorCode) 
                case ScanCallback.SCAN_FAILED_ALREADY_STARTED:
                    textView.append("Scan failed: already started.\n");
                    break;
                case ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
                    textView.append("Scan failed: app registration failed.\n");
                    break;
                case ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED:
                    textView.append("Scan failed: feature unsupported.\n");
                    break;
                case ScanCallback.SCAN_FAILED_INTERNAL_ERROR:
                    textView.append("Scan failed: internal error.\n");
                    break;
            
        

        private void printScanResult(ScanResult result) 
            String id = result.getDevice() != null ? result.getDevice().getAddress() : "unknown";
            int tx = result.getScanRecord() != null ? result.getScanRecord().getTxPowerLevel() : 0;
            textView.append("TX: " + tx + " RX: " + result.getRssi() + " from " + id+ ".\n");
        
    ;

    @Override
    public boolean onCreateOptionsMenu(Menu menu) 
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    

    @Override
    public boolean onOptionsItemSelected(MenuItem item) 
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) 
            return true;
        

        return super.onOptionsItemSelected(item);
    

Android Manifest.xml 的代码

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="rockwellcollins.blutooth_advertise">
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

能否请您告诉我我做错了什么以及如何解决此错误?

谢谢

【问题讨论】:

【参考方案1】:

根据我的经验,关于 BLE 广告的 Android 设备有 4 种类型:

    Android pre-5.0 的设备 - 不支持 LE 广告 Android 5+ 的设备不支持 LE 广告并从 getBluetoothLeAdvertiser() 返回 null。这些设备从isMultipleAdvertisementSupported() 返回false。他们甚至在蓝牙开启的情况下也会这样做(请参阅下面的注释)。 Android 5+ 设备返回 BluetoothLeAdvertiser 对象,但每次广告尝试都以 ADVERTISE_FAILED_TOO_MANY_ADVERTISERS 错误结束(您就是这种情况)。这些设备从isMultipleAdvertisementSupported() 返回true,如您所见,这是不正确的。到目前为止,我只看到过一款属于此类别的手机:Sony xperia z1 compact,但如果有的话,还有更多。 支持 LE 广告的 Android 5+ 设备。那些从isMultipleAdvertisementSupported() 返回 true 但仅在蓝牙开启时。

注意:在 2.、3. 和 4. 中,BluetoothLeAdvertiser 对象仅在蓝牙开启时返回。否则返回 null,因此在启用蓝牙之前,您实际上不知道设备是否支持 LE 广告。

检查 nRF Connect 应用程序:禁用蓝牙,安装应用程序,打开并选择广告商选项卡或导航菜单 -> 设备信息。在显示状态之前,它会要求您打开蓝牙。

【讨论】:

我目前在不属于任何这些类型的设备上进行测试,Huawei Mate 20 lite: isMultipleAdvertisementSupported() 返回 false,即使它确实允许广告,但一次只能有一个(因此它是正确的,因为它不允许多个)。 @JorgeGalvão 我在将荣耀 10 更新到 Android 10 时遇到了同样的问题。如果设备具有广告功能,但没有多重广告功能,您如何开始投放广告? @giuseppe_para 很抱歉直到现在才错过你的问题......我认为我从来没有解决过这个问题,因为我放弃了这个项目。我认为我什至没有处理 getBluetoothLeAdvertiser() 将返回 null 的情况。就我看到的代码而言,我刚刚测试了getSystemService(Context.BLUETOOTH_SERVICE),它的getAdapter() 为null。然后我还检查了AdvertiseCallbackonStartFailure回调。 @giuseppe_para 您可以在此处检查 ForegroundService.java 和 BluetoothLeAdvertiseHelperLollipop.java(它总体上工作正常,但我从未针对许多设备进行过测试):github.com/Track-COVID-19/Track-COVID-19-Android/tree/develop/… 谢谢!我不再在这个项目上工作了,但是当我再次工作时,我会尝试这个解决方案。【参考方案2】:

请参阅this question 以获得可能的答案,并非所有设备都支持 BLE 广告。

还可以尝试按照建议的here省略设备名称。

【讨论】:

我检查了蓝牙 LE 功能,它在我的设备上可用。 PackageManager manager = getApplicationContext().getPackageManager(); boolean featureAvailable = manager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); if(!featureAvailable) Toast.makeText(this, "Bluetooth LE feature is not available", Toast.LENGTH_SHORT).show(); 仅蓝牙 LE 功能是不够的。并非所有具有蓝牙 LE 的设备都支持该广告。有一个list of compatible devices,如果您尝试this app from Radius,您可以测试您的设备是否可以做广告。 其实我用的是Qualcomm eval kit for Android,APQ 8084,它有蓝牙4.1,这意味着它支持LE模式。 您是否查看了@Markus Kauppinen 建议的应用程序? 我检查了它,但每次点击信标发射器时它都会崩溃。另外,通过我的代码,我看到“不支持多个广告”的 ToastMessage,这是否意味着我无法传输信标?【参考方案3】:

您只需在您的方法上添加此代码:@TargetApi(Build.VERSION_CODES.M)

【讨论】:

以上是关于Android 中的 BLE 广告的主要内容,如果未能解决你的问题,请参考以下文章

如何在android中配置ble广告以供ios检测

Android BLE扫描模式设置间隔

检测Android App收到扫描请求时是不是发送BLE扫描响应

如何在 Android 中获取当前连接的 BLE 外围设备

降低 ble for android 的 Tx 功率

Android BLE GATT 外设模式通知