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