Android应用在API 27上无法收到USB闪存驱动器的Intent.ACTION_MEDIA_MOUNTED通知

3
以下代码在API 23上可以工作,但在API 27上无法工作。我了解到,在Android API 26及以上版本中有一些限制,用于接收广播意图,但这些限制仅适用于清单文件中指定的广播意图:
public class USBTest extends Service
{
    private USBMountBroadcastReceiver mountBroadcastReceiver;

    private class USBMountBroadcastReceiver extends BroadcastReceiver
    {
        @Override
        public void onReceive(final Context context, Intent intent)
        {
            String action = intent.getAction();

            if (action == null)
            {
                Log.i(TAG, " got NULL action");
                return;
            }

            if (action.equals(Intent.ACTION_MEDIA_MOUNTED))
            {
                String root = intent.getData().getPath();
                Log.d(TAG, "USB mount path is" + root);
            }
        }
    }

    @Override
    public void onCreate()
    {
        mountBroadcastReceiver = new USBMountBroadcastReceiver();
        IntentFilter usbIntent = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
        usbIntent.addAction(Intent.ACTION_MEDIA_EJECT);
        usbIntent.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
        usbIntent.addAction(Intent.ACTION_MEDIA_REMOVED);
        usbIntent.addDataScheme("file");
        registerReceiver(mountBroadcastReceiver, usbIntent);
    }
}

更新1: 没有服务的广播接收器:

public class USBBroadcastReceiver  extends BroadcastReceiver
{
    private static final String TAG = "USBBroadcastReceiver";

    /**
     * @see android.content.BroadcastReceiver#onReceive(Context,Intent)
     */
    @Override public void onReceive(Context context, Intent intent) {

        String action = intent.getAction();

        if (action!=null) Log.i(TAG, "got action = " + action);

        if (action==null)
        {
            Log.i(TAG, "got NULL action");
        }
        else if (Intent.ACTION_MEDIA_MOUNTED.equals(action))
        {
            Log.i(TAG, "Received media mounted : " + action);
        }
    }
}

在清单文件中:
<receiver android:name="USBBroadcastReceiver" android:exported="True">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_EJECT" />
        <action android:name="android.intent.action.MEDIA_REMOVED" />
        <action android:name="android.intent.action.MEDIA_MOUNTED" />
        <action android:name="android.intent.action.MEDIA_BAD_REMOVAL" />
        <data android:scheme="file" />
    </intent-filter>
</receiver>

更新2: JobIntent服务:

public class TestUSB extends JobIntentService
{
    private static final String TAG = "TestUSB";

    /**
     * Unique job ID for this service.
     */
    static final int JOB_ID = 1001;

    @Override
    protected void onHandleWork(Intent intent)
    {
        Log.i(TAG, "onHandleWork");

        String action = intent.getAction();

        if (action!=null) Log.i(TAG, "got action = " + action);
    }
}

在清单文件中:

<service android:name="TestUSB"
        android:permission="android.permission.BIND_JOB_SERVICE">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_MOUNTED" />
    </intent-filter>
</service>

以上两种方法都无法正常工作。

1
你的目标API级别是多少? - Pablo Rodriguez
@PRA 我的目标是 API 27。 - androidFan
我在使用安卓9的Pixel 1上遇到了同样的问题。这个简单的安卓工程在清单文件中注册并在运行时也进行了注册,但是都没有起作用。然而,在安卓7.0的三星S6上,这段代码可以正常工作。这是工程链接 - Rupert Rawnsley
3个回答

2
从有关奥利奥的后台执行限制的文档中可以看到:

Android 8.0对应用程序在用户没有直接与其交互时可以做什么方面设置了限制。应用程序受到以下两种限制:

  • 后台服务限制:当应用程序处于空闲状态时,其使用后台服务的能力会受到限制。这不适用于前台服务,前台服务对用户更为明显。
  • 广播限制:除了一些特殊情况,应用程序不能使用其清单文件来注册隐式广播。它们仍然可以在运行时注册这些广播,并且可以使用清单文件来注册专门针对其应用程序的显式广播。
