Android - 蓝牙接收器无法在广告设备上运行

问题描述

这是我在 Androind 中使用蓝牙的第一个应用程序,但我遇到了一个独特的问题:蓝牙接收器无法在宣传蓝牙服务的设备上运行。

我同时在两部手机上测试了该应用程序(为了更好地解释,我将它们称为手机 A 和 B)。一开始我用手机A开始广告,然后我用手机B开始发现,最后我按下手机B上的按钮发送数据。此按钮应首先启动 Gatt 连接,如果它正在工作,则应广播一条消息以确认连接。要查看它,我在广播接收器中使用了日志,但我得到的结果是此消息仅出现在电话 B 的 logcat 中,而不出现在电话 A 中。

我查看了很多示例并在 Stackoverflow 上发帖,但似乎找不到解决此问题的方法

所以我真的找不到真正的问题是什么。也许我只是在使用蓝牙课程时使用得不好,或者我只是缺乏知识。无论如何,这里有 MainActivity 的所有代码,因为它是这个简单项目的唯一类。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView mText;
private Button mAdvertiseButton;
private Button mdiscoverButton;
private Button mSendButton;
private String TAG = "INFOBLERESULTS";
private BluetoothLeScanner mBluetoothLeScanner;
private BluetoothDevice bluetoothDevice;
private Handler mHandler = new Handler();
private ScanCallback mScanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType,ScanResult result) {
        Log.d(TAG,result.getDevice().getAddress());
        super.onScanResult(callbackType,result);
        if( result == null
                || result.getDevice() == null
                || TextUtils.isEmpty(result.getDevice().getAddress()) )
            return;

        StringBuilder builder = new StringBuilder( result.getDevice().getAddress() );
        builder.append("\n").append(result.getDevice().getName());

        //builder.append("\n").append(new String(result.getScanRecord().getServiceData(result.getScanRecord().getServiceUuids().get(0)),Charset.forName("UTF-8")));

        mText.setText(builder.toString());
        bluetoothDevice = result.getDevice();
        bluetoothLeService = new BluetoothLeService();
        bluetoothLeService.setAddress(result.getDevice().getAddress());
    }

    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        super.onBatchScanResults(results);
    }

    @Override
    public void onScanFailed(int errorCode) {
        Log.e( TAG,"discovery onScanFailed: " + errorCode );
        super.onScanFailed(errorCode);
    }
};
private BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
private boolean connected = false;

//Inner classes
class BluetoothLeService extends Service {
    public final static String ACTION_GATT_CONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
    public final static String ACTION_GATT_disCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_disCONNECTED";
    public final static String ACTION_GATT_SERVICES_disCOVERED =
            "com.example.bluetooth.le.ACTION_GATT_SERVICES_disCOVERED";
    public final static String ACTION_DATA_AVAILABLE =
            "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";

    private static final int STATE_disCONNECTED = 0;
    private static final int STATE_CONNECTED = 2;

    private int connectionState;
    public Context ctx;

    protected BluetoothGatt bluetoothGatt;

