Android_BLE 基于Jetpack Bluetooth实现文件传输指南。

一、技术选型说明

  1. 蓝牙协议选择

    • 大文件传输:使用 Classic Bluetooth RFCOMM Socket(适合 1MB 以上文件)
    • 小数据包传输:使用 BLE(适合实时性要求高的场景)
  2. Jetpack 组件整合

gradle 复制代码
dependencies {
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2"
    implementation "androidx.work:work-runtime-ktx:2.8.1"
}

二、核心实现步骤

1. 权限配置

xml 复制代码
<!-- Android 12+ 需要新增权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

<!-- 文件传输需要 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

2. 设备发现与配对

示例代码:

kotlin 复制代码
class BluetoothViewModel : ViewModel() {
    private val _devices = MutableStateFlow<List<BluetoothDevice>>(emptyList())
    val devices: StateFlow<List<BluetoothDevice>> = _devices

    private val receiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            when(intent.action) {
                BluetoothDevice.ACTION_FOUND -> {
                    val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
                    device?.let { 
                        _devices.update { current -> current + it }
                    }
                }
            }
        }
    }

    fun startDiscovery(context: Context) {
        context.registerReceiver(receiver, IntentFilter(BluetoothDevice.ACTION_FOUND))
        BluetoothAdapter.getDefaultAdapter()?.startDiscovery()
    }

    fun stopDiscovery(context: Context) {
        BluetoothAdapter.getDefaultAdapter()?.cancelDiscovery()
        context.unregisterReceiver(receiver)
    }
}

3、文件传输核心实现

示例代码:

kotlin 复制代码
class FileTransferService : Service() {
    private val channelId = "BluetoothTransfer"
    private var socket: BluetoothSocket? = null

    override fun onBind(intent: Intent?): IBinder? = null

    override fun onCreate() {
        createNotificationChannel()
        startForeground(1, createNotification())
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "File Transfer",
                NotificationManager.IMPORTANCE_LOW
            )
            getSystemService(NotificationManager::class.java)
                .createNotificationChannel(channel)
        }
    }

    private fun createNotification() = NotificationCompat.Builder(this, channelId)
        .setContentTitle("文件传输中")
        .setSmallIcon(R.drawable.ic_transfer)
        .build()

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        intent?.getParcelableExtra<BluetoothDevice>("device")?.let { device ->
            CoroutineScope(Dispatchers.IO).launch {
                try {
                    socket = device.createRfcommSocketToServiceRecord(
                        UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
                    )
                    socket?.connect()
                    transferFile(intent.getParcelableExtra("fileUri")!!)
                } catch (e: IOException) {
                    Log.e("BluetoothTransfer", "Transfer failed", e)
                } finally {
                    socket?.close()
                    stopSelf()
                }
            }
        }
        return START_NOT_STICKY
    }

    private fun transferFile(uri: Uri) {
        contentResolver.openInputStream(uri)?.use { input ->
            socket?.outputStream?.use { output ->
                val buffer = ByteArray(8192)
                var bytesRead: Int
                while (input.read(buffer).also { bytesRead = it } != -1) {
                    output.write(buffer, 0, bytesRead)
                    output.flush()
                }
            }
        }
    }
}

4、使用 WorkManager 管理传输任务

示例代码:

