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

Android平台基于RFCOMM套接字实现蓝牙传输文本/音视频文件问题

Android RFCOMM蓝牙套接字实现文本/图片/音频传输完整指南

看你现在在搞Android蓝牙RFCOMM的跨设备传输,代码里已经搭了按钮点击的基础框架,我来帮你把这个功能补全,涵盖文本、图片、音频这几种常见类型的传输,顺便把权限适配和细节处理都捋清楚,避免踩坑。

一、先把权限配置到位

Android不同版本的蓝牙和文件权限差异很大,先在AndroidManifest.xml里把该加的权限加上:

<!-- 蓝牙核心权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

<!-- 文件读写权限(适配全版本) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="33" />

然后你的checkBTPermissions()和file_permission()方法可以这么写,完美适配Android 12+的权限要求:

private void checkBTPermissions() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        String[] btPermissions = {
            Manifest.permission.BLUETOOTH_CONNECT,
            Manifest.permission.BLUETOOTH_SCAN
        };
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, btPermissions, 100);
        }
    } else {
        String[] oldBtPermissions = {
            Manifest.permission.BLUETOOTH,
            Manifest.permission.BLUETOOTH_ADMIN
        };
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, oldBtPermissions, 100);
        }
    }
}

private void file_permission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        if (!Environment.isExternalStorageManager()) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
            Uri uri = Uri.fromParts("package", getPackageName(), null);
            intent.setData(uri);
            startActivity(intent);
        }
    } else {
        String[] filePermissions = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
        };
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, filePermissions, 200);
        }
    }
}

二、完善RFCOMM连接与传输逻辑

首先假设你已经有了mBluetoothConnection这个蓝牙连接管理类,核心是把write()方法写健壮,因为不管是文本还是文件,本质都是传输字节流:

public class BluetoothConnection {
    private OutputStream mOutputStream;

    // 初始化时传入已建立的RFCOMM套接字,获取输出流
    public BluetoothConnection(BluetoothSocket socket) {
        try {
            mOutputStream = socket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void write(byte[] bytes) {
        try {
            if (mOutputStream != null) {
                // 先发送数据长度,让接收端知道要接收多少字节,避免粘包
                mOutputStream.write(intToBytes(bytes.length));
                mOutputStream.write(bytes);
                mOutputStream.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
            // 这里可以加个Toast提示发送失败
        }
    }

    // 把int转成4字节数组,用于传输长度标识
    private byte[] intToBytes(int value) {
        return new byte[]{
            (byte)(value >> 24),
            (byte)(value >> 16),
            (byte)(value >> 8),
            (byte)value
        };
    }
}

接下来分类型处理传输:

1. 传输文本

直接把输入框的文本转成UTF-8字节数组发送就行:

send.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        checkBTPermissions();
        file_permission();

        // 传输文本逻辑
        String text = etSend.getText().toString().trim();
        if (!text.isEmpty() && mBluetoothConnection != null) {
            byte[] textBytes = text.getBytes(StandardCharsets.UTF_8);
            mBluetoothConnection.write(textBytes);
            Toast.makeText(MainActivity.this, "文本已发送", Toast.LENGTH_SHORT).show();
        }
    }
});

2. 传输图片/音频文件

文件传输的核心是把文件读成字节流,这里给个基础实现,如果是大文件(比如几百M的音频),建议改成分段读取发送,避免OOM:

// 通用文件发送方法
private void sendFile(String filePath) {
    File targetFile = new File(filePath);
    if (!targetFile.exists() || !targetFile.isFile()) {
        Toast.makeText(this, "文件不存在或不是合法文件", Toast.LENGTH_SHORT).show();
        return;
    }

    try {
        FileInputStream fis = new FileInputStream(targetFile);
        byte[] fileBytes = new byte[(int) targetFile.length()];
        fis.read(fileBytes);
        fis.close();

        if (mBluetoothConnection != null) {
            mBluetoothConnection.write(fileBytes);
            Toast.makeText(this, "文件发送成功", Toast.LENGTH_SHORT).show();
        }
    } catch (IOException e) {
        e.printStackTrace();
        Toast.makeText(this, "文件读取失败", Toast.LENGTH_SHORT).show();
    }
}

然后在按钮点击里调用即可(比如你要传/sdcard/bluetooth/tom.mp3):

send.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        checkBTPermissions();
        file_permission();

        // 传输音频/图片文件
        String filePath = "/sdcard/bluetooth/tom.mp3"; // 换成你的文件路径
        sendFile(filePath);
    }
});

三、接收端的对应处理(重要!不然发了收不到)

接收端需要先读取4字节的长度标识,再读取对应长度的数据,避免粘包:

public class BluetoothReceiver {
    private InputStream mInputStream;

    public BluetoothReceiver(BluetoothSocket socket) {
        try {
            mInputStream = socket.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public byte[] read() throws IOException {
        // 先读取4字节的数据长度
        byte[] lengthBytes = new byte[4];
        mInputStream.read(lengthBytes);
        int dataLength = bytesToInt(lengthBytes);

        // 循环读取完整数据
        byte[] dataBytes = new byte[dataLength];
        int totalRead = 0;
        while (totalRead < dataLength) {
            int readCount = mInputStream.read(dataBytes, totalRead, dataLength - totalRead);
            if (readCount == -1) break; // 连接断开
            totalRead += readCount;
        }
        return dataBytes;
    }

    // 把4字节数组转回int
    private int bytesToInt(byte[] bytes) {
        return ((bytes[0] & 0xFF) << 24) |
               ((bytes[1] & 0xFF) << 16) |
               ((bytes[2] & 0xFF) << 8) |
               (bytes[3] & 0xFF);
    }
}

然后根据接收类型处理:

// 接收文本
byte[] receivedBytes = bluetoothReceiver.read();
String receivedText = new String(receivedBytes, StandardCharsets.UTF_8);
// 可以把receivedText显示到UI上

// 接收文件
byte[] receivedFileBytes = bluetoothReceiver.read();
File saveFile = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "received.jpg");
FileOutputStream fos = new FileOutputStream(saveFile);
fos.write(receivedFileBytes);
fos.close();

四、避坑小贴士

  • 大文件分段传输:如果文件超过100M,一定要改成每次读1024或4096字节循环发送,不然会直接OOM
  • RFCOMM套接字创建:服务器端要用BluetoothAdapter.listenUsingRfcommWithServiceRecord来监听,客户端用device.createRfcommSocketToServiceRecord来连接
  • 文件路径优化:尽量用Context.getExternalFilesDir()获取合法的外部存储路径,不要硬编码/sdcard,适配Android 10+的分区存储
  • 权限回调处理:别忘了在onRequestPermissionsResult里处理权限申请结果,确保权限通过后再执行传输逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:13:56