    protected final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt,int status,int newState) {
            Log.i(TAG,"GATT status = "+ status + " newState = " + newState);
            if(status == BluetoothGatt.GATT_SUCCESS){
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    // successfully connected to the GATT Server
                    connectionState = STATE_CONNECTED;
                    broadcastUpdate(ACTION_GATT_CONNECTED);
                    bluetoothGatt = gatt;
                } else if (newState == BluetoothProfile.STATE_disCONNECTED) {
                    // disconnected from the GATT Server
                    connectionState = STATE_disCONNECTED;
                    broadcastUpdate(ACTION_GATT_disCONNECTED);
                    gatt.close();
                }
            }else{
                gatt.close();
            }

        }

        @Override
        public void onServicesdiscovered(BluetoothGatt gatt,int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_GATT_SERVICES_disCOVERED);
            } else {
                Log.w(TAG,"onServicesdiscovered received: " + status);
            }
        }

        @Override
        public void onCharacteristicRead(
                BluetoothGatt gatt,BluetoothGattCharacteristic characteristic,int status
        ) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                broadcastUpdate(ACTION_DATA_AVAILABLE,characteristic);
            }
        }

        @Override
        public void onCharacteristicChanged(
                BluetoothGatt gatt,BluetoothGattCharacteristic characteristic
        ) {
            broadcastUpdate(ACTION_DATA_AVAILABLE,characteristic);
        }


    };

    private Binder binder = new LocalBinder();
    private String address = "";

    public void setAddress(String address) {
        this.address = address;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        close();
        return super.onUnbind(intent);
    }

    private void close() {
        if (bluetoothGatt == null) {
            return;
        }
        bluetoothGatt.close();
        bluetoothGatt = null;
    }

    public boolean connect() {
        if (bluetoothAdapter == null || this.address == null || this.address.equals("")) {
            Log.w(TAG,"BluetoothAdapter not initialized or unspecified address.");
            return false;
        }

        try {
            final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
            bluetoothGatt = device.connectGatt(MainActivity.this.getApplicationContext(),false,gattCallback);
            Log.d(TAG,"GATT "+ bluetoothGatt);
            return true;

        } catch (IllegalArgumentException exception) {
            Log.w(TAG,"Device not found with provided address.");
            return false;
        }
    }

    private void broadcastUpdate(final String action) {
        final Intent intent = new Intent(action);
        Log.i(TAG,intent + "");
        MainActivity.this.getApplicationContext().sendbroadcast(intent);
    }

    private void broadcastUpdate(final String action,BluetoothGattCharacteristic characteristic) {
        final Intent intent = new Intent(action);
        
        sendbroadcast(intent);

    }

    public List<BluetoothGattService> getSupportedGattServices() {
        if (bluetoothGatt == null) return null;
        return bluetoothGatt.getServices();
    }

    public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
        if (bluetoothGatt == null) {
            Log.w(TAG,"BluetoothGatt not initialized");
            return;
        }
        bluetoothGatt.readCharacteristic(characteristic);
    }

    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,boolean enabled) {
        if (bluetoothGatt == null) {
            Log.w(TAG,"BluetoothGatt not initialized");
            return;
        }
        bluetoothGatt.setCharacteristicNotification(characteristic,enabled);
    }



    class LocalBinder extends Binder {
        public BluetoothLeService getService() {
            return BluetoothLeService.this;
        }
    }
}
private BluetoothLeService bluetoothLeService = new BluetoothLeService();

private final ServiceConnection serviceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name,IBinder service) {
        bluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
        if (bluetoothLeService != null) {
            if (!bluetoothAdapter.isEnabled()) {
                Log.e(TAG,"Unable to initialize Bluetooth");
            }
            else{
                bluetoothLeService.connect();
                Log.i(TAG,"Service connected");
            }
        }
    }

    @Override
    public void onServicedisconnected(ComponentName name) {
        bluetoothLeService = null;
    }
};

private final broadcastReceiver gattUpdateReceiver = new broadcastReceiver() {
    @Override
    public void onReceive(Context context,Intent intent) {
        final String action = intent.getAction();
        Log.d(TAG,"RECEIVED " + action);
        if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
            connected = true;
            //Log.d(TAG,"CONNECTED");
        } else if (BluetoothLeService.ACTION_GATT_disCONNECTED.equals(action)) {
            connected = false;
            //Log.d(TAG,"disCONNECTED");
        }
    }

};



private final ActivityResultLauncher<Intent> someActivityResultLauncher = registerForActivityResult(
        new ActivityResultContracts.StartActivityForResult(),new ActivityResultCallback<ActivityResult>() {
            @Override
            public void onActivityResult(ActivityResult result) {
                Log.i("RESULT",result.getResultCode() + "");
                setup();
            }
        });

private void setup() {
    bluetoothLeService.ctx = this.getApplicationContext();
    Log.d("APPLICATIONCONTEXT",bluetoothLeService.ctx + "");
    mdiscoverButton.setonClickListener(this);
    mAdvertiseButton.setonClickListener(this);
    mSendButton.setonClickListener(this);

    mBluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();

    if (!BluetoothAdapter.getDefaultAdapter().isMultipleAdvertisementSupported()) {
        Toast.makeText(this,"Multiple advertisement not supported",Toast.LENGTH_SHORT).show();
        mAdvertiseButton.setEnabled(false);
        mdiscoverButton.setEnabled(false);
    }
}

@Override
public void onRequestPermissionsResult(int requestCode,@NonNull @org.jetbrains.annotations.NotNull String[] permissions,@NonNull @org.jetbrains.annotations.NotNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode,permissions,grantResults);
    if (requestCode == 2) {
        final LocationManager manager = (LocationManager) getSystemService( Context.LOCATION_SERVICE );
        if(!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)){
            Intent enableLocationIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
            someActivityResultLauncher.launch(enableLocationIntent);
        }
        else{
            setup();
        }


    }

}

