前言
之前自己也有些过NFC的相关功能,可是对于刚学Android半年的小白来说哪里知道NFC,那时也就是上网找了一段代码直接复制粘贴进去了,完全不懂为什么。今天看了有关Google对NFC的简单教学,了解了一些知识,但要达到能把NFC运用起来的程度,我还差的很远。我就今天学到的内容简单的记录下来,如果以后需要用到NFC的东西,再对它好好研究。
读完教程后我对NFC的了解也只是皮毛,如果大家对NFC感兴趣,可以去官方教程里看看这篇文章。
正文
Android允许我们通过Android Beam文件传输功能来实现大文件传输的功能。这个功能有相对比较简单的API,能让用户通过简单的触碰设备就开始文件传输。Android Beam文件传输会自动把文件从一个设备复制到另一个设备,传输完成后也会提示用户传输完成。
虽然Android Beam可以实现大文件的传输,但是在Android4.0(API14)引入的Android Beam NDEF 只能传输类似URI和小型信息这样的简单的数据。不仅如此,Android Beam也是Android的NFC框架中唯一可以从NFC标签中读取NDFC信息的功能。
这次的内容有两个:
- 向其他设备发送文件
- 从其他设备接收文件
向其他设备发送文件
如果要使用Android Beam来发送文件,首先应用需要有使用NFC和读取外部存储文件的权限,确保你的设备能够支持NFC,能给Android Beam文件传输提供URI。
Android Beam文件传输功能需要满足下列要求:
- 此功能只能在Android4.1(API16)或更高版本使用
- 要传输的文件应该位于外部存储中
- 你想传输的文件都应该是world-readable,可以调用File.setReadable(true,false)来改变文件读取权限
- 你必须提供你想传输文件的URI,此功能不能处理由FileProvider.getUriForFile产生的content URI
在Manifest文件里声明功能
请求权限
为了使用这个功能我们应该要有NFC权限和读取外部存储文件的权限,在manifest文件里加入这两个权限:
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
注意:在Android4.2.2(API 17)版本开始,这个权限不是强制需要的。但是未来的Android版本可能会改变这一设定。为了更好的向以后的Android兼容,我们不妨先声明这一权限。
明确NFC功能
APP使用NFC,还需要在manifest文件里添加 < uses-feature >元素。如果我们把 android:required 属性设置成true,就表示如果不使用NFC,那APP就不能发挥这方面的功能。
下面的片段向你展示了如何定义 < uses-feature > 元素:
<uses-feature
android:name="android.hardware.nfc"
android:required="true" />
明确Android Beam文件传输功能的开启
由于这个功能只能在Android 4.1 (API level 16)或是更高版本使用,所以我们应该要把 android:minSdkVersion 设置为16,但是如果你的APP必须向更低版本兼容,你可以把这个值继续降低,但是你必须要在程序中检测设备的系统版本,看是否能支持Android Beam文件传输功能。
测试设备是否支持Android Beam文件传输功能
如果在你的APP里,NFC只是传输文件的一种方式,你可以在manifest文件里这么定义:
<uses-feature android:name="android.hardware.nfc" android:required="false" />
如果你把 android:required 设置为false,你也必须要在代码中检查是否支持NFC和Android Beam文件传输功能。
一般步骤是这样的,首先检查系统是否支持NFC,调用带有参数FEATURE_NFC 的 PackageManager.hasSystemFeature()方法,其次通过系统版本检查判断是否支持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文件传输功能可以使用,你就要添加一个回调函数,当系统检查到用户想要向其他设备通过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);
...
}
...
}
你也可以直接把要传输文件的URI设置给NfcAdapter示例,如果你的文件在交互之前就已经确定,你可以这么做。
明确要发送的文件
要向其他支持NFC的设备传输文件,你应该得到每个文件的URI(在file 模式下的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.");
}
从其他设备接收文件
展示接收到的数据
当文件传输完成之后,系统会发出一个带有Intent的通知。这个Intent的action 是ACTION_VIEW,MIME类型时第一个被传输的文件的MME类型,以及第一个被传输文件的URI。在用户点击这个通知之后,这个Intent就会被发送出去,为了让你的APP接受到这个Intent,就要在manifest文件里定义好响应Activity的Intent filter,它应该包含下列子元素:
< action android:name=”android.intent.action.VIEW” />
- 去匹配Intent的action类型
< category android:name=”android.intent.category.CATEGORY_DEFAULT” />
- 匹配没有显式指定目录的Intent
< data android:mimeType=”mime-type” />
- 匹配APP能处理的MIME类型的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>
请求文件读取权限
为了读取传输过来的文件,应该添加 READ_EXTERNAL_STORAGE 权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
如果你想把传输来的文件复制到自己APP的存储区域里,你就应该使用 WRITE_EXTERNAL_STORAGE 权限。WRITE_EXTERNAL_STORAGE 包含了 READ_EXTERNAL_STORAGE 。
因为APP可以控制自己的内部存储区域,所以如果想要复制到内部存储中就不需要添加权限了。
获取复制文件的目录
Android Beam文件传输功能会把要传输的文件都放在一个文件夹下一次性全部传输给接收设备。通知里的Intent包含的URI是指向传输文件的第一个文件。然而带有ACTION_VIEW的Intent不止是这种情况,也能由其他APP发出。所以为了确定你应该如何处理Intent,你应该先检查它的模式schema和授权authority。
为了得到URI的schema,我们可以调用Uri.getSchema()。下面的代码将会向你演示如何处理Uri授权并处理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);
}
}
...
}
...
}
从 file URI 得到文件目录
如果得到的URI 是 file模式,这URI中就包含了传输文件的全部绝对路径和文件名,对于Android Beam文件传输功能来说,全部绝对路径是其他传输文件的所在目录。为了得到全部绝对路径,我们应先得到除去file:前缀的URI,创建一个这个路径下的File对象,然后再从这个File对象得到它的父路径:
...
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();
}
从 content URI 得到文件目录
如果传入的Intent包含一个content URI,那么这个URI可能指向一个存储在MediaStore content provider里的文件目录和文件名。你可以通过检查content URI的授权信息来判断它是否存储在MediaStore里。当然了,MediaStore里的content URI可能来自Android Beam文件传输功能,也可能来自其他的APP,但是你都可以都过这个content URI获得文件目录和文件名。
当然,你可能收到action也为ACTION_VIEW,也包含content URI的Intent,不过这个 content URI 也可能不存储在MediaStore里面。但这样的content URI不会包含MediaStore的授权信息,一般也不会指向文件目录和文件名。
确定content provider类型
为了确定和URI相联系的content provider类型,你应该调用Uri.getAuthority()去得到URI的授权,一般有两种情况:
MediaStore.AUTHORITY:
- 文件的URI被MediaStore追踪,你可以从中获得文件的名称和目录
Any other authority value:
来自其他content provider的 content URI,你可以展示这个content URI的信息,但是得不到他的文件目录。
为了得到MediaStore里content URI的文件目录,运行query()方法,第一个参数为content URI,第二个参数为 MediaColumns.DATA。查询完后会返回一个Cursor包含了所有文件的名录和名称。
下面的代码向你展示了如何测试content 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;
}
}
}
...
好了,今天的内容就是这么多了。