Android允许你通过Android Beam功能来传输大文件,不过该功能只能在带有NFC并且Android版本在(4.1及以上 API 16)的设备上运行.更多关于Android Beam的内容可以看Beaming NDEF Messages to Other Devices,NFC的可以看Near Field Communication.
1. 发送文件到其他设备
要通过NFC发送文件,需要做到以下三点:
- 请求权限(NFC和external storage的使用权限).
- 测试设备是否支持NFC.
- 给Android Beam提供URI.
对于Android Beam文件传输功能,有四点要求:
- 只支持Android 4.1(API 16)及以上.
- 传输的文件必须是在external storage中.
- 所有你要发送的文件的必须是world-readable的,你也可以通过File.setReadable(true,false)来设置该权限.
- 你必须要给要传输的文件提供相应的URI(不能用 FileProvider.getUriForFile生成的Content URI).
1.1 在Manifest中声明
1.1.1 请求权限
// NFC
<uses-permission android:name="android.permission.NFC" />
//READ_EXTERNAL_STORAGE
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE" />
注意READ_EXTERNAL_STORAGE
这个权限,在API 19以前不需要,但是从API 19开始就需要了,所以这里还是要加上,为了兼容.
1.1.2 设置NFC feature
因为NFC是属于hardware feature,需要添加<uses-feature>标签,如下:
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
注意:
-
android:required
为true表示如果你的设备没有该feature,则你的app就不能运行. 如果该功能只是你的一个可选择的功能,你应该将android:required
的值设为false. - 这个属性只是告知性的,Google Play会用这个属性来过滤你的设备不支持的应用.
- 设置了<uses-feature>还要记得添加相应的权限.
1.1.3 设置SDK版本
因为Android Beam只支持Android 4.1(API 16)及以上,你过你的app必须要该功能,那么需要设置android:minSdkVersion="16"
.
1.2 测试设备是否支持Android Beam
要测试设备是否支持Android Beam,需要三步:
i. 将NFC这个featrue设置成optional,如下:
<uses-feature
android:name="android.hardware.nfc"
android:required="false" />
ii. 测试设备是否支持NFC.调用PackageManager.hasSystemFeature()方法并将FEATURE_NFC作为参数传入.
// NFC isn't available on the device
if (!getPackageManager().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
}
iii. 测试设备是否支持Android Beam.通过判断Android版本来测试.
// Android Beam file transfer isn't supported
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
}
在验证设备已经支持NFC和Android Beam后,使用时还有判断设备是否开启了NFC和Android Beam功能,可以分别用下面两个方法:
a. NfcAdapter的isEnabled()来验证NFC功能是否开启.
b. NfcAdapter的isNdefPushEnabled ()来验证是否开启了Android Beam功能.
1.3 创建提供文件的回调方法
一旦你验证了你的设备支持Android Beam来进行文件传输之后,需要添加一个回调方法来返回一组Uri对象,这些对象是你想要传输的文件的复制品的URI,但Android Beam检测到你想要分享文件给其他设备时,系统就会调用该回调方法. 要添加该回调方法,要实现NfcAdapter.CreateBeamUrisCallback这个接口然后override里面的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;
}
}
...
}
实现了接口后,就可以将该接口实现类的实例提供给Android Beam,通过setBeamPushUrisCallback()方法:
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);
...
}
...
}
注意: 除了通过setBeamPushUrisCallback()来提供URI之外,还可以使用setBeamPushUris()(方法,只不过前者是动态的,后者是固定的.
1.4 设置要发送的文件
/*
* 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.");
}
- 注意: 上述文件URI的获取是通过Uri.fromFile()方法.
- 上述的代码只是使用Android Beam的一部分,若想了解更多可以看Beaming NDEF Messages to Other Devices.
2. 从其他设备接收文件
2.1 响应展示数据的请求
当Android Beam传输问文件之后,它会弹出一个notification,里面包含有intent,这个intent由一个action为ACTION_VIEW,MIME类型为第一个文件的文件类型以及第一个文件的URI组成.当用户点击这个notification之后,这个intent就会被发出. 要让你的app来响应该intent,你需要在manifest中相应的Activity下添加<intent-filter>标签.如下示例:
<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>
2.2 请求文件读取权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
2.3 获取复制过来的文件的路径
Android Beam将所有复制过来的文件放在一个路径下,上述提到的intent中包含了第一个文件的URI,但是你的app也有可能被其他的app启动而不一定是Android Beam,而其他启动你的app的intent携带的URI可能是不一样的格式,所以你要判断Uri的scheme和authority来决定如何处理:
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);
}
}
...
}
...
}
2.3.1 处理file URI
File URI包含文件的绝对路径和文件名,处理如下:
...
public String 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 a string containing the file's parent directory
return copiedFile.getParent();
}
...
2.3.2 处理Content URI
如果intent包含的是content URI,那么该URI指向的文件的路径和文件名可能存在MediaStore中.你可以通过测试URI的authority的值来判断其是否来自MediaStore. MediaStore的URI可能是来自Android Beam传输过来的也有可能是其他app传过来的,但是两者都能拿到路径和文件名.
根据URI的authority来判断content provider的类型,然后进行相应的处理:
...
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;
}
}
}
...
网友评论