因此,存在重要的后台服务限制
在后台运行的服务可能会消耗设备资源,潜在地导致更糟糕的用户体验。为了缓解这个问题,系统对服务应用了一些限制。当一个应用程序在前台时,它可以自由地创建和运行前台和后台服务。当一个应用程序进入后台时,它有几分钟的时间窗口,在这段时间内它仍然被允许创建和使用服务。在该时间窗口结束时,该应用程序被视为处于空闲状态。此时,系统会停止应用程序的后台服务,就好像应用程序调用了服务的Service.stopSelf()方法一样。
在某些情况下,后台应用程序会被临时列入白名单几分钟。当一个应用程序在白名单上时,它可以无限制地启动服务,并且其后台服务被允许运行。
因此,为了使其在Android 8.0(API 26)中工作,通常可以使用JobScheduler任务来替换后台服务,这将在满足某些条件时安排任务执行。
或者,您可以让一个服务在后台持续检查这个问题,但通过通知用户有后台运行的东西,并且提供通知,这实际上使得这个服务成为一个前台服务。但是一旦通知消失或被取消,服务就会停止。
最后,我认为在您的情况下,您可以在清单文件中注册广播接收器,因为尽管文档说

针对 Android 8.0 或更高版本的应用程序不再能够在其清单文件中注册用于隐式广播的广播接收器。

但是这些广播被豁免了上述限制。
ACTION_MEDIA_MOUNTED, ACTION_MEDIA_CHECKING, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_EJECT, ACTION_MEDIA_UNMOUNTABLE, ACTION_MEDIA_REMOVED, ACTION_MEDIA_BAD_REMOVAL

所以你应该能够让它工作,在清单文件中声明你的广播接收器。
编辑:
尝试在你的清单文件中使用这段代码。
<receiver android:name="USBBroadcastReceiver" android:exported="True">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_EJECT" />
        <action android:name="android.intent.action.MEDIA_REMOVED" />
        <action android:name="android.intent.action.MEDIA_MOUNTED" />
        <action android:name="android.intent.action.MEDIA_BAD_REMOVAL" />
        <data android:scheme="file" />
    </intent-filter>
</receiver>

也许你需要像 <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> 这样指定权限? - Pablo Rodriguez
1
尝试添加了该权限,但仍然没有成功。我认为该权限实际上是用于从应用程序中挂载和卸载文件系统的。我只是想读取挂载路径。 - androidFan
嗨,@androidFan。你试过了吗? - Pablo Rodriguez
2
我建议您还要检查一下您的设备是否真正触发了广播,可以使用adb shell dumpsys activity broadcasts history命令来检查。我曾经遇到过这个问题,后来发现我的设备在插入USB闪存驱动器时甚至没有触发广播。 - JacquesBauer
我不得不添加IntentFilter#addDataScheme(“file”)才能接收android.intent.action.MEDIA_MOUNTED。对我来说似乎很奇怪,但是嘿... - JohnyTex
显示剩余9条评论

2

Intent.ACTION_MEDIA_MOUNTED 对我也没有起作用。以下方式是可行的,使用一个像这样的 ContentObserver:

Uri uri = DocumentsContract.buildRootsUri("com.android.externalstorage.documents");

ContentObserver contentObserver = new ContentObserver(handler) {
    @Override
    public void onChange(boolean selfChange) {
        StorageManager sm = (StorageManager) requireContext().getSystemService(Context.STORAGE_SERVICE);
        // Check for new volumes here:
        for (StorageVolume storageVolume : sm.getStorageVolumes()) {
        }
    }
};

context.getContentResolver().registerContentObserver(uri, false, contentObserver);

这是Android源代码中触发变化的代码(点击此处)。

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接