利用 NFC 从其他设备接收文件

Android Beam 文件传输会将文件复制到接收设备上的一个特殊目录中。此外,它还会使用 Android 媒体扫描器扫描复制的文件,并将媒体文件条目添加到 MediaStore 提供程序。本课程将介绍如何在文件复制完成时进行响应,以及如何在接收设备上找到复制的文件。

响应数据显示请求

当 Android Beam 文件传输将文件复制到接收设备后,它会发布一条通知,其中包含一个具有 ACTION_VIEW 操作的 Intent、传输的第一个文件的 MIME 类型以及一个指向第一个文件的 URI。当用户点击该通知时,此 intent 便会发送至系统。为了让您的应用对此 intent 做出响应,请为应响应的 Activity<activity> 元素添加 <intent-filter> 元素。在 <intent-filter> 元素中,添加以下子元素:

<action android:name="android.intent.action.VIEW" />
匹配从通知发送的 ACTION_VIEW intent。
<category android:name="android.intent.category.CATEGORY_DEFAULT" />
匹配没有明确类别的 Intent
<data android:mimeType="mime-type" />
匹配 MIME 类型。请仅指定您的应用可以处理的 MIME 类型。

例如,以下代码段展示了如何添加可触发 Activity com.example.android.nfctransfer.ViewActivity 的 intent 过滤器:

        <activity
            android:name="com.example.android.nfctransfer.ViewActivity"
            android:label="Android Beam Viewer" >
            ...
            <intent-filter>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                ...
            </intent-filter>
        </activity>
    

注意:Android Beam 文件传输不是 ACTION_VIEW intent 的唯一来源。接收设备上的其他应用也可以发送具有该操作的 Intent。如需了解如何处理此类情况,请参阅从内容 URI 中获取目录部分。

请求文件权限

如需读取 Android Beam 文件传输所复制到设备的文件,可以请求 READ_EXTERNAL_STORAGE 权限。例如:

        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

如果您要将传输的文件复制到应用自己的存储区域,可以请求 WRITE_EXTERNAL_STORAGE 权限。WRITE_EXTERNAL_STORAGE 包含 READ_EXTERNAL_STORAGE

注意:从 Android 4.2.2(API 级别 17)开始,只有在用户选择执行 READ_EXTERNAL_STORAGE 权限时,系统才会执行。在所有情况下,该平台的未来版本都可能需要此权限。为了确保向前兼容性,请立即请求此权限(在需要使用它之前)。

由于您的应用可以控制其内部存储区域,因此您无需请求写入权限,即可将传输的文件复制到内部存储区域。

获取已复制文件的目录

Android Beam 文件传输会将单次传输中的所有文件都复制到接收设备上的一个目录中。Android Beam 文件传输通知发送的内容 Intent 中的 URI 指向第一个传输的文件。但是,您的应用也可能会从 Android Beam 文件传输以外的来源接收 ACTION_VIEW intent。如需确定应如何处理传入的 Intent,您需要检查其架构和授权。

如需获取 URI 的架构,请调用 Uri.getScheme()。以下代码段展示了如何确定架构并相应地处理 URI:

Kotlin

    class MainActivity : Activity() {
        ...
        // A File object containing the path to the transferred files
        private var parentPath: File? = null
        ...
        /*
         * Called from onNewIntent() for a SINGLE_TOP Activity
         * or onCreate() for a new Activity. For onNewIntent(),
         * remember to call setIntent() to store the most
         * current Intent
         *
         */
        private fun handleViewIntent() {
            ...
            /*
             * For ACTION_VIEW, the Activity is being asked to display data.
             * Get the URI.
             */
            if (TextUtils.equals(intent.action, Intent.ACTION_VIEW)) {
                // Get the URI from the Intent
                intent.data?.also { beamUri ->
                    /*
                     * Test for the type of URI, by getting its scheme value
                     */
                    parentPath = when (beamUri.scheme) {
                        "file" -> handleFileUri(beamUri)
                        "content" -> handleContentUri(beamUri)
                        else -> null
                    }
                }
            }
            ...
        }
        ...
    }
    

Java

    public class MainActivity extends Activity {
        ...
        // A File object containing the path to the transferred files
        private File parentPath;
        // Incoming Intent
        private Intent intent;
        ...
        /*
         * Called from onNewIntent() for a SINGLE_TOP Activity
         * or onCreate() for a new Activity. For onNewIntent(),
         * remember to call setIntent() to store the most
         * current Intent
         *
         */
        private void handleViewIntent() {
            ...
            // Get the Intent action
            intent = getIntent();
            String action = intent.getAction();
            /*
             * For ACTION_VIEW, the Activity is being asked to display data.
             * Get the URI.
             */
            if (TextUtils.equals(action, Intent.ACTION_VIEW)) {
                // Get the URI from the Intent
                Uri beamUri = intent.getData();
                /*
                 * Test for the type of URI, by getting its scheme value
                 */
                if (TextUtils.equals(beamUri.getScheme(), "file")) {
                    parentPath = handleFileUri(beamUri);
                } else if (TextUtils.equals(
                        beamUri.getScheme(), "content")) {
                    parentPath = handleContentUri(beamUri);
                }
            }
            ...
        }
        ...
    }
    

从文件 URI 中获取目录

如果传入的 Intent 包含文件 URI,该 URI 包含文件的绝对文件名,包括完整的目录路径和文件名。对于 Android Beam 文件传输,该目录路径指向其他传输的文件(如有)的位置。如需获取目录路径,请获取 URI 的路径部分(其中包含除 file: 前缀以外的所有 URI 内容)。从路径部分创建 File,然后获取 File 的父级路径:

