Android蓝牙文件接收完成是否对外发广播?对应Intent是什么?
我完全懂你现在的困扰——那些你尝试的BT_OPP_TRANSFER_DONE之类的Intent,确实是系统默认蓝牙应用的私有广播,只给自身组件用,对外根本不触发,也就导致你实际传文件时接收器毫无反应,只有手动用ADB发Intent才会生效。下面给你几个经过验证的可行思路:
1. 监听媒体扫描完成广播
蓝牙接收的文件基本都会被系统媒体扫描器识别,你可以监听公开的android.intent.action.MEDIA_SCANNER_FINISHED广播。不过要注意,这个广播会在所有媒体文件扫描完成时触发,不止是蓝牙文件,所以需要额外判断文件的来源路径。
在AndroidManifest里的过滤配置示例:
<receiver android:name=".BluetoothFileReceiver"> <intent-filter> <action android:name="android.intent.action.MEDIA_SCANNER_FINISHED" /> <data android:scheme="file" /> </intent-filter> </receiver>
然后在接收器里获取文件路径,判断是否来自蓝牙默认接收目录(通常是/sdcard/Bluetooth/或者/storage/emulated/0/Bluetooth/),再执行你的逻辑。
2. 用ContentObserver监听BluetoothShare数据库
系统蓝牙应用会把所有传输记录存在content://com.android.bluetooth.opp/btopp这个ContentProvider里,你可以注册一个ContentObserver来监听这个数据库的变化,一旦有新的传输完成记录,就触发你的代码。
步骤参考:
- 先申请对应权限:Android 6.0+需要动态申请
READ_EXTERNAL_STORAGE;Android 10+如果要直接访问文件,可能需要MANAGE_EXTERNAL_STORAGE权限(需用户手动在设置授权),或者用MediaStore操作。 - 代码里注册ContentObserver:
getContentResolver().registerContentObserver( Uri.parse("content://com.android.bluetooth.opp/btopp"), true, new ContentObserver(new Handler(Looper.getMainLooper())) { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); // 查询最新的传输记录,判断状态是否为完成 Cursor cursor = getContentResolver().query( Uri.parse("content://com.android.bluetooth.opp/btopp"), new String[]{"_id", "status", "uri"}, null, null, "_id DESC" // 取最新的一条记录 ); if (cursor != null && cursor.moveToFirst()) { int status = cursor.getInt(cursor.getColumnIndex("status")); // 状态值2通常代表传输完成(不同系统版本可能有差异,建议自行测试确认) if (status == 2) { String fileUri = cursor.getString(cursor.getColumnIndex("uri")); // 这里执行你的业务逻辑 } cursor.close(); } } } );
注意:不同厂商的定制系统可能会修改这个ContentProvider的结构或包名,需要做适配测试。
3. 借助AccessibilityService捕获通知
如果上面的方法都不满足需求,你可以用AccessibilityService监听系统蓝牙应用的通知——当文件接收完成时,系统会弹出对应通知,AccessibilityService可以捕获到这个通知的内容,进而触发你的逻辑。
实现步骤:
- 创建继承自AccessibilityService的类:
public class BluetoothAccessibilityService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) { List<CharSequence> texts = event.getText(); if (!texts.isEmpty()) { for (CharSequence text : texts) { // 根据系统语言判断通知关键词,比如中文的"已接收"、英文的"Received" if (text.toString().contains("已接收")) { // 执行你的逻辑 break; } } } } } @Override public void onInterrupt() {} }
- 在AndroidManifest中注册Service:
<service android:name=".BluetoothAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service>
- 创建
res/xml/accessibility_service_config.xml配置文件:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeNotificationStateChanged" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault" android:canRetrieveWindowContent="true" />
这种方法需要用户手动在系统设置里开启辅助功能,体验上有一点门槛,但兼容性相对较好。
4. 权限适配提醒
- Android 6.0及以上,必须动态申请
READ_EXTERNAL_STORAGE权限; - Android 10(API 29)及以上,访问外部存储优先用MediaStore,或者申请
MANAGE_EXTERNAL_STORAGE权限(需用户手动授权); - 部分定制系统的蓝牙应用包名可能不是
com.android.bluetooth,监听ContentObserver时需要注意适配。
内容的提问来源于stack exchange,提问作者Prince Rambade