kotlin 复制代码
class BluetoothTransferWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        val device = inputData.getParcelable<BluetoothDevice>("device")!!
        val fileUri = inputData.getString("fileUri")!!.toUri()

        return try {
            withContext(Dispatchers.IO) {
                val socket = device.createRfcommSocketToServiceRecord(
                    UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
                )
                socket.connect()
                
                applicationContext.contentResolver.openInputStream(fileUri)?.use { input ->
                    socket.outputStream.use { output ->
                        input.copyTo(output)
                    }
                }
                Result.success()
            }
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

// 在 ViewModel 中启动传输
fun startTransfer(device: BluetoothDevice, uri: Uri) {
    val request = OneTimeWorkRequestBuilder<BluetoothTransferWorker>()
        .setInputData(workDataOf(
            "device" to device,
            "fileUri" to uri.toString()
        ))
        .build()

    WorkManager.getInstance(context).enqueue(request)
}

三、优化实践

1. 传输进度监控

示例代码:

kotlin 复制代码
class TransferProgressMonitor(private val callback: (Float) -> Unit) : FileTransferService() {
    override fun transferFile(uri: Uri) {
        val totalBytes = contentResolver.openFileDescriptor(uri, "r")?.use {
            it.statSize
        } ?: 0L

        var transferredBytes = 0L
        contentResolver.openInputStream(uri)?.use { input ->
            socket?.outputStream?.use { output ->
                val buffer = ByteArray(8192)
                var bytesRead: Int
                while (input.read(buffer).also { bytesRead = it } != -1) {
                    output.write(buffer, 0, bytesRead)
                    transferredBytes += bytesRead
                    val progress = transferredBytes.toFloat() / totalBytes
                    callback(progress.coerceIn(0f, 1f))
                }
            }
        }
    }
}

2. 断点续传实现

示例代码:

kotlin 复制代码
class ResumableTransferWorker : CoroutineWorker(...) {
    override suspend fun doWork(): Result {
        val sessionFile = File(cacheDir, "transfer_session")
        var session = sessionFile.readSession() ?: TransferSession()

        try {
            while (session.transferred < session.totalSize) {
                transferChunk(session)
                sessionFile.writeSession(session)
            }
            return Result.success()
        } catch (e: Exception) {
            return if (runAttemptCount < 3) Result.retry() else Result.failure()
        }
    }

    private suspend fun transferChunk(session: TransferSession) {
        // 实现分块传输逻辑
    }
}

data class TransferSession(
    val totalSize: Long = 0,
    val transferred: Long = 0
)

四、注意事项

1、版本兼容处理

kotlin 复制代码
fun checkBluetoothAvailability(): Boolean {
    return when {
        Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT -> {
            // 4.4 以下版本不支持 BLE
            false
        }
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            // Android 12+ 需要检查新权限
            checkSelfPermission(BluetoothAdapter.ACTION_REQUEST_ENABLE)
        }
        else -> true
    }
}

2、传输稳定性优化

  • 设置合理的 MTU 大小(512 bytes 为推荐值)
  • 实现 ACK 确认机制
  • 添加数据校验(CRC32/MD5)

3、典型错误处理

示例代码:

kotlin 复制代码
fun handleTransferError(exception: Exception) {
    when (exception) {
        is SocketTimeoutException -> showError("连接超时")
        is IOException -> showError("IO 异常")
        is SecurityException -> requestPermissions()
        else -> showError("未知错误")
    }
}

五、扩展功能建议

1、多设备并行传输

示例代码:

kotlin 复制代码
class ParallelTransferManager {
    private val transfers = ConcurrentHashMap<String, Job>()

    fun addTransfer(device: BluetoothDevice, file: File) {
        transfers[device.address] = CoroutineScope(Dispatchers.IO).launch {
            // 实现并行传输逻辑
        }
    }
}

2、文件加密传输

示例代码:

kotlin 复制代码
fun encryptData(data: ByteArray, key: SecretKey): ByteArray {
    val cipher = Cipher.getInstance("AES/GCM/NoPadding")
    cipher.init(Cipher.ENCRYPT_MODE, key)
    return cipher.doFinal(data)
}

3、传输历史记录

示例代码:

kotlin 复制代码
@Entity
data class TransferRecord(
    @PrimaryKey val id: String,
    val fileName: String,
    val deviceName: String,
    val timestamp: Long,
    val success: Boolean
)

@Dao
interface TransferDao {
    @Query("SELECT * FROM TransferRecord ORDER BY timestamp DESC")
    fun getAll(): Flow<List<TransferRecord>>
}

本方案结合了传统蓝牙的文件传输能力与 Jetpack 组件的现代化架构优势,实现了:

  • 完整的生命周期管理
  • 后台传输可靠性
  • 进度可视化
  • 断点续传能力
  • 完善的错误处理机制

建议根据实际业务需求选择合适的技术组合,对于需要兼容旧设备的场景可优先使用经典蓝牙方案,对于低功耗需求场景可考虑 BLE 分片传输方案。

更多分享

  1. Android_BLE开发------扫描
  2. Android_BLE开发------连接
  3. Android_BLE开发------读写
  4. Android_BLE开发------绑定
  5. Android_BLE开发------优化
相关推荐
雨白8 小时前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹9 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空11 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭11 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日12 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安12 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑12 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟16 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡18 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0018 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体