@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mText = (TextView) findViewById( R.id.text );
    mdiscoverButton = (Button) findViewById( R.id.discover_btn );
    mAdvertiseButton = (Button) findViewById( R.id.advertise_btn );
    mSendButton = (Button) findViewById( R.id.send_btn );

    this.requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.ACCESS_FINE_LOCATION},2);

}

@Override
protected void onResume() {
    super.onResume();
    MainActivity.this.getApplicationContext().registerReceiver(gattUpdateReceiver,makeGattUpdateIntentFilter());
}
@Override
protected void onPause() {
    super.onPause();
    MainActivity.this.getApplicationContext().unregisterReceiver(gattUpdateReceiver);
}




@Override
public void onClick(View v) {
    Log.d(TAG,getString( R.string.ble_uuid ));
    if( v.getId() == R.id.discover_btn ) {
        discover();
    } else if( v.getId() == R.id.advertise_btn ) {
        advertise();
        //MainActivity.this.getApplicationContext().registerReceiver(gattUpdateReceiver,makeGattUpdateIntentFilter());
    } else if (v.getId() == R.id.send_btn){
        //MainActivity.this.getApplicationContext().unregisterReceiver(gattUpdateReceiver);
        send();

    }
}

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

    AdvertiseSettings settings = new AdvertiseSettings.Builder().setTimeout(0)
            .setAdvertiseMode( AdvertiseSettings.ADVERTISE_MODE_BALANCED )
            .setTxPowerLevel( AdvertiseSettings.ADVERTISE_TX_POWER_HIGH )
            .setConnectable( true )
            .build();
    ParcelUuid pUuid = ParcelUuid.fromString( getString( R.string.ble_uuid ) ) ;

    AdvertiseData data = new AdvertiseData.Builder()
            .addServiceUuid( pUuid ).setIncludeDeviceName(false)
            .build();
    AdvertiseCallback advertisingCallback = new AdvertiseCallback() {
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect) {
            Log.d(TAG,"START ADVERTISING");
            super.onStartSuccess(settingsInEffect);
        }

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

    advertiser.startAdvertising( settings,data,advertisingCallback );


}

public void discover(){
    ScanFilter filter = new ScanFilter.Builder()
            .setServiceUuid( ParcelUuid.fromString( getString(R.string.ble_uuid ) ) )
            .build();
    List<ScanFilter> filters = new ArrayList<>();
    filters.add( filter );
    ScanSettings settings = new ScanSettings.Builder()
            .setScanMode( ScanSettings.SCAN_MODE_BALANCED )
            .build();
    mBluetoothLeScanner.startScan(filters,settings,mScanCallback);
    Log.d(TAG,"discovery started");
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            Log.d(TAG,"discovery stopped");
            mBluetoothLeScanner.stopScan(mScanCallback);
        }
    },10000);
}

public void send(){
    Log.d(TAG,"START CONNECTIONG GATT");
    mBluetoothLeScanner.stopScan(mScanCallback);
    //boundGatt();
    connectGatt();
}

public void boundGatt(){
    Intent gattServiceIntent = new Intent(MainActivity.this.getApplicationContext(),BluetoothLeService.class);
    bindService(gattServiceIntent,serviceConnection,Context.BIND_AUTO_CREATE);
}

public void connectGatt(){
    bluetoothLeService.connect();
}

private static IntentFilter makeGattUpdateIntentFilter() {
    final IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
    intentFilter.addAction(BluetoothLeService.ACTION_GATT_disCONNECTED);
    intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_disCOVERED);
    intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
    return intentFilter;
}

}

解决方法

您的外设(电话 A)只做广告,但BluetoothGattServer 未设置。这可能是所描述行为的原因 - 广告和扫描工作,但连接没有。

BluetoothGatt + BluetoothGattCallback 代表 Central 角色(你称之为电话 B)。 BluetoothGattServer + BluetoothGattServerCallback 用于外设(电话 A)。

注意事项:

  1. 来自中央端(电话 B)的连接看起来不错,因为当找到广告设备时,您可以使用 bluetoothAdapter.getRemoteDevice(address) 获取它,然后您拨打 device.connectGatt
  2. 要传输一些数据,您需要将带有 BluetoothGattCharacteristic 的 BluetoothGattService 添加到您的 BluetoothGattServer - example of setup in Kotlin
  3. 示例 github 上的项目:BLEProof - 在 Kotlin 中,2 个应用相互通信:Central 和 Peripheral,所有代码在 MainActivity.kt 中