Kotlin

        ...
        fun handleFileUri(beamUri: Uri): File? =
                // Get the path part of the URI
                beamUri.path.let { fileName ->
                    // Create a File object for this filename
                    File(fileName)
                            // Get the file's parent directory
                            .parentFile
                }
        ...
    

Java

        ...
        public File handleFileUri(Uri beamUri) {
            // Get the path part of the URI
            String fileName = beamUri.getPath();
            // Create a File object for this filename
            File copiedFile = new File(fileName);
            // Get the file's parent directory
            return copiedFile.getParentFile();
        }
        ...
    

从内容 URI 中获取目录

如果传入的 Intent 包含内容 URI,该 URI 可能指向 MediaStore 内容提供程序中存储的目录和文件名。如需检测 MediaStore 的内容 URI,您可以测试相应 URI 的授权值。MediaStore 的内容 URI 可能来自 Android Beam 文件传输,也可能来自其他应用,但在这两种情况下,您都可以检索内容 URI 的目录和文件名。

此外,您还可能会接收具有以下特征的传入 ACTION_VIEW intent:包含 MediaStore 以外的内容提供程序的内容 URI。在这种情况下,内容 URI 不包含 MediaStore 授权值,并且通常不指向目录。

注意:对于 Android Beam 文件传输,如果第一个传入文件的 MIME 类型是“audio/*”、“image/*”或“video/*”,您会在 ACTION_VIEW intent 中收到一个内容 URI,表示该文件与媒体相关。Android Beam 文件传输通过在存储已传输文件的目录中运行媒体扫描器,将它传输的媒体文件编入索引。媒体扫描器会将扫描结果写入 MediaStore 内容提供程序,然后后者会将第一个文件的内容 URI 传回至 Android Beam 文件传输。该内容 URI 即是您在通知 Intent 中收到的那个 URI。如需获取第一个文件的目录,您可以使用内容 URI 从 MediaStore 中进行检索。

确定内容提供程序

如需确定您是否可以从内容 URI 检索文件目录,请确定与 URI 相关联的内容提供程序,具体方法为:调用 Uri.getAuthority() 以获取 URI 的授权。结果有两个可能的值:

MediaStore.AUTHORITY
URI 用于由 MediaStore 跟踪的一个或多个文件。从 MediaStore 检索完整的文件名,然后从文件名中获取目录。
任何其他授权值
来自其他内容提供程序的内容 URI。显示与该内容 URI 相关联的数据,但不获取文件目录。

如需获取 MediaStore 内容 URI 对应的目录,请运行一个查询,该查询为 Uri 参数指定传入的内容 URI,并为映射指定 MediaColumns.DATA 列。返回的 Cursor 包含由 URI 表示的文件的完整路径和名称。此路径还包含 Android Beam 文件传输刚刚复制到设备上的所有其他文件。

以下代码段展示了如何测试内容 URI 的授权并检索已传输文件的路径和文件名:

Kotlin

        ...
        private fun handleContentUri(beamUri: Uri): File? =
                // Test the authority of the URI
                if (beamUri.authority == MediaStore.AUTHORITY) {
                    /*
                     * Handle content URIs for other content providers
                     */
                    ...
                // For a MediaStore content URI
                } else {
                    // Get the column that contains the file name
                    val projection = arrayOf(MediaStore.MediaColumns.DATA)
                    val pathCursor = contentResolver.query(beamUri, projection, null, null, null)
                    // Check for a valid cursor
                    if (pathCursor?.moveToFirst() == true) {
                        // Get the column index in the Cursor
                        pathCursor.getColumnIndex(MediaStore.MediaColumns.DATA).let { filenameIndex ->
                            // Get the full file name including path
                            pathCursor.getString(filenameIndex).let { fileName ->
                                // Create a File object for the filename
                                File(fileName)
                            }.parentFile // Return the parent directory of the file
                        }
                    } else {
                        // The query didn't work; return null
                        null
                    }
                }
        ...
    

Java

        ...
        public String handleContentUri(Uri beamUri) {
            // Position of the filename in the query Cursor
            int filenameIndex;
            // File object for the filename
            File copiedFile;
            // The filename stored in MediaStore
            String fileName;
            // Test the authority of the URI
            if (!TextUtils.equals(beamUri.getAuthority(), MediaStore.AUTHORITY)) {
                /*
                 * Handle content URIs for other content providers
                 */
            // For a MediaStore content URI
            } else {
                // Get the column that contains the file name
                String[] projection = { MediaStore.MediaColumns.DATA };
                Cursor pathCursor =
                        getContentResolver().query(beamUri, projection,
                        null, null, null);
                // Check for a valid cursor
                if (pathCursor != null &&
                        pathCursor.moveToFirst()) {
                    // Get the column index in the Cursor
                    filenameIndex = pathCursor.getColumnIndex(
                            MediaStore.MediaColumns.DATA);
                    // Get the full file name including path
                    fileName = pathCursor.getString(filenameIndex);
                    // Create a File object for the filename
                    copiedFile = new File(fileName);
                    // Return the parent directory of the file
                    return copiedFile.getParentFile();
                 } else {
                    // The query didn't work; return null
                    return null;
                 }
            }
        }
        ...
    

如需详细了解如何从内容提供程序检索数据,请参阅从提供程序检索数据部分。

如需其他相关信息,请参阅: