6.4 Android NFC P2P开发进阶
Android NFC开发实战详解
本节将介绍Android API 16+中引入的针对NFC P2P功能开发的新功能——文件传输进行介绍。该功能包括setBeamPushUrisCallback和setBeamPushUris两个方法。通过本节的介绍,大家可以结合NFC和蓝牙或WiFi很轻松的实现Android设备之间大数据(如图片、音乐等)的传输。
6.4.1 Beam实现文件传输的方法
Android4.1(Jelly Bean,Android API 16)新增了一种基于NFC和蓝牙(或WiFi)实现Android设备之间大数据传递的新方法,其中包含setBeamPushUrisCallback( )和setBeamPushUris( )两个方法。该方法比传统的蓝牙传输少了显示配对的过程,在你的应用无需考虑更多的其他工作同时还能发挥蓝牙传输的优势。
1.setBeamPushUrisCallback( )方法的原型
public void setBeamPushUrisCallback (NfcAdapter.CreateBeamUrisCallback callback, Activity activity)
其中,callback为通过Android Beam发送一个或多个动态生成的Uri(s)的回调接口;activity为当前调用该方法的activity,即push Uri(s)的Activity。
使用setBeamPushUrisCallback()方法时,应注意以下几点:
(1)该方法可以在Activity中任何位置调用(onDestroy()之前)。因为Uri只有Activity在前台状态(resume()状态)下才可用,所以Android的官方建议在OnCreate()中调用该方法。同时,由于该方法并不阻塞线程,因此可以在UI主线程中使用。
(2)使用该方法时,如果callback为null,则调用该方法的Activity的Uri Push功能将会disable。
(3)当同时使用该方法和setNdefPushMessage(NdefMessage, Activity, Activity...) 或setNdefPush MessageCallback(NfcAdapter.CreateNdefMessageCallback, Activity, Activity...)方法时,该方法具有较高优先级。
(4)关于该方法的使用,官方提供的使用范例如下(关于更详细的使用方法,读者可以参考本节后面的具体实例)。
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null)
return; // NFC not available on this device
nfcAdapter.setBeamPushUrisCallback(callback, this);
}
(5)如果用户的手机不支持蓝牙或WiFi,那么该方法将失效,使用该方法时则不会有任何反应。
(6)使用该方法需要在AndroidManifest.xml中添加NFC权限。
(7)在Android API 16+以上的系统中使用。
2.setBeamPushUris ( )方法的原型
public void setBeamPushUris (Uri[] uris, Activity activity):其中,uris为用于Android Beam的一个或多个Uri(s);activity为当前调用该方法的activity,即push Uri(s)的Activity。
使用setBeamPushUris()方法时,应注意以下几点:
(1)提供的每一个Uri中的Uri Scheme必须包含“file”或“content”。
(2)该方法可以在Activity中任何位置调用(onDestroy()之前)。因为Uri只有Activity在前台状态(resume()状态)下才可用,所以Android的官方建议在OnCreate()中调用该方法。同时,由于该方法并不阻塞线程,所以可以在UI主线程中使用。
(3)使用该方法时,如果Uri为null,则调用该方法的Activity的Uri Push功能将会disable。
(4)当同时使用该方法和setNdefPushMessage(NdefMessage, Activity, Activity...)或setNdefPush MessageCallback(NfcAdapter.CreateNdefMessageCallback, Activity, Activity...)方法时,该方法具有较高优先级。
(5)关于该方法的使用,官方提供的使用范例如下(关于更详细的使用方法,读者可以参考本节后面的具体实例)。
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null)
return; // NFC not available on this device
nfcAdapter.setBeamPushUris(new Uri[]
{ uri1, uri2 }, this);
}
}
(6)如果用户的手机不支持蓝牙或WiFi,那么该方法将失效,使用该方法时则不会有任何反应。
(7)如果希望动态的提供Uri(s),那么可以使用上述的setBeamPushUrisCallback()方法。
(8)使用该方法需要在AndroidManifest.xml中添加NFC权限。
(9)在Android API 16+以上的系统中使用。
3.setBeamPushUrisCallback( ) 和setBeamPushUris( ) 的区别和选择
setBeamPushUrisCallback( )提供的是一个回调接口。当使用这个回调接口时,用户每Beam一次,该回调函数执行一次。如果待分享的Uri是动态的,会随着activity中的用户环境变化而变化,就可以使用该回调方法;反之,当待分享的Uri是静态的,是不会改变的,就可以使用setBeam PushUris( )方法提前定义好它们。总之,setBeamPushUris( )方法是在初始化Beam的同时立即提供Uri对象值,而setBeamPushUrisCallback( )回调方法在初始化Beam时并无需提供Uri对象值,而是在建立Beam连接时提供Uri对象值。
6.4.2 Beam文件传输实例1:setBeamPushUris
在Beam文件传输实例1中,本书对setBeamPushUris ( )方法实现Android Beam传输Uri功能进行了实例描述。由于Android Beam实现文件传输的方法中接收端可以由操作系统完成的,无需在App中处理,因此该实例中仅包括Uri发送端。Uri的Push部分主要包括两个步骤,分别为:
(1)在Activity中创建文件Uri。
(2)在需要的地方调用setBeamPushUris。
具体参见实例代码中对应的注释,详细代码如下:
1. package skyseraph.nfc_demo.p2p.beam.app; //声明包
2. import skyseraph.android.util.LogUtil; //导入相关类
3. ……//该处省略了导入相关类的代码
4. public class BeamFileDemo1 extends Activity implements OnClickListener
5. {
6. private static final String Tag_ASSIST = "[BeamFileDemo1]-";
7. private Context mContext = null;
8. private boolean flagNFC = false;
9. private boolean flagVersion = false;
10. private static int mRequestCode = 1001;
11. // NFC相关
12. private NfcAdapter mNfcAdapter = null;
13.
14. @Override
15. protected void onCreate(Bundle savedInstanceState)
16. {
17. // TODO Auto-generated method stub
18. super.onCreate(savedInstanceState);
19. mContext = this;
20. LogUtil.i(MyConstant.Tag, Tag_ASSIST + "into onCreate");
21. flagNFC = checkNFCFunction();
22. LogUtil.i(MyConstant.Tag, Tag_ASSIST + "flagNFC=" + flagNFC);
23. if (flagNFC)
24. {
25. checkSystemVersion();
26. LogUtil.i(MyConstant.Tag, Tag_ASSIST + "flagVersion=" + flagVersion);
27. if(flagVersion)
28. {
29. // BNM步骤1: Create 文件Uri
30. Intent i = new Intent(Intent.ACTION_GET_CONTENT);
//用户选择某种特殊数据并返回
31. i.setType("image/*");
//查看类型,如果是其他类型,如视频则替换成 video/,或 /*
32. startActivityForResult(i, mRequestCode);
33. }
34. }
35. }
36.
37.
38. /*
39. * (non-Javadoc)
40. *
41. * @see android.app.Activity#onActivityResult(int, int,
42. * android.content.Intent)
43. */
44. @Override
45. protected void onActivityResult(int requestCode, int resultCode, Intent data)
46. {
47. // TODO Auto-generated method stub
48. // super.onActivityResult(requestCode, resultCode, data);
49. if (requestCode == mRequestCode && resultCode == RESULT_OK)
50. {
51. Uri[] uriArray = new Uri[]
52. { data.getData() };
53. LogUtil.i(MyConstant.Tag, Tag_ASSIST + "uriArray=" + uriArray);
54.
55. // BNM步骤2:call setBeamPushUris anywhere your want
56. mNfcAdapter.setBeamPushUris(uriArray, this);
57.
58. Button btn = new Button(this);
59. btn.setText("Touch this to finish!");
60. btn.setTextSize(30);
61. btn.setOnClickListener(this);
62. setContentView(btn);
63. }
64. }
65.
66. @Override
67. public void onClick(View v)
68. {
69. finish();
70. }
71.
72. /**
73. * NFC Function Check By skyseraph 2013-2
74. */
75. private boolean checkNFCFunction()
76. {
77. // TODO Auto-generated method stub
78. mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
79. if (mNfcAdapter == null)
80. {
81. // mTextView.setText("NFC apdater is not available");
82. Dialog dialog = null;
83. CustomDialog.Builder customBuilder = new CustomDialog.Builder (mContext);
84. customBuilder.setTitle(getString(R.string.inquire)).setMessage(getString(R.string.
nfc_notice2))
85. .setIcon(R.drawable.dialog_icon2)
86. .setNegativeButton(getString(R.string.no), new Dialog
Interface.OnClickListener()
87. {
88. public void onClick(DialogInterface dialog, int which)
89. {
90. dialog.dismiss();
91. finish();
92. }
93. }).setPositiveButton(getString(R.string.yes), new Dialog
Interface.OnClickListener()
94. {
95. public void onClick(DialogInterface dialog, int which)
96. {
97. dialog.dismiss();
98. finish();
99. }
100. });
101. dialog = customBuilder.create();
102. dialog.setCancelable(false);// back
103. dialog.setCanceledOnTouchOutside(false);
104. SetDialogWidth(dialog).show();
105. return false;
106. } else
107. {
108. if (!mNfcAdapter.isEnabled())
109. {
110. Dialog dialog = null;
111. CustomDialog.Builder customBuilder = new CustomDialog.Builder
(mContext);
112. customBuilder.setTitle(getString(R.string.inquire)).setMessage(getString(R.string.
nfc_notice3))
113. .setIcon(R.drawable.dialog_icon2)
114. .setNegativeButton(getString(R.string.no), new
DialogInterface.OnClickListener()
115. {
116. public void onClick(DialogInterface dialog, int which)
117. {
118. dialog.dismiss();
119. finish();
120. }
121. }).setPositiveButton(getString(R.string.yes), new
DialogInterface.OnClickListener()
122. {
123. public void onClick(DialogInterface dialog, int which)
124. {
125. dialog.dismiss();
126. Intent setnfc = new Intent(Settings.
ACTION_WIRELESS_SETTINGS);
127. // Intent setnfc = new
128. // Intent(Settings.ACTION_NFC_SETTINGS);
129. startActivity(setnfc);
130. }
131. });
132. dialog = customBuilder.create();
133. dialog.setCancelable(false);// back
134. dialog.setCanceledOnTouchOutside(false);
135. SetDialogWidth(dialog).show();
136. return false;
137. } else if (!mNfcAdapter.isNdefPushEnabled())
138. {
139. Intent setnfc = new Intent(Settings.ACTION_NFCSHARING_SETTINGS);
140. startActivity(setnfc);
141. return false;
142. } else
143. {
144. return true;
145. }
146. }
147. }
148.
149. /**
150. * @param dialog
151. * @return
152. */
153. private Dialog SetDialogWidth(Dialog dialog)
154. {
155. DisplayMetrics dm = new DisplayMetrics();
156. // 取得窗口属性
157. getWindowManager().getDefaultDisplay().getMetrics(dm);
158. // 窗口的宽度
159. int screenWidth = dm.widthPixels;
160. // 窗口高度
161. int screenHeight = dm.heightPixels;
162. WindowManager.LayoutParams params = dialog.getWindow().getAttributes();
163. if (screenWidth > screenHeight)
164. {
165. params.width = (int) (((float) screenHeight) * 0.875);
166.
167. } else
168. {
169. params.width = (int) (((float) screenWidth) * 0.875);
170. }
171. dialog.getWindow().setAttributes(params);
172.
173. return dialog;
174. }
175.
176. /**
177. * check System Version()
178. */
179. private void checkSystemVersion()
180. {
181. // TODO Auto-generated method stub
182. // if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
183.
184. LogUtil.i(MyConstant.Tag, Tag_ASSIST + "Product Model=" + android.os. Build.MODEL + ", "+ android.os.Build.VERSION.SDK + ", " + android.os.Build.
185. VERSION.RELEASE);
186. String systemModel;
187. String releaseVersion;
188. String sdkVersion;
189. systemModel = android.os.Build.MODEL;
190. sdkVersion = android.os.Build.VERSION.SDK;
191. releaseVersion = android.os.Build.VERSION.RELEASE;
192. if (Integer.parseInt(sdkVersion) > 15)
193. {
194. flagVersion = true;
195. } else
196. {
197. flagVersion = false;
198. Toast.makeText(mContext, "Your android system version is low to API-16", Toast.LENGTH_SHORT).show();
199. }
200. }
201. }
第21行为实现NFC功能的检测。在使用Android Beam功能前,需要确保设备支持NFC功能、NFC功能可用,且Android Beam功能是enable。具体可通过isEnabled() 和 isNdefPushEnabled()函数实现,参考代码第72~147行checkNFCFunction()函数。
第25行为实现系统版本的检测,因为setBeamPushUris ( )方法是在Android API 16+中才提供,所以此处加入了系统版本检测的代码。具体参考代码第176~200行checkSystemVersion()函数,该函数中主要通过android.os.Build.VERSION.SDK检测Android系统SDK API以及通过android.os.Build.VERSION.RELEASE检测Android系统版本。
第20~32行为BNM步骤1阶段,即准备Uri数据。代码通过Intent.ACTIONGET CONTENT进行过滤选择需要生产Uri的文件类型,本例中采用的是图片。
第38~64行中为onActivityResult()的处理,即用户选择了某个图片文件后进入到该函数中(根据第32行调用的startActivityForResult(i, mRequestCode)),具体包含了BNM步骤2阶段部分(即调用setBeamPushUris ( )方法)和UI操控部分(设置Button,用户触控结束该Activity)。
在AndroidManifest.xml中声明Activity,并添加NFC权限,其代码如下:
1. <uses-permission android:name="android.permission.NFC" />
2. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
3. <uses-feature
android: name="android.hardware.nfc"
android:required="true" />
4. <activity
5. android:name="skyseraph.nfc_demo.p2p.beam.app.BeamFileDemo1"
6. android:label="NFC_Demo_BeamFile-1" >
7. <intent-filter>
8. <action android:name="android.intent.action.MAIN" />
9.
10. <category android:name="android.intent.category.skyseraph_nfc_demo" />
11. </intent-filter>
12. </activity>
第1行为APP添加NFC权限。
第2行为添加APP读取外部存储器权限。注意,在Android API 17+中已不再强调需要这个权限,但未来的平台版本可能会对想要从外部存储器上读取文件的应用程序要求这个权限。因此,为了确保兼容性,在它变成必须的要求之前,申请这个权限。
第3行为添加只有存在NFC功能的设备才能使用你的应用。
Beam文件传输实例1的具体效果如图6-13所示,其中,图6-13(a)为初次启动界面,即提示用户选择待分享的图片,用户选择完后,显示图6-13(b)所示的界面,此时,用户触碰界面结束当前分享界面。
6.4.3 Beam文件传输实例2:setBeamPushUrisCallback
在Beam文件传输实例2中,本书对setBeamPushUrisCallback ( )方法实现Android Beam传输Uri功能进行了实例描述。同实例1,由于Android Beam实现文件传输的方法中接收端可以由操作系统完成的,无需在App中处理,因此实例2中也仅包括Uri发送端。Uri的Push部分主要包括三个步骤,分别为:
(1)在Activity中实现CreateBeamUrisCallback接口;
(2)在需要的地方调用setBeamPushUrisCallback;
(3)回调函数中实现Beam Data。
具体参见实例代码中对应的注释,详细代码如下:
1. package skyseraph.nfc_demo.p2p.beam.app; //声明包
2. import skyseraph.android.util.LogUtil; //导入相关类
3. ……//该处省略了导入相关类的代码
4. public class BeamFileDemo2 extends Activity implements CreateBeamUrisCallback,
OnClickListener
5. { // BNM步骤1:在你的Activity中实现CreateBeamUrisCallback接口(implements)
6. private static final String Tag_ASSIST = "[BeamFileDemo2]-";
7. private Context mContext = null;
8. private boolean flagNFC = false;
9. private static int mRequestCode = 1001;
10. private Intent mIntent;
11. // NFC相关
12. private NfcAdapter mNfcAdapter = null;
13.
14. @Override
15. protected void onCreate(Bundle savedInstanceState)
16. {
17. // TODO Auto-generated method stub
18. super.onCreate(savedInstanceState);
19. mContext = this;
20. LogUtil.i(MyConstant.Tag, Tag_ASSIST + "into onCreate");
21. flagNFC = checkNFCFunction();
22. LogUtil.i(MyConstant.Tag, Tag_ASSIST + "flagNFC=" + flagNFC);
23.
24. if (flagNFC)
25. {
26. Intent i = new Intent(Intent.ACTION_GET_CONTENT);
// 用户选择某种特殊数据并返回
27. i.setType("image/"); // 查看类型,如果是其他类型,如视频则替换成 video/,或 /
28. startActivityForResult(i, mRequestCode);
29. }
30. }
31.
32. // BNM步骤3:回调函数中实现Beam Data。
33. // 当发现有支持Beam的手机时,该回调接口会自动激活,你只需将你需要Beam的文件Uri准备好
// 并返回即可。
34. // 本例中Uri为用户选择的图片为例。
35. @Override
36. public Uri[] createBeamUris(NfcEvent event)
37. {
38. // TODO Auto-generated method stub
39. LogUtil.i(MyConstant.Tag, Tag_ASSIST + "into createBeamUris");
40. Uri[] uriArray = new Uri[]
41. { mIntent.getData() };
42. LogUtil.i(MyConstant.Tag, Tag_ASSIST + "uriArray=" + uriArray);
43. return uriArray;
44. }
45.
46. /*
47. * (non-Javadoc)
48. *
49. * @see android.app.Activity#onActivityResult(int, int,
50. * android.content.Intent)
51. */
52. @Override
53. protected void onActivityResult(int requestCode, int resultCode, Intent data)
54. {
55. // TODO Auto-generated method stub
56. // super.onActivityResult(requestCode, resultCode, data);
57. if (requestCode == mRequestCode && resultCode == RESULT_OK)
58. {
59. mIntent = data;
60. // BNM步骤2:call setBeamPushUrisCallback anywhere your want
61. mNfcAdapter.setBeamPushUrisCallback(this, this);
62.
63. Button btn = new Button(this);
64. btn.setText("Touch this to finish!");
65. btn.setTextSize(30);
66. btn.setOnClickListener(this);
67. setContentView(btn);
68. }
69. }
70.
71. @Override
72. public void onClick(View v)
73. {
74. finish();
75. }
76.
77. /**
78. * NFC Function Check By skyseraph 2013-2
79. */
80. private boolean checkNFCFunction()
81. {
82. // Beam文件传输实例1中的checkNFCFunction()函数,该处省略
83. }
84.
85. /**
86. * @param dialog
87. * @return
88. */
89. private Dialog SetDialogWidth(Dialog dialog)
90. {
91. // Beam文件传输实例1中的SetDialogWidth ()函数,该处省略
92. }
93. }
第4行为BNM步骤1阶段,即实现CreateBeamUrisCallback接口。实现该接口后,该Activity中需重载createBeamUris()函数。在该函数中返回需要Beam 的文件的Uri。
第21行为实现NFC功能的检测。在使用Android Beam功能前,需要确保设备支持NFC功能、NFC功能可用,且Android Beam功能是enable,具体通过isEnabled() 和 isNdefPushEnabled()函数实现,参考代码第77~83行checkNFCFunction()函数。
第26~28行为用户选择Uri数据,代码中通过Intent.ACTION_GET_CONTENT进行过滤选择需要生产Uri的文件类型,本例中采用的是图片。
第60~61行为BNM步骤2阶段,即调用setBeamPushUrisCallback(),调用该方法后Activity中会接收一个回调接口,一旦有发现其他设备的Beam数据,createBeamUris()就会自动调用。
第32~44行为BNM步骤3阶段,即回调接口createBeamUris(NfcEvent)中实现Beam Data,具体为将第26描述的用户选择的Uri数据获取并返回,Uri数组中的图片文件就会自动Beam to目标设备。
AndroidManifest.xml中声明Activity,并添加NFC权限,其代码如下:
1. <uses-permission android:name="android.permission.NFC" />
2. <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
3. <uses-feature
android: name="android.hardware.nfc"
android:required="true" />
4. <activity
5. android:name="skyseraph.nfc_demo.p2p.beam.app.BeamFileDemo2"
6. android:label="NFC_Demo_BeamFile-2" >
7. <intent-filter>
8. <action android:name="android.intent.action.MAIN" />
9.
10. <category android:name="android.intent.category.skyseraph_nfc_demo" />
11. </intent-filter>
12. </activity>
第1行为APP添加NFC权限。
第2行为添加APP读取外部存储器权限。注意,虽然在Android 4.2.2 (API 17)中已不再强调需要这个权限,但未来的平台版本可能会对想要从外部存储器上读取文件的应用程序要求这个权限。因此,为了确保兼容性,在它变成必须的要求之前,申请这个权限。
第3行为添加只有存在NFC功能的设备才能使用应用。
Beam文件传输实例2的具体效果如图6-14所示。其中,图6-14(a)为初次启动界面,即提示用户选择待分享的图片。用户选择完后,显示图6-14(b)所示的界面。此时,用户触碰界面结束当前分享界面。