Skip to content

Commit de6f5ff

Browse files
committed
Add more training files.
1 parent cb69b02 commit de6f5ff

File tree

4 files changed

+958
-3
lines changed

4 files changed

+958
-3
lines changed

AndroidTraining/10.使用NFC分享文件.md

Lines changed: 321 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,341 @@
11
10.使用NFC分享文件
22
===
33

4+
`Android`系统允许在程序之间使用`Android Beam`的文件传输功能来传输一些比较大的文件。该功能提供了一些简单的`API`可以让用户通过简单的触摸操作来开启文件的传输。`Android Beam`会自动从一个机器拷贝文件到另一个机器中,并且在拷贝结束时通知用户。
45

6+
`Android Beam`可以用来处理规模较大的数据,而在`Android4.0`(`API Level 14`)引入的`Android BeamNDEF`传输`API`则用来处理规模较小的数据,比如:`URI`或者消息数据等。另外,`Android Beam`仅仅是`Android NFC`框架提供的众多特性之一,它允许你从`NFC`标签中读取`NDEF`消息。
57

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+
```
6101

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+
接收其他设备的文件
8183
---
9184

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+
```
10275

276+
#####从content URI中获取目录
11277

278+
如果接收的`Intent`包含一个`Content URI`,这个`URI`可能指向的是存储于`MediaStore Content Provider`的目录和文件名。你可以通过检测`URI``Authority`值来判断它是否是来自于`MediaStore``Content URI`。一个`MediaStore``Content URI`可能来自`Android Beam`文件传输也可能来自其它应用程序,但不管怎么样,你都能根据该`Content URI`获得一个目录路径和文件名。
12279

13-
###指定FileProvider
280+
你也可以接收一个含有`ACTION_VIEW`这一`Action``Intent`,它包含的`Content URI`针对于`Content Provider`,而不是`MediaStore`,在这种情况下,这个`Content URI`不包含`MediaStore``Authority`,且这个`URI`一般不指向一个目录。
14281

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`中获取它。
15283

16284

285+
#####确定content provider
17286

18-
![image](https://raw.githubusercontent.com/CharonChui/Pictures/master/share-text-screenshot.png?raw=true)
287+
为了明确能从`Content URI`中获取文件目录,可以通过调用`Uri.getAuthority()`获取`URI``Authority`,以此确定与该`URI`相关联的`Content Provider`。其结果有两个可能的值:
19288

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`文件传输传送到该设备上的其它文件。
20296

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+
```
21339

22340
---
23341

0 commit comments

Comments
 (0)