You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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...

问题成因及修复方案

  1. UUID不匹配

    • 当前使用的UUID 00001800-0000-1000-8000-00805f9b34fb是蓝牙Generic Access Profile的UUID,并非串行端口(SPP)的标准UUID。串行连接需要使用SPP UUID:00001101-0000-1000-8000-00805f9b34fb。
    • 修复:替换代码中的UUID为SPP标准UUID,同时确保树莓派端监听该UUID对应的服务。
  2. 树莓派未启动串行服务

    • 仅通过bluetoothctl配置可发现和可配对不足以建立连接,需要树莓派启动SPP服务监听连接请求。
    • 修复:在树莓派执行sudo rfcomm watch hci0启动临时监听,或配置开机启动的SPP服务,确保设备处于等待连接状态。
  3. 蓝牙角色配置错误

    • 树莓派设置为“Device”角色可能不适合作为服务端接收连接,通常需要设置为“Host”角色。
    • 修复:在bluetoothctl中执行power on、discoverable on、pairable on,并确保agent NoInputNoOutput已设置,可尝试重置蓝牙配置后重新配对。
  4. 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" />
      
  5. 连接前未取消蓝牙发现

    • Android设备扫描蓝牙时会占用资源,导致无法建立连接。
    • 修复:在connectToDevice方法开头添加bluetoothAdapter.cancelDiscovery(),确保蓝牙资源可用。

内容的提问来源于stack exchange,提问作者CodePlague

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 15:42:31