Android应用与Raspberry Pi蓝牙串行连接失败排查请求
蓝牙串行连接树莓派失败:Android应用无法建立稳定连接
问题描述
我正在开发一款Android应用,通过蓝牙串行连接树莓派设备,目标是配对后向树莓派发送通知。目前遇到的核心问题:
- 树莓派可在Android蓝牙设置中被发现,无需密钥即可配对,但配对后手机仅显示“已配对”而非“已连接”,树莓派端显示连接已断开。
- 点击应用内的Pi设备列表项后,Toast提示“正在连接”,但最终连接超时失败,抛出读取错误。
树莓派蓝牙配置
使用bluetoothctl完成如下配置(开机自动运行,角色设置为“Device”而非“Host”):
sudo bluetoothctl power on agent NoInputNoOutput default-agent discoverable on pairable on
Android代码实现
FirstFragment 代码
package com.aaadev.myapplication; import android.Manifest; import android.annotation.SuppressLint; import android.app.NotificationChannel; import android.app.NotificationManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.fragment.app.Fragment; import com.aaadev.myapplication.databinding.FragmentFirstBinding; import java.util.ArrayList; import java.util.Set; public class FirstFragment extends Fragment { private Button buttonSendNotification; private static final int REQUEST_BLUETOOTH_PERMISSIONS = 1; private FragmentFirstBinding binding; private BluetoothAdapter bluetoothAdapter; private ArrayList<String> deviceList; private ArrayList<BluetoothDevice> pairedDevicesList; private ListView listViewDevices; private static final String CHANNEL_ID = "app_push_notification_channel"; @Override public View onCreateView(@NonNull android.view.LayoutInflater inflater, @Nullable android.view.ViewGroup container, @Nullable Bundle savedInstanceState) { binding = FragmentFirstBinding.inflate(inflater, container, false); return binding.getRoot(); } @Override public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); buttonSendNotification = binding.buttonSendNotification; listViewDevices = binding.listViewDevices; bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); deviceList = new ArrayList<>(); pairedDevicesList = new ArrayList<>(); // Check Bluetooth permissions and list paired devices if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (ActivityCompat.checkSelfPermission(getContext(), Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.BLUETOOTH_CONNECT}, REQUEST_BLUETOOTH_PERMISSIONS); } else { listPairedDevices(); } } else { listPairedDevices(); } // Button click listener to send a push notification buttonSendNotification.setOnClickListener(v -> sendPushNotification()); // ListView item click listener to select a device listViewDevices.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { BluetoothDevice selectedDevice = pairedDevicesList.get(position); @SuppressLint("MissingPermission") String deviceName = selectedDevice.getName(); if (deviceName != null && deviceName.contains("Pi")) { // Start NotificationService and pass the device's address Intent intent = new Intent(getActivity(), NotificationService.class); intent.putExtra("device_address", selectedDevice.getAddress()); getActivity().startService(intent); Toast.makeText(getContext(), "Connecting to " + deviceName, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(getContext(), "Selected device is not a Pi device", Toast.LENGTH_SHORT).show(); } } }); } @SuppressLint("MissingPermission") private void listPairedDevices() { if (bluetoothAdapter != null) { @SuppressLint("MissingPermission") Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices(); if (pairedDevices.size() > 0) { for (BluetoothDevice device : pairedDevices) { pairedDevicesList.add(device); deviceList.add(device.getName() + "\n" + device.getAddress()); } ArrayAdapter<String> adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, deviceList); listViewDevices.setAdapter(adapter); } else { Toast.makeText(getContext(), "No paired devices found", Toast.LENGTH_SHORT).show(); } } else { Toast.makeText(getContext(), "Bluetooth not supported on this device", Toast.LENGTH_SHORT).show(); } } @SuppressLint("MissingPermission") private void sendPushNotification() { createNotificationChannel(); NotificationCompat.Builder builder = new NotificationCompat.Builder(getContext(), CHANNEL_ID) .setSmallIcon(android.R.drawable.stat_notify_chat) .setContentTitle("New Push Notification") .setContentText("This is a push notification from YapYap!") .setPriority(NotificationCompat.PRIORITY_HIGH); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getContext()); notificationManager.notify(1, builder.build()); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_BLUETOOTH_PERMISSIONS) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { listPairedDevices(); } else { Toast.makeText(getContext(), "Bluetooth permission denied", Toast.LENGTH_SHORT).show(); } } } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { CharSequence name = "App Push Notifications"; String description = "Channel for app push notifications"; int importance = NotificationManager.IMPORTANCE_HIGH; NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); channel.setDescription(description); NotificationManager notificationManager = getContext().getSystemService(NotificationManager.class); if (notificationManager != null) { notificationManager.createNotificationChannel(channel); } } } @Override public void onDestroyView() { super.onDestroyView(); binding = null; } }
NotificationService 代码
package com.aaadev.myapplication; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; import android.os.IBinder; import android.util.Log; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationCompat; import java.io.IOException; import java.io.OutputStream; import java.util.UUID; public class NotificationService extends Service { private static final String CHANNEL_ID = "notification_service_channel"; public static final String ACTION_CONNECTION_STATUS = "com.aaadev.myapplication.CONNECTION_STATUS"; public static final String EXTRA_CONNECTION_SUCCESS = "connection_success"; private BluetoothSocket bluetoothSocket; private OutputStream outputStream; @Override public void onCreate() { super.onCreate(); createNotificationChannel(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { String deviceAddress = intent.getStringExtra("device_address"); if (deviceAddress != null) { new Thread(() -> connectToDevice(deviceAddress)).start(); // Run on background thread } Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("Pi Notification Service") .setContentText("Attempting to connect to Pi device...") .setSmallIcon(android.R.drawable.stat_notify_chat) .build(); startForeground(1, notification); return START_NOT_STICKY; } private void connectToDevice(String address) { BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (!checkBluetoothPermission()) { Log.e("NotificationService", "Missing BLUETOOTH_CONNECT permission."); broadcastConnectionStatus(false); stopSelf(); return; } BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); Log.d("NotificationService", "Attempting to connect to device: " + device.getName() + " (" + address + ")"); try { Log.d("NotificationService", "Creating BluetoothSocket..."); bluetoothSocket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001800-0000-1000-8000-00805f9b34fb")); Log.d("NotificationService", "Connecting to BluetoothSocket..."); bluetoothSocket.connect(); // Attempt to connect Log.d("NotificationService", "BluetoothSocket connected successfully!"); outputStream = bluetoothSocket.getOutputStream(); // Broadcast success status broadcastConnectionStatus(true); } catch (IOException e) { Log.e("NotificationService", "Connection failed: " + e.getMessage(), e); handleConnectionFailure(); } } private boolean checkBluetoothPermission() { return Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED; } private void handleConnectionFailure() { if (bluetoothSocket != null) { try { Log.d("NotificationService", "Closing BluetoothSocket due to failure..."); bluetoothSocket.close(); } catch (IOException closeException) { Log.e("NotificationService", "Failed to close BluetoothSocket: " + closeException.getMessage(), closeException); } } broadcastConnectionStatus(false); stopSelf(); } private void broadcastConnectionStatus(boolean success) { Intent broadcastIntent = new Intent(ACTION_CONNECTION_STATUS); broadcastIntent.putExtra(EXTRA_CONNECTION_SUCCESS, success); sendBroadcast(broadcastIntent); } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel serviceChannel = new NotificationChannel( CHANNEL_ID, "Notification Service Channel", NotificationManager.IMPORTANCE_DEFAULT ); NotificationManager manager = getSystemService(NotificationManager.class); if (manager != null) { manager.createNotificationChannel(serviceChannel); } } } @Override public void onDestroy() { super.onDestroy(); try { if (outputStream != null) outputStream.close(); if (bluetoothSocket != null) bluetoothSocket.close(); } catch (Exception e) { Log.e("NotificationService", "Error closing resources: " + e.getMessage(), e); } } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
连接错误日志
Attempting to connect to device: Pi (XX:XX:XX:D2:XX:XX) 2024-12-15 13:53:42.781 27103-27156 NotificationService com.aaadev.myapplication D Creating BluetoothSocket... 2024-12-15 13:53:42.782 27103-27156 NotificationService com.aaadev.myapplication D Connecting to BluetoothSocket... 2024-12-15 13:53:42.782 27103-27156 BluetoothSocket com.aaadev.myapplication I connect() for device XX:XX:XX:XX:5D:XX called by pid: 27103 2024-12-15 13:53:47.172 27103-27196 ProfileInstaller com.aaadev.myapplication D Installing profile for com.aaadev.myapplication 2024-12-15 13:53:47.868 27103-27156 NotificationService com.aaadev.myapplication E Connection failed: read failed, socket might closed or timeout, read ret: -1 java.io.IOException: read failed, socket might closed or timeout, read ret: -1 at android.bluetooth.BluetoothSocket.readAll(BluetoothSocket.java:1054) at android.bluetooth.BluetoothSocket.readInt(BluetoothSocket.java:1068) at android.bluetooth.BluetoothSocket.connect(BluetoothSocket.java:583) at com.aaadev.myapplication.NotificationService.connectToDevice(NotificationService.java:74) at com.aaadev.myapplication.NotificationService.lambda$onStartCommand$0$com-aaadev-myapplication-NotificationService(NotificationService.java:43) at com.aaadev.myapplication.NotificationService$$ExternalSyntheticLambda0.run(Unknown Source:4) at java.lang.Thread.run(Thread.java:1012) 2024-12-15 13:53:47.868 27103-27156 NotificationService com.aaadev.myapplication D Closing BluetoothSocket due to failure...
问题成因及修复方案
UUID不匹配
- 当前使用的UUID
00001800-0000-1000-8000-00805f9b34fb是蓝牙Generic Access Profile的UUID,并非串行端口(SPP)的标准UUID。串行连接需要使用SPP UUID:00001101-0000-1000-8000-00805f9b34fb。 - 修复:替换代码中的UUID为SPP标准UUID,同时确保树莓派端监听该UUID对应的服务。
- 当前使用的UUID
树莓派未启动串行服务
- 仅通过
bluetoothctl配置可发现和可配对不足以建立连接,需要树莓派启动SPP服务监听连接请求。 - 修复:在树莓派执行
sudo rfcomm watch hci0启动临时监听,或配置开机启动的SPP服务,确保设备处于等待连接状态。
- 仅通过
蓝牙角色配置错误
- 树莓派设置为“Device”角色可能不适合作为服务端接收连接,通常需要设置为“Host”角色。
- 修复:在
bluetoothctl中执行power on、discoverable on、pairable on,并确保agent NoInputNoOutput已设置,可尝试重置蓝牙配置后重新配对。
Android权限缺失
- Android 12+除了
BLUETOOTH_CONNECT,还需BLUETOOTH_SCAN(扫描设备时)和FOREGROUND_SERVICE权限。 - 修复:在
AndroidManifest.xml添加权限声明,并动态申请必要权限:<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
- Android 12+除了
连接前未取消蓝牙发现
- Android设备扫描蓝牙时会占用资源,导致无法建立连接。
- 修复:在
connectToDevice方法开头添加bluetoothAdapter.cancelDiscovery(),确保蓝牙资源可用。
内容的提问来源于stack exchange,提问作者CodePlague
相关产品推荐
相关产品推荐

