Android使用NFC传文件

发送文件给其他设备


本节将向您介绍如何设计应用程序,以使用Android Beam文件传输将大文件发送到另一台设备。要发送文件,您请求使用NFC和外部存储的权限,测试以确保您的设备支持NFC,并提供URI到Android Beam文件传输。

Android Beam文件传输功能有以下要求:

  1. 适用于大文件的Android Beam文件传输仅适用于Android 4.1(API级别16)及更高版本。
  2. 要传输的文件必须位于外部存储器中。
  3. 您要传输的每个文件必须是全局可读的。您可以通过调用File.setReadable(true,false)方法来设置此权限。
  4. 您必须为要传输的文件提供文件URI。 Android Beam文件传输无法处理由FileProvider.getUriForFile生成的内容URI。

声明Manifest中的功能

首先,修改应用Manifest以声明应用需要的权限和功能。

请求权限
要允许您的应用使用Android Beam文件传输功能使用NFC从外部存储设备发送文件,您必须在应用Manifest中请求以下权限:

NFC
允许应用程式透过NFC传送资料。要指定此权限,请将以下元素作为<manifest>元素的子元素添加:

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

READ_EXTERNAL_STORAGE
允许应用程式从外部储存装置读取。要指定此权限,请将以下元素作为<manifest>元素的子元素添加:

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

注意:从Android 4.2.2(API级别17)开始,不会强制实施此权限。未来版本的平台可能需要它从想要从外部存储读取的应用程序。为了确保向前兼容性,请在需要之前立即请求许可。

指定NFC功能

通过添加<uses-feature>元素作为<manifest>元素的子元素,指定您的应用使用NFC。将android:required属性设置为true表示您的应用程序将不会运行,除非NFC存在。

以下代码段显示如何指定<uses-feature>元素:

<uses-feature
    android:name="android.hardware.nfc"
    android:required="true" />

注意,如果你的应用程序只使用NFC作为侯选项,但假如NFC不存在你仍想它工作,你应该设置android:required为false,并在代码中测试NFC。

扫描二维码关注公众号,回复: 4893409 查看本文章

指定Android Beam文件传输

由于Android Beam文件传输仅在Android 4.1(API级别16)及更高版本中可用,如果您的应用程序依赖于Android Beam文件传输的一部分功能,您必须使用android:minSdkVersion指定<uses-sdk>元素=“16”属性。否则,您可以根据需要将android:minSdkVersion设置为另一个值,并在代码中测试平台版本。

测试Android Beam文件传输是否支持

要在您的应用清单中指定NFC是可选的,请使用以下元素:

<uses-feature android:name="android.hardware.nfc" android:required="false" />

如果你设置属性android:required =“false”,你必须在代码中测试NFC支持和Android Beam文件传输是否支持。

要测试代码中的Android Beam文件传输支持,首先通过调用带有参数FEATURE_NFC的PackageManager.hasSystemFeature()来测试设备是否支持NFC。接下来,通过测试SDK_INT的值来检查Android版本是否支持Android Beam文件传输。如果支持Android Beam文件传输,请获取NFC控制器的实例,允许您与NFC硬件通信。例如:

public class MainActivity extends Activity {
    ...
    NfcAdapter mNfcAdapter;
    // Flag to indicate that Android Beam is available
    boolean mAndroidBeamAvailable  = false;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // NFC isn't available on the device

        if (!PackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
            /*
             * Disable NFC features here.
             * For example, disable menu items or buttons that activate
             * NFC-related features
             */
            ...
        // Android Beam file transfer isn't supported
        } else if (Build.VERSION.SDK_INT <
                Build.VERSION_CODES.JELLY_BEAN_MR1) {
            // If Android Beam isn't available, don't continue.
            mAndroidBeamAvailable = false;
            /*
             * Disable Android Beam file transfer features here.
             */
            ...
        // Android Beam file transfer is available, continue
        } else {
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        ...
        }
    }
    ...
}

创建提供文件的回调方法

一旦您验证设备支持Android Beam文件传输,请添加回调方法,系统在Android Beam文件传输检测到用户想要将文件发送到另一个启用NFC的设备时调用。在这个回调方法中,返回一个Uri对象数组。 Android Beam文件传输将由这些URI表示的文件复制到接收设备。

要添加回调方法,请实现NfcAdapter.CreateBeamUrisCallback接口及其方法createBeamUris()。以下代码段显示了如何执行此操作:

public class MainActivity extends Activity {
    ...
    // List of URIs to provide to Android Beam
    private Uri[] mFileUris = new Uri[10];
    ...
    /**
     * Callback that Android Beam file transfer calls to get
     * files to share
     */
    private class FileUriCallback implements
            NfcAdapter.CreateBeamUrisCallback {
        public FileUriCallback() {
        }
        /**
         * Create content URIs as needed to share with another device
         */
        @Override
        public Uri[] createBeamUris(NfcEvent event) {
            return mFileUris;
        }
    }
    ...
}

一旦你实现了接口,通过调用setBeamPushUrisCallback()提供回调到Android Beam文件传输。以下代码段显示了如何执行此操作:

