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开发------优化
相关推荐
用户693717500138418 分钟前
315曝光AI搜索问题:GEO技术靠内容投喂操控答案,新型营销操作全揭秘
android·前端·人工智能
sp4227 分钟前
NativeScript iOS 平台开发技巧
app·nativescript
进击的cc34 分钟前
彻底搞懂 Binder:不止是 IPC,更是 Android 的灵魂
android·面试
段娇娇44 分钟前
Android jetpack LiveData (三) 粘性数据(数据倒灌)问题分析及解决方案
android·android jetpack
用户2018792831671 小时前
TabLayout被ViewPager2遮盖部分导致Tab难选中
android
法欧特斯卡雷特1 小时前
Kotlin 2.3.20 现已发布,来看看!
android·前端·后端
闻哥1 小时前
深入理解 MySQL InnoDB Buffer Pool 的 LRU 冷热数据机制
android·java·jvm·spring boot·mysql·adb·面试
ii_best2 小时前
安卓/ios开发辅助软件按键精灵小精灵实现简单的UI多配置管理
android·ui·ios·自动化
码农xo2 小时前
android 设备实时传输相机采集的视频到电脑pc端 通过内网wifi 方案
android·数码相机·音视频
常利兵2 小时前
解锁Android的隐藏超链接:Deep Link与App Link探秘
android·gitee