发送文件给其他设备
本节将向您介绍如何设计应用程序,以使用Android Beam文件传输将大文件发送到另一台设备。要发送文件,您请求使用NFC和外部存储的权限,测试以确保您的设备支持NFC,并提供URI到Android Beam文件传输。
Android Beam文件传输功能有以下要求:
- 适用于大文件的Android Beam文件传输仅适用于Android 4.1(API级别16)及更高版本。
- 要传输的文件必须位于外部存储器中。
- 您要传输的每个文件必须是全局可读的。您可以通过调用File.setReadable(true,false)方法来设置此权限。
- 您必须为要传输的文件提供文件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。
指定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;
}
}
}