public class MainActivity extends Activity {
    ...
    // Instance that returns available files from this app
    private FileUriCallback mFileUriCallback;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Android Beam file transfer is available, continue
        ...
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        /*
         * Instantiate a new FileUriCallback to handle requests for
         * URIs
         */
        mFileUriCallback = new FileUriCallback();
        // Set the dynamic callback for URI requests.
        mNfcAdapter.setBeamPushUrisCallback(mFileUriCallback,this);
        ...
    }
    ...
}

注意:您还可以通过应用程序的NfcAdapter实例将Uri对象数组直接提供给NFC框架。如果可以在NFC触摸事件发生之前定义要传输的URI,请选择此方法。要了解有关此方法的更多信息,请搜索NfcAdapter.setBeamPushUris()。

指定要发送的文件

要将一个或多个文件传输到另一个启用NFC的设备,请为每个文件获取文件URI(具有文件方案的URI),然后将该URI添加到Uri对象数组。 要传输文件,您还必须具有该文件的永久性读取访问权限。 例如,以下代码段显示如何从文件名获取文件URI,然后将URI添加到数组:

/*
         * Create a list of URIs, get a File,
         * and set its permissions
         */
        private Uri[] mFileUris = new Uri[10];
        String transferFile = "transferimage.jpg";
        File extDir = getExternalFilesDir(null);
        File requestFile = new File(extDir, transferFile);
        requestFile.setReadable(true, false);
        // Get a URI for the File and add it to the list of URIs
        fileUri = Uri.fromFile(requestFile);
        if (fileUri != null) {
            mFileUris[0] = fileUri;
        } else {
            Log.e("My Activity", "No File URI available for file.");
        }

从其他设备接收文件

Android Beam文件传输将文件复制到接收设备上的特殊目录。它还使用Android Media Scanner扫描复制的文件,并将媒体文件的条目添加到MediaStore提供程序。下面将向您介绍如何在文件复制完成后进行响应,以及如何在接收设备上查找已复制的文件。

响应显示数据的请求

当Android Beam文件传输完成将文件复制到接收设备时,它将发布一个通知,其中包含具有操作ACTION_VIEW的Intent,传输的第一个文件的MIME类型和指向第一个文件的URI。当用户单击通知时,此Intent将发送到系统。要使您的应用程序响应此Intent,请为应该响应的Activity的<activity>元素添加一个<intent-filter>元素。在<intent-filter>元素中,添加以下子元素:

//匹配通知发送的ACTION_VIEW Intent。
<action android:name="android.intent.action.VIEW" />

//匹配没有显式类别的Intent。
<category android:name="android.intent.category.CATEGORY_DEFAULT" />

//匹配MIME类型。仅指定您的应用程序可以处理的MIME类型。
<data android:mimeType="mime-type" />

例如,以下代码段显示如何添加触发Activitycom.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。处理这种情况将在后面讨论。

请求文件权限

要读取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_VIEWIntent。要确定如何处理传入的Intent,您需要检查其方案和权限。

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

public class MainActivity extends Activity {
    ...
    // A File object containing the path to the transferred files
    private File mParentPath;
    // Incoming Intent
    private Intent mIntent;
    ...
    /*
     * 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
        mIntent = getIntent();
        String action = mIntent.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 = mIntent.getData();
            /*
             * Test for the type of URI, by getting its scheme value
             */
            if (TextUtils.equals(beamUri.getScheme(), "file")) {
                mParentPath = handleFileUri(beamUri);
            } else if (TextUtils.equals(
                    beamUri.getScheme(), "content")) {
                mParentPath = handleContentUri(beamUri);
            }
        }
        ...
    }
    ...
}

从文件URI获取目录

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

... ...
public String handleFileUri(Uri beamUri){
    //获取URI的路径部分
    String fileName = beamUri.getPath();
    //为此文件名创建一个File对象
    File copiedFile = new File(fileName);
    //获取包含文件的父目录的字符串
    return copiedFile.getParent();
}}
... ...

从内容URI获取目录

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

您还可以接收传入的ACTION_VIEWIntent,其中包含除MediaStore之外的内容提供者的内容URI。在这种情况下,内容URI不包含MediaStore权限值,并且内容URI通常不指向目录。

注意:对于Android Beam文件传输,如果第一个传入文件的MIME类型为“audio / ”,“image / ”或“video / *”,则会在ACTION_VIEWIntent中接收到一个内容URI,是媒体相关的。 Android Beam文件传输通过在存储传输文件的目录上运行Media Scanner来对其传输的媒体文件进行索引。 Media Scanner将其结果写入MediaStore内容提供程序,然后将第一个文件的内容URI传回Android Beam文件传输。此内容URI是您在通知Intent中收到的URI。要获取第一个文件的目录,您可以使用内容URI从MediaStore中检索它。

确定content provider

要确定是否可以从内容URI检索文件目录,请通过调用Uri.getAuthority()来确定与URI关联的内容提供者以获取URI的权限。结果有两个可能的值:

MediaStore.AUTHORITY

  • URI用于由MediaStore跟踪的一个或多个文件。从MediaStore检索完整的文件名,并从文件名获取目录。

任何其他权限值

  • 来自另一内容提供商的内容URI。显示与内容URI相关联的数据,但不获取文件目录。

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

以下代码段显示如何测试内容URI的权限,并检索传输文件的路径和文件名:

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 new File(copiedFile.getParent());
             } else {
                // The query didn't work; return null
                return null;
             }
        }
    }

猜你喜欢

转载自blog.csdn.net/jimo_lonely/article/details/53456523