|
1 | 1 | 10.使用NFC分享文件 |
2 | 2 | === |
3 | 3 |
|
| 4 | +`Android`系统允许在程序之间使用`Android Beam`的文件传输功能来传输一些比较大的文件。该功能提供了一些简单的`API`可以让用户通过简单的触摸操作来开启文件的传输。`Android Beam`会自动从一个机器拷贝文件到另一个机器中,并且在拷贝结束时通知用户。 |
4 | 5 |
|
| 6 | +`Android Beam`可以用来处理规模较大的数据,而在`Android4.0`(`API Level 14`)引入的`Android BeamNDEF`传输`API`则用来处理规模较小的数据,比如:`URI`或者消息数据等。另外,`Android Beam`仅仅是`Android NFC`框架提供的众多特性之一,它允许你从`NFC`标签中读取`NDEF`消息。 |
5 | 7 |
|
| 8 | +发送文件给其他设备 |
| 9 | +--- |
| 10 | + |
| 11 | +接下来我们就看一下如何设置你的应用使用`Android Beam`文件传输传递文件给其他应用。想要发送文件,你需要请求使用`NFC`以及外部存储设备的权限,要先测试一下你的设备是否支持`NFC`功能并且提供`URIs`个`Android Beam`。 |
| 12 | + |
| 13 | +`Android Beam`文件传输功能有以下几个要求: |
| 14 | + |
| 15 | +- `Android Beam`打文件传输只有在`Android`4.1(API 16)及以上才可以使用。 |
| 16 | +- 你想要传输的文件必须要在内部存储中。如果想要使用外部存储中的文件时,请阅读[Using the External Storage](http://developer.android.com/guide/topics/data/data-storage.html#filesExternal) |
| 17 | +- 每个想要传输的文件必须是全局可读的。可以通过调用`File.setReadable(true,false)`来设置它的权限。 |
| 18 | +- 必须要提供想要传输的文件的`URI`。`Android Beam`文件传输无法处理由`FileProvider.getUriForFile`生成的`content URI`。 |
| 19 | + |
| 20 | +###在Manifest中声明该功能 |
| 21 | + |
| 22 | +首先,编辑程序中的`manifest`文件来声明程序所需相应的权限和功能。 |
| 23 | + |
| 24 | +#####请求权限 |
| 25 | + |
| 26 | +想要允许应用使用`Android Beam`文件传输功能使用`NFC`从外部存储中发送文件,必须要在`manifest`文件中声明一下权限: |
| 27 | + |
| 28 | +`NFC` |
| 29 | + |
| 30 | +```xml |
| 31 | + <uses-permission android:name="android.permission.NFC" /> |
| 32 | +``` |
| 33 | +`READ_EXTERNAL_STORAGE` |
| 34 | +```xml |
| 35 | +<uses-permission |
| 36 | + android:name="android.permission.READ_EXTERNAL_STORAGE" /> |
| 37 | +``` |
| 38 | + |
| 39 | +#####指定NFC功能 |
| 40 | + |
| 41 | +可以在`manifest`中添加`<uses-feature>`标签来声明应用使用`NFC`功能。将`android:required`属性设置为`true`,这样可以保证你应用只有在支持`NFC`功能时才可以使用。 |
| 42 | +如下: |
| 43 | +```xml |
| 44 | +<uses-feature |
| 45 | + android:name="android.hardware.nfc" |
| 46 | + android:required="true" /> |
| 47 | +``` |
| 48 | +如果`NFC`制作为应用的一个可选功能,在不支持`NFC`功能时仍然希望应用可以运行,这时就需要将`android:required`属性设置为`false`,并且在代码中进行测试`NFC`. |
| 49 | + |
| 50 | +#####指定Android Beam文件传输 |
| 51 | + |
| 52 | +由于`Android Beam`文件传输只有在`Android`4.1(`API`16)及以上版本才可以用,如果`Android Beam`文件传输作为应用的一个重要功能,你就必须指定`<uses-sdk>`属性的`android:minSdkVersion=16`。或者你也可以将`android:minSdkVersion`设置为其他值,并向下面这样在代码中测试平台版本。 |
| 53 | + |
| 54 | + |
| 55 | +###检测是否支持Android Beam文件传输 |
| 56 | + |
| 57 | +在`manifest`文件中指定`NFC`功能是可选的,可以像下面这样操作: |
| 58 | +```xml |
| 59 | +<uses-feature android:name="android.hardware.nfc" android:required="false" /> |
| 60 | +``` |
| 61 | +一旦指定`android:required=false`,就必须在代码中检测是否支持`Android Beam`文件传输。 |
| 62 | + |
| 63 | +想要在代码中检测是否支持`Android Beam`文件传输功能,可以通过调用`PackageManager.hasSystemFeature()`方法,并且传递`FEATURE_NFC`作为参数来进行检测。接下来就是通过`SDK_INT`的值来检查当前的系统版本是否支持`Android Beam`功能。 |
| 64 | +如果系统支持,可以获取一个`NFC controller`的实例,通过它可以用`NFC`硬件进行交互。例如: |
| 65 | +```java |
| 66 | +public class MainActivity extends Activity { |
| 67 | + ... |
| 68 | + NfcAdapter mNfcAdapter; |
| 69 | + // Flag to indicate that Android Beam is available |
| 70 | + boolean mAndroidBeamAvailable = false; |
| 71 | + ... |
| 72 | + @Override |
| 73 | + protected void onCreate(Bundle savedInstanceState) { |
| 74 | + ... |
| 75 | + // NFC isn't available on the device |
| 76 | + if (!PackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) { |
| 77 | + /* |
| 78 | + * Disable NFC features here. |
| 79 | + * For example, disable menu items or buttons that activate |
| 80 | + * NFC-related features |
| 81 | + */ |
| 82 | + ... |
| 83 | + // Android Beam file transfer isn't supported |
| 84 | + } else if (Build.VERSION.SDK_INT < |
| 85 | + Build.VERSION_CODES.JELLY_BEAN_MR1) { |
| 86 | + // If Android Beam isn't available, don't continue. |
| 87 | + mAndroidBeamAvailable = false; |
| 88 | + /* |
| 89 | + * Disable Android Beam file transfer features here. |
| 90 | + */ |
| 91 | + ... |
| 92 | + // Android Beam file transfer is available, continue |
| 93 | + } else { |
| 94 | + mNfcAdapter = NfcAdapter.getDefaultAdapter(this); |
| 95 | + ... |
| 96 | + } |
| 97 | + } |
| 98 | + ... |
| 99 | +} |
| 100 | +``` |
6 | 101 |
|
7 | | -建立文件分享 |
| 102 | +###创建一个提供文件的回调方法 |
| 103 | + |
| 104 | +一旦确定了系统支持`Android Beam`文件传输功能,接下来就要添加一个当`Android Beam`文件传输功能发现用户想要发送文件给其他啊设备时的系统回调方法。在该方法中,返回一个`Uri`对象的数组。`Android Beam`文件通过这些`URIs`来拷贝文件给其他设备。 |
| 105 | + |
| 106 | +想要添加回调方法,需要实现`NfcAdapter.CreateBeamUrisCallback`接口和`createBeamUris()`方法。如下: |
| 107 | +```java |
| 108 | +public class MainActivity extends Activity { |
| 109 | + ... |
| 110 | + // List of URIs to provide to Android Beam |
| 111 | + private Uri[] mFileUris = new Uri[10]; |
| 112 | + ... |
| 113 | + /** |
| 114 | + * Callback that Android Beam file transfer calls to get |
| 115 | + * files to share |
| 116 | + */ |
| 117 | + private class FileUriCallback implements |
| 118 | + NfcAdapter.CreateBeamUrisCallback { |
| 119 | + public FileUriCallback() { |
| 120 | + } |
| 121 | + /** |
| 122 | + * Create content URIs as needed to share with another device |
| 123 | + */ |
| 124 | + @Override |
| 125 | + public Uri[] createBeamUris(NfcEvent event) { |
| 126 | + return mFileUris; |
| 127 | + } |
| 128 | + } |
| 129 | + ... |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +一旦实现了这个方法,可以通过调用`setBeamPushUrisCallback()`方法来将该回调提供给`Android Beam`文件传输。 |
| 134 | +```java |
| 135 | +public class MainActivity extends Activity { |
| 136 | + ... |
| 137 | + // Instance that returns available files from this app |
| 138 | + private FileUriCallback mFileUriCallback; |
| 139 | + ... |
| 140 | + @Override |
| 141 | + protected void onCreate(Bundle savedInstanceState) { |
| 142 | + ... |
| 143 | + // Android Beam file transfer is available, continue |
| 144 | + ... |
| 145 | + mNfcAdapter = NfcAdapter.getDefaultAdapter(this); |
| 146 | + /* |
| 147 | + * Instantiate a new FileUriCallback to handle requests for |
| 148 | + * URIs |
| 149 | + */ |
| 150 | + mFileUriCallback = new FileUriCallback(); |
| 151 | + // Set the dynamic callback for URI requests. |
| 152 | + mNfcAdapter.setBeamPushUrisCallback(mFileUriCallback,this); |
| 153 | + ... |
| 154 | + } |
| 155 | + ... |
| 156 | +} |
| 157 | +``` |
| 158 | + |
| 159 | +###指定发送的文件 |
| 160 | + |
| 161 | +为了将一个或更多个文件发送给其他支持`NFC`的设备,需要为每一个文件获取一个`File URI`(一个具有文件格式(`file scheme`)的`URI`),然后将它们添加至一个`Uri`对象数组中。要传输一个文件,你必须也有读该文件的权限。例如,下面的例子展示的是你如何根据文件名获取它的`File URI`,然后将`URI`添加至数组当中: |
| 162 | + |
| 163 | +```java |
| 164 | +/* |
| 165 | + * Create a list of URIs, get a File, |
| 166 | + * and set its permissions |
| 167 | + */ |
| 168 | +private Uri[] mFileUris = new Uri[10]; |
| 169 | +String transferFile = "transferimage.jpg"; |
| 170 | +File extDir = getExternalFilesDir(null); |
| 171 | +File requestFile = new File(extDir, transferFile); |
| 172 | +requestFile.setReadable(true, false); |
| 173 | +// Get a URI for the File and add it to the list of URIs |
| 174 | +fileUri = Uri.fromFile(requestFile); |
| 175 | +if (fileUri != null) { |
| 176 | + mFileUris[0] = fileUri; |
| 177 | +} else { |
| 178 | + Log.e("My Activity", "No File URI available for file."); |
| 179 | +} |
| 180 | +``` |
| 181 | + |
| 182 | +接收其他设备的文件 |
8 | 183 | --- |
9 | 184 |
|
| 185 | +`Android Beam`文件传输会将文件拷贝到接受设备的一个特别的目录中。可以使用`Android Media Scanner`来浏览拷贝的文件,也可以将文件添加到`MediaaStore`中。 |
| 186 | + |
| 187 | +###响应请求并显示数据 |
| 188 | + |
| 189 | +当`Android Beam`文件传输将文件拷贝至接收设备后,它会发布一个通知,该通知包含有一个`Intent`,该`Intent`拥有`ACTION_VIEW`这一`Action`,首个被传输文件的`MIME`类型,以及一个指向第一个文件的`URI`。当用户点击了这个通知后,`Intent`会被发送至系统。想要在应用中响应该`Intent`,就需要在`<activity>`中添加`<intent-filter>`标签。在`<intent-filter>`中添加以下子标签: |
| 190 | + |
| 191 | +```xml |
| 192 | +<action android:name="android.intent.action.VIEW" /> |
| 193 | +Matches the ACTION_VIEW intent sent from the notification. |
| 194 | +<category android:name="android.intent.category.CATEGORY_DEFAULT" /> |
| 195 | +Matches an Intent that doesn't have an explicit category. |
| 196 | +<data android:mimeType="mime-type" /> |
| 197 | +Matches a MIME type. Specify only those MIME types that your app can handle. |
| 198 | +``` |
| 199 | + |
| 200 | +例如,下面就是具体的添加示例: |
| 201 | +```xml |
| 202 | +<activity |
| 203 | + android:name="com.example.android.nfctransfer.ViewActivity" |
| 204 | + android:label="Android Beam Viewer" > |
| 205 | + ... |
| 206 | + <intent-filter> |
| 207 | + <action android:name="android.intent.action.VIEW"/> |
| 208 | + <category android:name="android.intent.category.DEFAULT"/> |
| 209 | + ... |
| 210 | + </intent-filter> |
| 211 | +</activity> |
| 212 | +``` |
| 213 | + |
| 214 | +###获取拷贝文件的目录 |
| 215 | + |
| 216 | +`Android Beam`文件传输一次性将所有文件拷贝到目标设备的一个目录中,`Android Beam`文件传输通知所发出的`Intent`中包含有`URI`,该`URI`指向了第一个被传输的文件。然而,你的应用程序也有可能接收到除了`Android Beam`文件传输之外的某个来源所发出的含有`ACTION_VIEW`这一`Action`的`Intent`。为了明确你应该如何处理接收的`Intent`,你需要检查它的`Scheme`和`Authority`。 |
| 217 | + |
| 218 | +可以调用`Uri.getScheme()`获得`URI`的`Scheme`,下面的代码展示了如何确定`Scheme`并对`URI`进行相应的处理: |
| 219 | +```java |
| 220 | +public class MainActivity extends Activity { |
| 221 | + ... |
| 222 | + // A File object containing the path to the transferred files |
| 223 | + private File mParentPath; |
| 224 | + // Incoming Intent |
| 225 | + private Intent mIntent; |
| 226 | + ... |
| 227 | + /* |
| 228 | + * Called from onNewIntent() for a SINGLE_TOP Activity |
| 229 | + * or onCreate() for a new Activity. For onNewIntent(), |
| 230 | + * remember to call setIntent() to store the most |
| 231 | + * current Intent |
| 232 | + * |
| 233 | + */ |
| 234 | + private void handleViewIntent() { |
| 235 | + ... |
| 236 | + // Get the Intent action |
| 237 | + mIntent = getIntent(); |
| 238 | + String action = mIntent.getAction(); |
| 239 | + /* |
| 240 | + * For ACTION_VIEW, the Activity is being asked to display data. |
| 241 | + * Get the URI. |
| 242 | + */ |
| 243 | + if (TextUtils.equals(action, Intent.ACTION_VIEW)) { |
| 244 | + // Get the URI from the Intent |
| 245 | + Uri beamUri = mIntent.getData(); |
| 246 | + /* |
| 247 | + * Test for the type of URI, by getting its scheme value |
| 248 | + */ |
| 249 | + if (TextUtils.equals(beamUri.getScheme(), "file")) { |
| 250 | + mParentPath = handleFileUri(beamUri); |
| 251 | + } else if (TextUtils.equals( |
| 252 | + beamUri.getScheme(), "content")) { |
| 253 | + mParentPath = handleContentUri(beamUri); |
| 254 | + } |
| 255 | + } |
| 256 | + ... |
| 257 | + } |
| 258 | + ... |
| 259 | +} |
| 260 | +``` |
| 261 | + |
| 262 | +#####从文件的URI获取目录 |
| 263 | + |
| 264 | +如果接收的`Intent`包含一个`File URI`,则该`URI`包含了一个文件的绝对文件名,它包括了完整的路径和文件名。对于`Android Beam`文件传输来说,目录路径指向了其它被传输文件的位置(如果有其它传输文件的话),要获得这个目录路径,需要取得`URI`的路径部分(`URI`中除去`file:`前缀的部分),根据路径创建一个`File`对象,然后获取这个`File`的父目录: |
| 265 | +```java |
| 266 | +public String handleFileUri(Uri beamUri) { |
| 267 | + // Get the path part of the URI |
| 268 | + String fileName = beamUri.getPath(); |
| 269 | + // Create a File object for this filename |
| 270 | + File copiedFile = new File(fileName); |
| 271 | + // Get a string containing the file's parent directory |
| 272 | + return copiedFile.getParent(); |
| 273 | +} |
| 274 | +``` |
10 | 275 |
|
| 276 | +#####从content URI中获取目录 |
11 | 277 |
|
| 278 | +如果接收的`Intent`包含一个`Content URI`,这个`URI`可能指向的是存储于`MediaStore Content Provider`的目录和文件名。你可以通过检测`URI`的`Authority`值来判断它是否是来自于`MediaStore`的`Content URI`。一个`MediaStore`的`Content URI`可能来自`Android Beam`文件传输也可能来自其它应用程序,但不管怎么样,你都能根据该`Content URI`获得一个目录路径和文件名。 |
12 | 279 |
|
13 | | -###指定FileProvider |
| 280 | +你也可以接收一个含有`ACTION_VIEW`这一`Action`的`Intent`,它包含的`Content URI`针对于`Content Provider`,而不是`MediaStore`,在这种情况下,这个`Content URI`不包含`MediaStore`的`Authority`,且这个`URI`一般不指向一个目录。 |
14 | 281 |
|
| 282 | +< **Note:**对于`Android Beam`文件传输,接收在含有`ACTION_VIEW`的`Intent`中的`Content URI`时,如果第一个接收的文件,其`MIME`类型为`audio/`,`image/`或者`video/*`,`Android Beam`文件传输会在它存储传输文件的目录内运行`Media Scanner`,以此为媒体文件添加索引。同时`Media Scanner`将结果写入`MediaStore`的`Content Provider`,之后它将第一个文件的`Content URI`回递给`Android Beam`文件传输。这个`Content URI`就是你在通知`Intent`中所接收到的。要获得第一个文件的目录,你需要使用该`Content URI`从`MediaStore`中获取它。 |
15 | 283 |
|
16 | 284 |
|
| 285 | +#####确定content provider |
17 | 286 |
|
18 | | - |
| 287 | +为了明确能从`Content URI`中获取文件目录,可以通过调用`Uri.getAuthority()`获取`URI`的`Authority`,以此确定与该`URI`相关联的`Content Provider`。其结果有两个可能的值: |
19 | 288 |
|
| 289 | +`MediaStore.AUTHORITY` |
| 290 | + 表明这个`URI`关联了被`MediaStore`记录的一个文件或者多个文件。可以从`MediaStore`中获取文件的全名,目录名就自然可以从文件全名中获取。 |
| 291 | + |
| 292 | +其他值 |
| 293 | + 来自其他`Content Provider`的`Content URI`。可以显示与该`Content URI`相关联的数据,但是不要尝试去获取文件目录。 |
| 294 | + |
| 295 | +要从`MediaStore`的`Content URI`中获取目录,我们需要执行一个查询操作,它将`Uri`参数指定为收到的`ContentURI`,将`MediaColumns.DATA`列作为投影(`Projection`)。返回的`Cursor`对象包含了`URI`所代表的文件的完整路径和文件名。该目录路径下还包含了由`Android Beam`文件传输传送到该设备上的其它文件。 |
20 | 296 |
|
| 297 | +下面的代码展示了该如何测试`Content URI`的`Authority`,并获取传输文件的路径和文件名: |
| 298 | +```java |
| 299 | +... |
| 300 | +public String handleContentUri(Uri beamUri) { |
| 301 | + // Position of the filename in the query Cursor |
| 302 | + int filenameIndex; |
| 303 | + // File object for the filename |
| 304 | + File copiedFile; |
| 305 | + // The filename stored in MediaStore |
| 306 | + String fileName; |
| 307 | + // Test the authority of the URI |
| 308 | + if (!TextUtils.equals(beamUri.getAuthority(), MediaStore.AUTHORITY)) { |
| 309 | + /* |
| 310 | + * Handle content URIs for other content providers |
| 311 | + */ |
| 312 | + // For a MediaStore content URI |
| 313 | + } else { |
| 314 | + // Get the column that contains the file name |
| 315 | + String[] projection = { MediaStore.MediaColumns.DATA }; |
| 316 | + Cursor pathCursor = |
| 317 | + getContentResolver().query(beamUri, projection, |
| 318 | + null, null, null); |
| 319 | + // Check for a valid cursor |
| 320 | + if (pathCursor != null && |
| 321 | + pathCursor.moveToFirst()) { |
| 322 | + // Get the column index in the Cursor |
| 323 | + filenameIndex = pathCursor.getColumnIndex( |
| 324 | + MediaStore.MediaColumns.DATA); |
| 325 | + // Get the full file name including path |
| 326 | + fileName = pathCursor.getString(filenameIndex); |
| 327 | + // Create a File object for the filename |
| 328 | + copiedFile = new File(fileName); |
| 329 | + // Return the parent directory of the file |
| 330 | + return new File(copiedFile.getParent()); |
| 331 | + } else { |
| 332 | + // The query didn't work; return null |
| 333 | + return null; |
| 334 | + } |
| 335 | + } |
| 336 | +} |
| 337 | +... |
| 338 | +``` |
21 | 339 |
|
22 | 340 | --- |
23 | 341 |
|
|
0 commit comments